Topcoatは、フルスタックのリアクティブWebアプリを構築するためのモジュール式でバッテリーインクルードのRustフレームワークです。シンプルさと生産性を優先しています。Topcoatは完全にサーバー側でレンダリングされます。リアクティビティは、サーバーでHTMLスニペットをレンダリングし、「リアクティブな指示」をメタデータとして追加することで実現されます(HTMXに似た考え方)。
以下は、Topcoatを使った「Hello World」アプリの例です:
#[tokio::main]
async fn main() {
topcoat::start(Router::builder().discover().build()).await.unwrap();
}
#[page("/")]
async fn home() -> Result {
view! {
<!DOCTYPE html>
<html>
<head>
<title>"Hello world"</title>
topcoat::dev::script()
</head>
<body>
hello(name: "World")
</body>
</html>
}
}
#[component]
async fn hello(name: &str) -> Result {
view! {
<h1>"Hello, " (name) "!"</h1>
}
}
Motivation
技術的な詳細に入る前に、Topcoatの背景にある動機について議論しておきましょう。私の TokioConfでの講演(Rustがグリーンフィールド開発のトップ3言語になる可能性を予測したもの)をご覧になった方にとっては、これらはいずれも驚くようなことではないでしょう。
3年前に、RustがWebアプリを構築するのに優れた言語になると言ったら、おそらくあなたは私を頭がおかしいと呼ぶでしょうし、それは当然のことです。結局のところ、Webアプリは伝統的にパフォーマンスに敏感なアプリケーションではありません。そのため、仕事をこなすのに適したツールは、迅速にリリースできるものです。パフォーマンスはあれば嬉しい程度のものです。驚くことではありませんが、最も充実したWebアプリエコシステムは、JavaScript、Ruby、PHPのように生産性に重点を置いた言語にあります。
しかし、AIは完全にその計算を変えました。AIは学習障壁と生産性のギャップを埋めます。最新のコーディングAIツールが何かを構築するのにかかる時間の違いは、主に利用可能なライブラリのセットによるもので、プログラミング言語によるものではありません。オペレーターの特定の専門知識でさえ重要性が低下します。私は、Rustを書いたことがない経験豊富なソフトウェアエンジニアが、AIツールを使って初日からRustで構築する様子を見てきました。これは「雰囲気コーディング」の話ではなく、AIツールと対話的に作業して言語を学びながら進捗を出すための、汎用的なエンジニアリング経験を活用する話です。
さて、私はRustが素晴らしい言語であり、本当に素晴らしい特性(速度と信頼性)を持っていると思います。しかし、それはすでにあなたにとって機能しているスタックがあり、Rustのパフォーマンスと信頼性が必要ない場合にRustに切り替えるべきだという意味ではありません(まあ、私はそうすべきだと思いますが、それがRustへの愛情であって実用的な提案ではないこともわかっています)。しかし、多くの組織は、解決すべき問題にパフォーマンスと信頼性の高い言語が必要である理由でRustを採用しています。Rustを採用した組織には、すでにRustで上位レベルのアプリを開発する多くの理由があります。それはパフォーマンスが必要だからではなく、組織内にすでにその言語を中心とした内部インフラストラクチャ(ライブラリ、ビルドシステム、プロセスなど)があり、組織内のプログラミング言語やツールの数を最小限に抑えることは生産性の向上につながるからです。
そこで、今私たちに必要なのは、Webアプリ用のRustで生産性を高めるための豊富なライブラリエコシステムです。それが私が構築してきたものです。私は、おそらく最も困難なコンポーネントであるため、Rust用のORMである Toasty から始めました。Toasty は2026年4月から使用可能になっています。ロードマップの次のステップはWebフレームワークであり、それが Topcoat の役割です。
昨年末、私は Julien Scholz と出会い、彼のセンスの良さとRust用の優れたWebアプリフレームワークを構築する情熱に感銘を受け、彼に時間をかけて構築してもらうよう説得しました:Topcoat。
WebAssemblyを使わないリアクティビティ
Leptos や Dioxus のようなフレームワークは、コードをWebAssemblyにコンパイルしてブラウザで実行することで、高度にインタラクティブなWebアプリケーションをRustで作成するのに優れています。ただし、多くのアプリケーションはこのレベルのインタラクティビティを必要としません。そのようなユースケースでは、別のターゲットへのコンパイル、バンドルサイズや分割の懸念、クライアント/サーバー境界を越えたデータのシリアライズが負担になります。Topcoat はよりシンプルなアプローチを追求しています。すべてのマークアップはサーバーでレンダリングされるため、コンポーネントは async にすることができ、データベースにアクセスしたり、ユーザーの権限を安全に検証したりできます。リアクティビティを散りばめるために、マクロを使用して完全に型チェックされたRust式のサブセットをJavaScriptにクロスコンパイルし、WebAssemblyに触れることなくRustで作業できるようにします。この例では、<button> をクリックすると下の <p> タグが表示されます:
view! {
// クライアント側の状態変数を宣言:
signal open = false;
<button
// このボタンの「クリック時」ハンドラーとしてRustのクロージャを設定します。
// $(...) 内のコードはブラウザ内でJavaScriptとして実行されます:
@click=$(|_e| open.set(!open.get()))
>
"What is Topcoat?"
</button>
// `hidden` 属性は `open` の値を追跡し、
// ボタンが押されるたびに更新されます。
<p :hidden=$(!open.get())>"A fullstack Rust framework."</p>
}
トグルロジックは完全にブラウザ内で実行され、サーバーへの往復は必要ありません。
Topcoat は、クライアントの状態が変化するたびにUIの全体をサーバーで再レンダリングし、ページの必要な部分だけを入れ替えることもできます。ここでは、ユーザーが検索 <input> フィールドに入力すると検索結果が更新されます:
#[component]
async fn search() -> Result {
view! {
signal query = String::new();
// 現在のテキスト入力を `query` シグナルに書き込みます:
<input @input=$(|e: Event| query.set(e.target.value))>
// ユーザーが入力すると更新されます。
search_results(query: $(query.get()))
}
}
// シャードは、ルーターからAPIエンドポイントを公開する特別なタイプのコンポーネントです。
#[shard]
async fn search_results(cx: &Cx, query: String) -> Result {
// この関数はサーバーで実行されます。非同期でデータベースにアクセスできます。
view! {
<ul>
for product in search_products(cx, &query).await? {
<li>(product.name)</li>
}
</ul>
}
}
クライアント側のリアクティビティシステムはまだ開発の初期段階にあり、いくつかの制限があります。将来的にはこれを改善するための多くのアイデアがあります。その間、HTMX や Alpine.js の統合を利用することもできます。
Webインターフェースの構築は、HTMLのレンダリングよりもはるかに複雑です。Webアプリはカスタムフォント、スタイルシート、画像などを読み込む必要があります。Topcoatは asset マクロを使用した完全なアセットパイプラインを提供します:
const FERRIS: Asset = asset!("./ferris.png");
view! { <img src=(FERRIS)> }
ビルド時に、Topcoat CLIはすべてのアセットを収集またはダウンロードし、単一のディレクトリに保存します。アプリケーションの実行時に、ブラウザキャッシュを最適化するためにコンテンツハッシュを使用して提供されます。
すべての優れたデザインには独自のフォントとアイコンが必要です。Fontsource と Iconify は、自由に利用できる巨大なフォントとアイコンのライブラリを提供しています。これらはTopcoatアプリに簡単に組み込むことができます:
// Fontsourceから「Roboto」Webフォントを読み込みます。
const ROBOTO: Font = fontsource_font!(ROBOTO);
// 「feather」アイコンセットを含むRustモジュールを作成します。
iconify::include!("feather");
一貫性のあるデザインシステムとそのすべてのコンポーネントを考え出すのは困難な場合があります。コンポーネントライブラリはすぐに使い始めるのに役立ちますが、デザインが変わると柔軟性が失われることがよくあります。shadcn/ui に着想を得て、Topcoat の組み込みコンポーネントライブラリは Tailwind に基づいており、すぐに使えるコンポーネントをソースディレクトリに直接コピーします。つまり、必要なものを変更して、デザインを本当に自分のものにできるのです:
#[component]
async fn delete_card() -> Result {
view! {
card(
card_header(
card_title("Delete workspace")
card_description("This permanently removes the workspace and all of its data.")
)
card_footer(
attrs: attributes! { class="justify-end" },
button(variant: ButtonVariant::Ghost, "Cancel")
button(variant: ButtonVariant::Destructive, "Delete workspace")
)
)
}
}
Locality of behaviorを指針とする原則
人間もAIも、コードの小さな領域にまたがる推論が得意です。Topcoatは、ロジックをローカルで構成可能に保つことを可能にするために、最初からアーキテクチャが設計されています。例えば、コンポーネントが独自のデータフェッチを行うことを推奨し、データをコンポーネントの引数として渡すのではなく:
#[component]
async fn user_profile(cx: &Cx, user_id: &str) -> Result {
// このコンポーネントだけが、どのようなユーザーデータが必要かを知っています。
let user = load_user(cx, user_id).await?;
view! {
<h1>(user.name)</h1>
...
}
}
データを複数回フェッチすることを避けるために、Topcoatには Reactのキャッシュ に着想を得た、リクエストレベルの組み込みメモ化機能があります:
#[memoize]
async fn load_user(cx: &Cx, user_id: &str) -> Result<User> {
// このデータベース呼び出しは、一意の `user_id` ごとに1回だけ行われます。
db(cx).load_user_by_id(user_id).await
}
この原則は認証にもさらに拡張できます。コードベースのまったく別の部分にある、実行されるかどうかわからないミドルウェアに頼るのではなく、コンポーネント内で直接データを保護できます:
async fn require_auth(cx: &Cx) -> Result<User> {
if let Some(Session { user_id }) = current_session(cx).await? {
Ok(load_user(cx, user_id).await?)
} else {
// データは秘密に保たれ、ログインにリダイレクトします。
Err(redirect("/login").into())
}
}
#[component]
async fn user_profile(cx: &Cx) -> Result {
// `user_profile` は、ユーザーがログインしていない場合の誤用から自身を守ります!
let user = require_auth(cx).await?;
view! {
<h1>(user.name)</h1>
...
}
}
Reactのフックのように(しかし dreaded rules of hooks はありません)、関数はリクエストコンテキスト(cx)を渡すことでうまく構成できます。
Axumについて
Axum は、サーバーアプリケーションを構築するためにtokio-rsでホストされている別のクレートです。このため、TopcoatとAxumが非常に異なるユースケースをカバーすることを指摘しておきたいと思います。実際、多くのTopcoatユーザーは、プロジェクト内でAxumも使用することになると思います。Axumは低レベルのHTTPルーターであり、HTTP APIエンドポイントを簡単に構築できます。Axumを使用してリアクティブなWebアプリを構築することもできますが、多くのボイラープレートとセットアップが必要になります。Topcoatは、そのボイラープレートを削除することを目指していますが、単に低レベルのHTTPエンドポイントを構築したい場合は、引き続き Axum を利用してください。
今後の予定
これはTopcoatの最初のリリースです。Toasty とのより緊密な統合、バリデーション、メールなど、ロードマップには多くのものが含まれています。その一部は Readme で確認できます。
その間、Topcoat は今すぐ使用可能であり、データベースが必要な場合は Toasty を導入してください。試してみて、質問やフィードバックをお寄せください。Tokioの Discord の #topcoat チャンネルに全員がいます。
Happy building.
0 Comments
Log in to join the conversation.No comments yet. Be the first to share your thoughts.