Chapter 06 · the home stretch
async/await you already know — with one catch.The syntax is almost identical to C#. The difference is that Rust doesn't ship a runtime, so you pick one. Then we'll read the actual server delivering this page.
An async fn returns a Future — Rust's Task<T>. You .await it with the same meaning as C#. The big conceptual difference: a C# Task starts running the moment it's created (hot), while a Rust Future is lazy — it does nothing until something polls it by awaiting or spawning it.
async Task<string> Get() {
var r = await http.GetAsync(url);
return await r.Content
.ReadAsStringAsync();
}
async fn get() -> Result<String, Error> {
let r = fetch(url).await?;
let body = r.text().await?;
Ok(body)
}
Note .await is postfix in Rust (it chains cleanly with ?), and errors ride along as Result exactly as in chapter 4.
In .NET the thread pool and task scheduler are part of the platform. Rust keeps the language minimal: async/.await are syntax, but the executor that actually drives futures is a library you choose. In practice that's tokio — a multi-threaded, work-stealing scheduler that plays the role of the CLR thread pool.
tokio::spawn(future) ≈ Task.Run / _ = SomeAsync() fire-and-forget onto the pool. tokio::join! ≈ Task.WhenAll. tokio::select! ≈ Task.WhenAny with cancellation. The #[tokio::main] attribute is the bit that boots the runtime so your async fn main can run at all.
Everything above is now concrete in src/main.rs. Here is the heart of it — a minimal-API-style router, async handlers, and static file serving:
src/main.rs// boots the tokio runtime — like enabling async Main
#[tokio::main]
async fn main() {
let app = Router::new()
.route("/", get(home)) // app.MapGet("/", Home)
.route("/health", get(health))
.fallback_service(ServeDir::new("public")) // UseStaticFiles()
.layer(TraceLayer::new_for_http()); // middleware
let listener = tokio::net::TcpListener
::bind(addr).await.unwrap(); // await + panic-on-fail at startup
axum::serve(listener, app.into_make_service())
.await.unwrap(); // runs until killed
}
// a handler is just an async fn returning something responseable
async fn health() -> &'static str { "ok" }
Reading that with five chapters behind you, every piece should land: the async fn and .await, the .unwrap() deciding that startup failure is unrecoverable (ch. 4), the borrowed &'static str return with its lifetime (ch. 2), and a router built by chaining methods on a value you own.
The full file has a comment on nearly every line mapping it to ASP.NET Core. Open src/main.rs in your editor, change the health handler's string, and — because pages are served from disk — edit any HTML file under public/ and just refresh. Recompile (cargo run) only when you touch the Rust.
You now have the map. The territory worth exploring from here: iterators and closures (LINQ, but zero-cost and lazier), the serde crate (JSON (de)serialization, like System.Text.Json), and writing your first Result-returning library with thiserror. The canonical free book — "The Rust Programming Language" (a.k.a. "the book") — and rustlings exercises are the standard next steps.
Most of Rust is "C# with a stricter, more expressive type system and no null." The genuinely new 20% is ownership/borrowing — and it exists to buy you C++-level performance with safety the GC gave you for free. Spend your learning budget on chapter 2; everything else you already half-know.