FABRKNT
Reth Fundamentals — Your First Steps with Alloy
Inside the EVM
Lesson 8 of 11·CONTENT15 min30 XP

Treat this page as a workbench, not a blog post. The goal is to extract a reusable mental model from the source and carry it into the rest of the Fabrknt stack.

Course
Reth Fundamentals — Your First Steps with Alloy
Lesson role
CONTENT
Sequence
8 / 11

Lesson 8 — Rust: async, traits, and generics

Question

Open Alloy / Reth / Revm source and you'll always see async / trait / <T: Bound>. Make sure you can read these three. async = a function that returns a Future; trait = Java's interface; generics = type parameters for reuse.

Principle (minimum model)

  • async fn. Returns a Future (an unfinished computation). .await drives it to completion. Runs on a runtime (Tokio etc).
  • trait. A shared interface. Like Java/Kotlin's interface. impl Trait for Type implements it; resolved at compile time.
  • Box<dyn Trait> / &dyn Trait. Runtime dispatch. Multiple concrete types treated uniformly. Vtable-based; slower than static but flexible.
  • Generics <T> + bounds T: Bound. Compile-time type parameters, monomorphised → no runtime overhead.
  • impl Trait syntax. Argument &impl Provider = "anything that implements Provider"; return impl Future<Output=T> = "some concrete type, hidden".
  • async + trait. Trait methods returning async used to require #[async_trait]; from Rust 1.75 it's standard.

Worked example + steps

Rust: async, traits, and generics

Three features you have to understand to read serious Alloy/Reth code.

1. async / await — describe "wait for it"

For things that take time (network I/O, disk reads), Rust uses async/await.

async fn fetch_block_number() -> u64 {
    // imagine an HTTP request here
    42
}

#[tokio::main]
async fn main() {
    let n = fetch_block_number().await;   // .await actually runs it
    println!("{}", n);
}

async returns a "future"

An async fn doesn't run when you call it — it returns a Future. .await is what actually drives it.

What #[tokio::main] does

Rust's standard library doesn't include an async runtime. tokio is the runtime; #[tokio::main] boots it. Alloy runs on tokio.

2. Traits — "this type can do X"

A trait is like a TypeScript / Java interface, but more powerful. It declares a contract:

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

How Alloy uses traits

provider.get_block_number().await?;

provider is some type that implements the Provider trait. The actual type might be HTTP, WebSocket, or IPC — but you call it the same way. That's the power of traits.

Trait patterns you'll see often

PatternMeaning
impl Trait for Typeimplement Trait for Type
fn f<T: Trait>(x: T)accept anything implementing Trait
Box<dyn Trait>dynamic dispatch (resolve method at runtime)
async fn ... -> Result<T, E>sugar for "returns a Future implementing a trait"

3. Generics — "decide the type later"

The <i32> in Vec<i32> is a generic. Vec works for any element type, fixed at compile time.

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

let v = vec![10, 20, 30];
let f = first(&v);   // T inferred as i32

Alloy's Provider<N: Network = Ethereum>

Alloy's Provider actually carries a type parameter for which network it talks to:

let p = ProviderBuilder::new()              // defaults to Ethereum
    .connect_http(rpc_url);

let p = ProviderBuilder::new()
    .network::<Optimism>()                  // switch to OP-stack
    .connect_http(rpc_url);

The chain choice is encoded in the type — fewer runtime bugs.

4. Lifetimes (a peek)

Borrows like &str carry an implicit lifetime <'a>:

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

It says: "the returned reference lives at least as long as the inputs." For now, read-only is fine. The Intermediate tier covers writing them.

5. Putting it all together (Reth-style)

async fn my_exex<Node: FullNodeComponents>(
    mut ctx: ExExContext<Node>,
) -> eyre::Result<()> {
    while let Some(notification) = ctx.notifications.recv().await {
        // ...
    }
    Ok(())
}
  • async fn — long-running work
  • <Node: FullNodeComponents> — generic + trait bound
  • while let Some(x) = ... — pattern matching to unwrap an Option
  • .await? — async + error propagation

All combinations of what you've already seen. If you can read it, you can write it.

Next: step into the Revm world itself.

Summary (3 lines)

  • async fn = returns a Future; .await waits. Tokio runtime. Alloy's entire RPC surface is async.
  • trait = shared interface; <T: Bound> = compile-time generics; impl Trait = shorthand at argument/return positions.
  • Box<dyn Trait> = vtable runtime dispatch; generics = static monomorphisation. Next: Revm execution engine.