<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Async on</title><link>/tags/async/</link><description>Recent content in Async on</description><generator>Hugo</generator><language>en</language><lastBuildDate>Thu, 02 Oct 2025 16:55:31 +0000</lastBuildDate><atom:link href="/tags/async/index.xml" rel="self" type="application/rss+xml"/><item><title>Lesson 8: Runtime Comparison — Tokio vs async-std vs smol vs glommio</title><link>/post/rust/rust-runtime-comparison/</link><pubDate>Thu, 02 Oct 2025 16:55:31 +0000</pubDate><guid>/post/rust/rust-runtime-comparison/</guid><description>&lt;p&gt;Every few months, someone asks me &amp;ldquo;which async runtime should I use?&amp;rdquo; and every time my answer is frustrating: &amp;ldquo;it depends.&amp;rdquo; But after spending the last seven lessons understanding how runtimes work from the inside, we can finally have a real conversation about what the differences actually are, why they exist, and when each one is the right choice.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve used all four of these runtimes in production. Tokio for most things, smol for a lightweight embedded project, glommio for a storage engine prototype, and async-std briefly before migrating away from it. Let me share what I&amp;rsquo;ve learned — not from reading documentation, but from debugging real problems at 2 AM.&lt;/p&gt;</description></item><item><title>Lesson 7: Designing a Custom Async Runtime — When Tokio isn't enough</title><link>/post/rust/rust-runtime-custom-runtime/</link><pubDate>Tue, 30 Sep 2025 11:20:06 +0000</pubDate><guid>/post/rust/rust-runtime-custom-runtime/</guid><description>&lt;p&gt;A colleague once asked me, &amp;ldquo;Why would anyone build a custom async runtime when Tokio exists?&amp;rdquo; Fair question. Tokio is battle-tested, well-maintained, and fast. For 95% of use cases, it&amp;rsquo;s the right answer. But I&amp;rsquo;ve now been in three situations where it wasn&amp;rsquo;t — and each one taught me something about what a runtime actually does.&lt;/p&gt;
&lt;p&gt;The first was a latency-sensitive trading system where work-stealing&amp;rsquo;s cache invalidation was unacceptable. The second was an embedded system with no allocator. The third was a specialized database engine where we needed precise control over I/O scheduling. Each time, understanding how to build a runtime from the ground up saved the project.&lt;/p&gt;</description></item><item><title>Lesson 6: Work-Stealing Schedulers — Balancing load across cores</title><link>/post/rust/rust-runtime-work-stealing/</link><pubDate>Sat, 27 Sep 2025 13:45:50 +0000</pubDate><guid>/post/rust/rust-runtime-work-stealing/</guid><description>&lt;p&gt;I once spent two days debugging a performance issue where our Tokio application was using four cores but only one was doing any work. Three threads were idle, one was pegged at 100%. The problem wasn&amp;rsquo;t Tokio&amp;rsquo;s scheduler — it was ours. We&amp;rsquo;d accidentally created a pattern where all tasks were spawned from and waking on the same thread, and nothing triggered work-stealing because the tasks completed too quickly.&lt;/p&gt;
&lt;p&gt;That experience taught me that you can&amp;rsquo;t treat the scheduler as a black box. If you understand how work-stealing works, you can design your task topology to take advantage of it. If you don&amp;rsquo;t, you&amp;rsquo;ll write code that accidentally defeats it.&lt;/p&gt;</description></item><item><title>Lesson 5: The Reactor Pattern — How Tokio actually works</title><link>/post/rust/rust-runtime-reactor-pattern/</link><pubDate>Wed, 24 Sep 2025 09:27:13 +0000</pubDate><guid>/post/rust/rust-runtime-reactor-pattern/</guid><description>&lt;p&gt;I read the Tokio source code on a Sunday afternoon, expecting to find something inscrutable. Layers of unsafe code, impenetrable abstractions, the kind of thing that makes you question your career choices. What I actually found was a clean, well-documented reactor implementation that I could follow. Not easily — but followably. The architecture is elegant once you see how the pieces connect.&lt;/p&gt;
&lt;p&gt;This lesson is about that architecture. Not Tokio&amp;rsquo;s API — you already know how to use &lt;code&gt;tokio::spawn&lt;/code&gt; and &lt;code&gt;TcpStream&lt;/code&gt;. This is about what happens &lt;em&gt;underneath&lt;/em&gt; when you call those functions. The reactor pattern, the I/O driver, the timer wheel, and how they all feed into the executor.&lt;/p&gt;</description></item><item><title>Lesson 4: epoll/kqueue — Platform event loops</title><link>/post/rust/rust-runtime-epoll-kqueue/</link><pubDate>Sun, 21 Sep 2025 17:08:45 +0000</pubDate><guid>/post/rust/rust-runtime-epoll-kqueue/</guid><description>&lt;p&gt;Before I understood event loops, I thought async I/O was some kind of kernel magic. You register interest in a socket, and somehow the OS tells you when data arrives — without blocking a thread. How? I imagined complex kernel subsystems doing heavy lifting behind the scenes.&lt;/p&gt;
&lt;p&gt;Turns out, the mechanism is almost embarrassingly simple. The kernel maintains a list of file descriptors you care about. When something happens on one of them, it flips a bit. You ask &amp;ldquo;what happened?&amp;rdquo;, it tells you. That&amp;rsquo;s it. The entire async I/O ecosystem — Tokio, Node.js, nginx, everything — is built on this one primitive.&lt;/p&gt;</description></item><item><title>Lesson 3: io_uring — Zero-copy async I/O on Linux</title><link>/post/rust/rust-runtime-io-uring/</link><pubDate>Fri, 19 Sep 2025 10:33:08 +0000</pubDate><guid>/post/rust/rust-runtime-io-uring/</guid><description>&lt;p&gt;I ran a benchmark last year that genuinely surprised me. A simple TCP echo server using &lt;code&gt;io_uring&lt;/code&gt; was handling 40% more connections per second than the same server on &lt;code&gt;epoll&lt;/code&gt;, with measurably lower tail latencies. Not 5% — forty percent. On the same hardware, same kernel version, same application logic. That&amp;rsquo;s when I stopped treating &lt;code&gt;io_uring&lt;/code&gt; as a curiosity and started treating it as the future of Linux I/O.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;ve been building async runtimes on &lt;code&gt;epoll&lt;/code&gt; (or even &lt;code&gt;kqueue&lt;/code&gt; on macOS), &lt;code&gt;io_uring&lt;/code&gt; changes the game completely. Let me show you why.&lt;/p&gt;</description></item><item><title>Lesson 2: Building a Minimal Executor — Your own async runtime</title><link>/post/rust/rust-runtime-executor/</link><pubDate>Wed, 17 Sep 2025 14:12:37 +0000</pubDate><guid>/post/rust/rust-runtime-executor/</guid><description>&lt;p&gt;The moment I built my first executor from scratch, async Rust stopped being scary. Not because executors are simple — they&amp;rsquo;re not — but because once you see the machinery, every &amp;ldquo;mysterious&amp;rdquo; behavior has an obvious explanation. Futures hanging? The waker isn&amp;rsquo;t being called. Tasks not making progress? The executor&amp;rsquo;s run loop has a bug. Performance terrible? You&amp;rsquo;re probably polling too aggressively or not enough.&lt;/p&gt;
&lt;p&gt;So let&amp;rsquo;s build one. A real, working executor. Not a production-quality one (that&amp;rsquo;s Tokio&amp;rsquo;s job), but one that actually runs futures to completion, handles multiple tasks, and demonstrates every concept from the previous lesson.&lt;/p&gt;</description></item><item><title>Lesson 1: Future Internals — Poll, Waker, Context</title><link>/post/rust/rust-runtime-future-internals/</link><pubDate>Mon, 15 Sep 2025 08:45:22 +0000</pubDate><guid>/post/rust/rust-runtime-future-internals/</guid><description>&lt;p&gt;I thought I understood Rust futures until I tried to implement one without &lt;code&gt;async&lt;/code&gt;/&lt;code&gt;await&lt;/code&gt;. Not a toy future that immediately returns &lt;code&gt;Ready&lt;/code&gt;. A real future — one that yields, gets woken up, and resumes where it left off. That exercise broke every mental model I had and rebuilt it from scratch.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;async&lt;/code&gt;/&lt;code&gt;await&lt;/code&gt; syntax is one of Rust&amp;rsquo;s great lies. It looks simple. It &lt;em&gt;feels&lt;/em&gt; like you&amp;rsquo;re writing sequential code. Under the hood, the compiler is generating state machines, threading waker references through call graphs, and constructing self-referential structs that would make most C++ programmers nervous. Let&amp;rsquo;s rip the lid off.&lt;/p&gt;</description></item><item><title>Lesson 20: Production Async Architecture — Connection pools, retries, circuit breakers</title><link>/post/rust/rust-async-production-patterns/</link><pubDate>Wed, 12 Feb 2025 17:09:33 +0000</pubDate><guid>/post/rust/rust-async-production-patterns/</guid><description>&lt;p&gt;This is the lesson that ties everything together. Over the last 19 lessons, we&amp;rsquo;ve built up from mental models to executors, from channels to cancellation safety. Now it&amp;rsquo;s time to put it all into a production-grade architecture.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve run async Rust services handling tens of thousands of requests per second. The patterns in this lesson are the ones that survived contact with real traffic, real failures, and 3 AM pages. Nothing theoretical here — just battle-tested code.&lt;/p&gt;</description></item><item><title>Lesson 19: Testing Async Code — Mocking time and I/O</title><link>/post/rust/rust-async-testing/</link><pubDate>Mon, 10 Feb 2025 11:28:56 +0000</pubDate><guid>/post/rust/rust-async-testing/</guid><description>&lt;p&gt;Async code is harder to test than sync code. Not because the logic is more complex, but because time, I/O, and concurrency introduce non-determinism. A test that passes 99 times and fails on the 100th is worse than a test that always fails — at least the always-failing test tells you something.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve developed a set of patterns for testing async Rust that eliminate flakiness. The key insight: mock the things that make tests non-deterministic (time, network, randomness) and let everything else run for real.&lt;/p&gt;</description></item><item><title>Lesson 18: Tracing Async Code — Context propagation across .await</title><link>/post/rust/rust-async-tracing/</link><pubDate>Sat, 08 Feb 2025 15:43:29 +0000</pubDate><guid>/post/rust/rust-async-tracing/</guid><description>&lt;p&gt;Debugging async code with &lt;code&gt;println!&lt;/code&gt; is like debugging a highway pileup by asking each car individually what happened. You get disconnected fragments — &amp;ldquo;I was going north,&amp;rdquo; &amp;ldquo;I hit something,&amp;rdquo; &amp;ldquo;I heard a crash&amp;rdquo; — but no coherent story. With 500 concurrent tasks all writing to stdout, good luck finding which log line belongs to which request.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;tracing&lt;/code&gt; crate solves this. It gives you structured, contextual logging that follows your request across tasks, &lt;code&gt;.await&lt;/code&gt; points, and even service boundaries. I switched from &lt;code&gt;log&lt;/code&gt; to &lt;code&gt;tracing&lt;/code&gt; three years ago and haven&amp;rsquo;t looked back.&lt;/p&gt;</description></item><item><title>Lesson 17: Pin in Async Context — Why futures must be pinned</title><link>/post/rust/rust-async-pinning-revisited/</link><pubDate>Thu, 06 Feb 2025 08:27:44 +0000</pubDate><guid>/post/rust/rust-async-pinning-revisited/</guid><description>&lt;p&gt;I&amp;rsquo;ve been hand-waving around &lt;code&gt;Pin&lt;/code&gt; for 16 lessons. Every time we saw &lt;code&gt;Pin&amp;lt;&amp;amp;mut Self&amp;gt;&lt;/code&gt; in a &lt;code&gt;poll&lt;/code&gt; method, I said &amp;ldquo;don&amp;rsquo;t worry about it.&amp;rdquo; But now it&amp;rsquo;s time to worry about it, because without Pin, async Rust&amp;rsquo;s entire safety model falls apart.&lt;/p&gt;
&lt;p&gt;The good news: the mental model is simpler than it looks. The bad news: the &lt;em&gt;syntax&lt;/em&gt; is still ugly. Let&amp;rsquo;s deal with both.&lt;/p&gt;
&lt;h2 id="the-problem-pin-solves"&gt;The Problem Pin Solves&lt;/h2&gt;
&lt;p&gt;When the compiler turns your &lt;code&gt;async fn&lt;/code&gt; into a state machine, it needs to store local variables across &lt;code&gt;.await&lt;/code&gt; points. Sometimes those variables reference each other:&lt;/p&gt;</description></item><item><title>Lesson 16: How Async Executors Work Under the Hood — Demystifying the runtime</title><link>/post/rust/rust-async-executor-internals/</link><pubDate>Tue, 04 Feb 2025 13:56:18 +0000</pubDate><guid>/post/rust/rust-async-executor-internals/</guid><description>&lt;p&gt;There&amp;rsquo;s a moment in every async Rust developer&amp;rsquo;s journey where the runtime stops being a black box and starts being a machine you understand. For me, it was when I built a toy executor from scratch. Suddenly, &lt;code&gt;Waker&lt;/code&gt;, &lt;code&gt;Context&lt;/code&gt;, &lt;code&gt;poll_ready&lt;/code&gt; — all of it made sense. Not as abstract concepts, but as mechanical parts.&lt;/p&gt;
&lt;p&gt;This lesson won&amp;rsquo;t make you build a production executor. But it will show you how the pieces fit together, and that understanding will change how you write and debug async code.&lt;/p&gt;</description></item><item><title>Lesson 15: Backpressure — Bounded channels and flow control</title><link>/post/rust/rust-async-backpressure/</link><pubDate>Sun, 02 Feb 2025 10:33:45 +0000</pubDate><guid>/post/rust/rust-async-backpressure/</guid><description>&lt;p&gt;The fastest way to kill a production service is to accept work faster than you can process it. I learned this the hard way when a log ingestion pipeline I built consumed 50GB of RAM and crashed because I used unbounded channels everywhere. &amp;ldquo;It works fine in testing&amp;rdquo; — yeah, testing with 100 messages, not 10 million.&lt;/p&gt;
&lt;p&gt;Backpressure is the mechanism by which a system says &amp;ldquo;slow down, I&amp;rsquo;m full.&amp;rdquo; Without it, fast producers overwhelm slow consumers, and your only flow control is the OOM killer.&lt;/p&gt;</description></item><item><title>Lesson 14: Tower — The service middleware pattern</title><link>/post/rust/rust-async-tower/</link><pubDate>Fri, 31 Jan 2025 19:18:37 +0000</pubDate><guid>/post/rust/rust-async-tower/</guid><description>&lt;p&gt;I avoided Tower for months. The trait bounds looked terrifying, the documentation assumed you already knew what you were doing, and I couldn&amp;rsquo;t figure out why I&amp;rsquo;d want it when I could just write functions. Then I needed to add logging, retries, timeouts, and rate limiting to every HTTP handler in a service with 40 endpoints.&lt;/p&gt;
&lt;p&gt;Writing those as middleware that composes? That&amp;rsquo;s Tower&amp;rsquo;s whole thing. And once it clicks, you&amp;rsquo;ll never build a service without it.&lt;/p&gt;</description></item><item><title>Lesson 13: Building HTTP Clients with reqwest — Async HTTP done right</title><link>/post/rust/rust-async-http-clients/</link><pubDate>Thu, 30 Jan 2025 16:42:09 +0000</pubDate><guid>/post/rust/rust-async-http-clients/</guid><description>&lt;p&gt;Almost every backend service I&amp;rsquo;ve built makes HTTP calls to &lt;em&gt;something&lt;/em&gt; — a third-party API, another microservice, a webhook endpoint. And almost every production HTTP bug I&amp;rsquo;ve dealt with comes from one of three things: missing timeouts, not reusing connections, or ignoring response bodies.&lt;/p&gt;
&lt;p&gt;reqwest is the HTTP client for async Rust. It&amp;rsquo;s built on hyper and Tokio, handles connection pooling, TLS, cookies, compression, and all the stuff you don&amp;rsquo;t want to think about. But you still need to use it correctly.&lt;/p&gt;</description></item><item><title>Lesson 12: Async I/O — Files, sockets, DNS</title><link>/post/rust/rust-async-io/</link><pubDate>Tue, 28 Jan 2025 08:55:13 +0000</pubDate><guid>/post/rust/rust-async-io/</guid><description>&lt;p&gt;Here&amp;rsquo;s an uncomfortable truth about async file I/O: on most operating systems, it doesn&amp;rsquo;t really exist. When you call &lt;code&gt;tokio::fs::read_to_string&lt;/code&gt;, Tokio dispatches the operation to a thread pool because Linux&amp;rsquo;s file I/O isn&amp;rsquo;t truly asynchronous (yes, there&amp;rsquo;s io_uring, but Tokio doesn&amp;rsquo;t use it by default). Network I/O, on the other hand, is genuinely async through epoll/kqueue.&lt;/p&gt;
&lt;p&gt;Understanding this distinction matters. It changes how you architect things.&lt;/p&gt;
&lt;h2 id="the-async-io-traits"&gt;The Async I/O Traits&lt;/h2&gt;
&lt;p&gt;Tokio defines two core traits that mirror their std counterparts:&lt;/p&gt;</description></item><item><title>Lesson 11: Timeouts, Deadlines, and Graceful Shutdown — Bounded operations</title><link>/post/rust/rust-async-timeouts/</link><pubDate>Sun, 26 Jan 2025 12:24:51 +0000</pubDate><guid>/post/rust/rust-async-timeouts/</guid><description>&lt;p&gt;Every production outage I&amp;rsquo;ve investigated boils down to one of two things: unbounded retries or missing timeouts. A function that &amp;ldquo;usually takes 50ms&amp;rdquo; eventually takes 30 seconds because the database is overloaded, and suddenly your entire service is frozen because every thread is waiting on that one slow call.&lt;/p&gt;
&lt;p&gt;Timeouts aren&amp;rsquo;t optional in production code. They&amp;rsquo;re as important as error handling. And graceful shutdown — cleanly stopping your service when it&amp;rsquo;s time to deploy — is what separates &amp;ldquo;my service runs in production&amp;rdquo; from &amp;ldquo;my service runs in production &lt;em&gt;well&lt;/em&gt;.&amp;rdquo;&lt;/p&gt;</description></item><item><title>Lesson 10: Cancellation Safety — The silent footgun</title><link>/post/rust/rust-async-cancellation/</link><pubDate>Fri, 24 Jan 2025 09:41:22 +0000</pubDate><guid>/post/rust/rust-async-cancellation/</guid><description>&lt;p&gt;This is the lesson I wish someone had shoved in my face before I wrote my first &lt;code&gt;select!&lt;/code&gt; loop. I lost three days to a bug where messages were disappearing from a queue. No errors. No panics. Just&amp;hellip; gone. Turns out, &lt;code&gt;select!&lt;/code&gt; was cancelling a future that had already read the message from the channel but hadn&amp;rsquo;t finished processing it.&lt;/p&gt;
&lt;p&gt;Cancellation safety is the most under-discussed footgun in async Rust. If you use &lt;code&gt;select!&lt;/code&gt;, you need to understand this.&lt;/p&gt;</description></item><item><title>Lesson 9: Semaphores and Rate Limiting — Bounded async concurrency</title><link>/post/rust/rust-async-semaphores/</link><pubDate>Wed, 22 Jan 2025 15:08:36 +0000</pubDate><guid>/post/rust/rust-async-semaphores/</guid><description>&lt;p&gt;I once crashed a third-party API by spawning 10,000 concurrent requests from an async Rust service. The code was correct — every request completed (eventually). But the API&amp;rsquo;s rate limiter kicked in after 50 concurrent connections, and we got IP-banned for two hours.&lt;/p&gt;
&lt;p&gt;The fix was a single type: &lt;code&gt;tokio::sync::Semaphore&lt;/code&gt;. Five lines of code turned &amp;ldquo;as fast as possible&amp;rdquo; into &amp;ldquo;at most N at a time.&amp;rdquo;&lt;/p&gt;
&lt;h2 id="whats-a-semaphore"&gt;What&amp;rsquo;s a Semaphore?&lt;/h2&gt;
&lt;p&gt;A semaphore is a counter with a maximum value. You acquire a permit before doing work, and release it when you&amp;rsquo;re done. If all permits are taken, acquiring blocks (yields) until one becomes available.&lt;/p&gt;</description></item><item><title>Lesson 8: Async Mutexes — tokio::sync::Mutex vs std</title><link>/post/rust/rust-async-mutex/</link><pubDate>Mon, 20 Jan 2025 07:15:44 +0000</pubDate><guid>/post/rust/rust-async-mutex/</guid><description>&lt;p&gt;&amp;ldquo;Should I use &lt;code&gt;tokio::sync::Mutex&lt;/code&gt; or &lt;code&gt;std::sync::Mutex&lt;/code&gt; in async code?&amp;rdquo; I&amp;rsquo;ve seen this question in every Rust Discord server, every forum, every team Slack. And the answer most people give — &amp;ldquo;always use the async one in async code&amp;rdquo; — is wrong.&lt;/p&gt;
&lt;p&gt;The real answer depends on how long you hold the lock and whether you need to &lt;code&gt;.await&lt;/code&gt; while holding it. Get this wrong and you&amp;rsquo;ll either deadlock your runtime or tank your performance.&lt;/p&gt;</description></item><item><title>Lesson 7: Async Channels — tokio::sync::mpsc</title><link>/post/rust/rust-async-channels/</link><pubDate>Sat, 18 Jan 2025 13:52:28 +0000</pubDate><guid>/post/rust/rust-async-channels/</guid><description>&lt;p&gt;The first real async service I built had a classic architecture: an HTTP handler receives a request, puts work on a queue, a background worker processes it, and the result gets sent back. In Go, this is channels all day. In async Rust, it&amp;rsquo;s &lt;em&gt;also&lt;/em&gt; channels — but you&amp;rsquo;ve got four different kinds to choose from, and picking the wrong one leads to subtle bugs.&lt;/p&gt;
&lt;p&gt;This lesson covers all four of Tokio&amp;rsquo;s channel types and when to reach for each one.&lt;/p&gt;</description></item><item><title>Lesson 6: Streams — Async iterators</title><link>/post/rust/rust-async-streams/</link><pubDate>Thu, 16 Jan 2025 10:31:07 +0000</pubDate><guid>/post/rust/rust-async-streams/</guid><description>&lt;p&gt;Regular iterators give you one value at a time, synchronously. Futures give you one value, asynchronously. Streams give you &lt;em&gt;multiple&lt;/em&gt; values, asynchronously. It&amp;rsquo;s the obvious combination, and once you start using them, you&amp;rsquo;ll wonder how you ever processed sequences of async data without them.&lt;/p&gt;
&lt;p&gt;I first needed streams when building a log aggregator. I had dozens of log sources, each producing lines at their own pace. I needed to merge them, filter them, and process them in real time. Without streams, that code was a tangled mess of channels and select loops. With streams, it was a pipeline.&lt;/p&gt;</description></item><item><title>Lesson 5: tokio::select! — Racing futures</title><link>/post/rust/rust-async-select/</link><pubDate>Tue, 14 Jan 2025 16:19:55 +0000</pubDate><guid>/post/rust/rust-async-select/</guid><description>&lt;p&gt;A couple months ago I was building a WebSocket handler that needed to do three things simultaneously: read from the socket, check a shutdown signal, and send periodic heartbeats. With &lt;code&gt;join!&lt;/code&gt;, I&amp;rsquo;d need all three to complete. But I didn&amp;rsquo;t want them all to complete — I wanted to react to whichever one happened &lt;em&gt;first&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s what &lt;code&gt;select!&lt;/code&gt; does. It races multiple futures and gives you the result of the winner. The losers are dropped.&lt;/p&gt;</description></item><item><title>Lesson 4: Spawning Tasks and JoinHandles — Concurrent work units</title><link>/post/rust/rust-async-spawn-join/</link><pubDate>Sun, 12 Jan 2025 08:44:19 +0000</pubDate><guid>/post/rust/rust-async-spawn-join/</guid><description>&lt;p&gt;I remember the exact moment async Rust &amp;ldquo;clicked&amp;rdquo; for me. I was building an API aggregator that needed to call five different services. My first version awaited them sequentially — 2 seconds total. Then I spawned them as concurrent tasks — 400ms. Same work, 5x faster, and I didn&amp;rsquo;t need to think about threads, locks, or shared state.&lt;/p&gt;
&lt;p&gt;But spawning tasks isn&amp;rsquo;t free, and &lt;code&gt;JoinHandle&lt;/code&gt; has some sharp edges that nobody warned me about. This lesson covers the patterns you&amp;rsquo;ll use every day.&lt;/p&gt;</description></item><item><title>Lesson 3: Tokio — The runtime that powers async Rust</title><link>/post/rust/rust-async-tokio-intro/</link><pubDate>Fri, 10 Jan 2025 11:05:33 +0000</pubDate><guid>/post/rust/rust-async-tokio-intro/</guid><description>&lt;p&gt;Every async Rust tutorial shows you &lt;code&gt;#[tokio::main]&lt;/code&gt; in the first example and then moves on like that&amp;rsquo;s totally self-explanatory. It&amp;rsquo;s not. That macro hides a &lt;em&gt;lot&lt;/em&gt; of important decisions, and if you don&amp;rsquo;t understand what it&amp;rsquo;s doing, you&amp;rsquo;re going to make some costly mistakes.&lt;/p&gt;
&lt;p&gt;I once spent an entire day debugging a deadlock that happened because I was using the single-threaded runtime without realizing it. One &lt;code&gt;#[tokio::main(flavor = &amp;quot;current_thread&amp;quot;)]&lt;/code&gt; vs the default, and my entire application&amp;rsquo;s behavior changed. That shouldn&amp;rsquo;t surprise anyone — but it surprised me.&lt;/p&gt;</description></item><item><title>Lesson 2: The Future Trait — What .await actually does</title><link>/post/rust/rust-async-future-trait/</link><pubDate>Wed, 08 Jan 2025 14:37:42 +0000</pubDate><guid>/post/rust/rust-async-future-trait/</guid><description>&lt;p&gt;After I understood the mental model from lesson 1, my next question was obvious: what happens &lt;em&gt;mechanically&lt;/em&gt; when I write &lt;code&gt;.await&lt;/code&gt;? I could accept &amp;ldquo;it&amp;rsquo;s a state machine&amp;rdquo; as a hand-wave, but I wanted to see the gears turning.&lt;/p&gt;
&lt;p&gt;Turns out, the answer is beautifully simple once you strip away the syntax sugar. The entire async machinery in Rust boils down to one trait, two types, and a contract.&lt;/p&gt;
&lt;h2 id="the-future-trait-for-real-this-time"&gt;The Future Trait, For Real This Time&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; std::pin::Pin;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; std::task::{Context, Poll};
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;trait&lt;/span&gt; Future {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;type&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Output&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;poll&lt;/span&gt;(self: &lt;span style="color:#a6e22e"&gt;Pin&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;&amp;amp;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;mut&lt;/span&gt; Self&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;, cx: &lt;span style="color:#66d9ef"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;mut&lt;/span&gt; Context&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;&amp;#39;_&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;) -&amp;gt; &lt;span style="color:#a6e22e"&gt;Poll&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;Self::Output&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Three things to note:&lt;/p&gt;</description></item><item><title>Lesson 1: Async Mental Model — Futures, not threads</title><link>/post/rust/rust-async-mental-model/</link><pubDate>Mon, 06 Jan 2025 09:22:14 +0000</pubDate><guid>/post/rust/rust-async-mental-model/</guid><description>&lt;p&gt;I spent three weeks writing async Rust code that compiled, ran, and produced correct results — while having absolutely no idea what was actually happening. I was copy-pasting &lt;code&gt;async fn&lt;/code&gt;, slapping &lt;code&gt;.await&lt;/code&gt; on things, and hoping for the best. Sound familiar?&lt;/p&gt;
&lt;p&gt;The problem wasn&amp;rsquo;t syntax. The problem was that I was thinking about async Rust the same way I thought about threads in Go or Java. And that mental model is &lt;em&gt;wrong&lt;/em&gt; for Rust.&lt;/p&gt;</description></item></channel></rss>