FABRKNT
Reth Fundamentals — Alloy で動かす最初の一歩
EVM の中身を覗く
レッスン 8 / 11·CONTENT15 分30 XP
コース
Reth Fundamentals — Alloy で動かす最初の一歩
レッスンの役割
CONTENT
順序
8 / 11

レッスン8 — Rust:async・トレイト・ジェネリクス

問い

Alloy も Reth も Revm も、コードを読むと必ず async / trait / <T: Bound> が出てくる。この 3 つを最低限読めるようにする — async は Future を返す関数、trait は Java の interface 相当、ジェネリクスは型パラメータでコード再利用。

原理(最小モデル)

  • async fn. 戻り値は Future(未完了の計算)、.await で完了まで進める、Tokio 等のランタイム上で実行。
  • trait. 共有インターフェース、Java/Kotlin の interface に類似、impl Trait for Type で実装、コンパイル時解決。
  • Box<dyn Trait> / &dyn Trait. 実行時ディスパッチ、複数の具象型を統一で扱う、vtable 経由で遅い代わりに柔軟。
  • ジェネリクス <T> + 境界 T: Bound. コンパイル時に型パラメータを具象化、モノモーフ化で実行時オーバーヘッドなし。
  • impl Trait 構文. 引数 &impl Provider = 「Provider 実装の何か」、戻り値 impl Future<Output=T> = 具体的な型は隠蔽。
  • async + trait = async-trait crate or Rust 1.75+ 標準. トレイトメソッドが async を返す場合、過去は #[async_trait] 属性が必要、現在は標準対応。

具体例 + ステップで組み立てる

Rust:async・トレイト・ジェネリクス

Revmや Reth のコードを読む前に、3つの言語機能 を押さえる。これがないとAlloy/Reth本体のコードはほぼ読めない。

1. async / await — 「待つ」を書ける

ネットワーク通信のような 時間がかかる処理 を効率よく扱うための機能である。

async fn fetch_block_number() -> u64 {
    // ここに重い処理(HTTP通信など)
    42
}

#[tokio::main]
async fn main() {
    let n = fetch_block_number().await;   // .await で実際に待つ
    println!("{}", n);
}

async が返すのは「未来の値」

async fn は実行時に Future(あとで値になるもの)を返す。.await を呼ぶまで実際の実行は進まない。

#[tokio::main] の正体

Rust標準には非同期ランタイムが入っていない。tokio でランタイムを起動するのが #[tokio::main] の役割であり、Alloyは tokio 上で動く。

2. トレイト(trait) — 「○○できる」という契約

Java/TypeScriptの interface に近いが、より強力である。「この型は××できる」を宣言する。

trait HasArea {
    fn area(&self) -> f64;
}

struct Square { side: f64 }

impl HasArea for Square {
    fn area(&self) -> f64 {
        self.side * self.side
    }
}

let s = Square { side: 3.0 };
println!("{}", s.area());   // 9.0

Alloyではどう使われているか

provider.get_block_number().await?;

providerProvider トレイトを実装した何らかの型 である。HTTP・WebSocket・IPCで中身が違っても、同じインターフェースで呼べる。これがトレイトの威力である。

トレイトの最重要ポイント — Reth/Revmで頻出

パターン意味
impl Trait for Type型にトレイトを実装
fn f<T: Trait>(x: T)「Traitを実装した何か」を受け取る関数
Box<dyn Trait>動的ディスパッチ(実行時にメソッド解決)
async fn ... -> Result<T, E>async関数(Futureを返すトレイト実装の糖衣構文)

3. ジェネリクス — 「型を後から決める」

Vec<i32><i32> がジェネリクスである。Vec何でも入れられる が、コンパイル時に要素型を確定する。

fn first<T: Clone>(v: &Vec<T>) -> T {
    v[0].clone()
}

let v = vec![10, 20, 30];
let f = first(&v);   // T = i32 と推論される

Alloyの Provider<N: Network = Ethereum>

Alloy の Provider は 「どのネットワーク向けか」 を型パラメータで持つ。

let p = ProviderBuilder::new()              // デフォルトは Ethereum
    .connect_http(rpc_url);

let p = ProviderBuilder::new()
    .network::<Optimism>()                  // 別のネットワークに切り替え
    .connect_http(rpc_url);

これは「コンパイル時に型でチェーンを固定し、ランタイムバグを減らす」というRustらしい設計である。

4. ライフタイム(チラ見せ)

&str のような借用には、実は ライフタイム <'a> 注釈が暗黙にある。

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() >= y.len() { x } else { y }
}

「返す参照は、引数の参照と同じ寿命を持つ」というアノテーション。最初は読めればOK、書けるのは中級ティアで。

5. これからRevm/Rethで見る形

async fn my_exex<Node: FullNodeComponents>(
    mut ctx: ExExContext<Node>,
) -> eyre::Result<()> {
    while let Some(notification) = ctx.notifications.recv().await {
        // ...
    }
    Ok(())
}
  • async fn :時間がかかる処理
  • <Node: FullNodeComponents> :ジェネリクス+トレイト境界
  • while let Some(x) = ... :Optionの中身を取り出すパターン
  • .await? :非同期+エラー伝播

すべて、ここまでで紹介した文法の組み合わせである。読めれば、書ける。

次のレッスンで、実際のRevmの世界に入っていきましょう。

まとめ(3行)

  • async fn = Future を返す、.await で完了待ち、Tokio ランタイム上、Alloy の全 RPC メソッドが async。
  • trait = 共有インターフェース、ジェネリクス <T: Bound> でコンパイル時解決、impl Trait は引数 / 戻り値で短縮記法。
  • Box<dyn Trait> は実行時ディスパッチ(vtable)、ジェネリクスは静的ディスパッチ(モノモーフ化)、次は Revm 実行エンジン紹介。