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)..awaitdrives it to completion. Runs on a runtime (Tokio etc).trait. A shared interface. Like Java/Kotlin'sinterface.impl Trait for Typeimplements 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>+ boundsT: Bound. Compile-time type parameters, monomorphised → no runtime overhead. impl Traitsyntax. Argument&impl Provider= "anything that implements Provider"; returnimpl 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
| Pattern | Meaning |
|---|---|
impl Trait for Type | implement 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 boundwhile 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;.awaitwaits. 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.