{"slug": "who-runs-your-rust-future-hands-on-intro-to-async-rust", "title": "Who Runs Your Rust Future? Hands-On Intro to Async Rust", "summary": "A new hands-on series teaches async Rust by building a custom executor from scratch, bridging the gap between understanding Future internals and shipping production code with Tokio. The series targets developers familiar with async in other languages and Rust basics, starting with the fundamental question of who runs a Rust Future.", "body_md": "Chapter 1\n\n# Who Runs Your Rust Future? Hands-On Intro to Async Rust\n\n## On AI assistance\n\n[Discord](https://discord.com/invite/cD9qEsSjUH)and I'll work on it.\n\nAsync Rust is usually taught from one of two directions. The async book and the runtime write-ups show you the internals: how `Future`\n\n, `poll`\n\n, and `Pin`\n\nwork, and you can even hand-build a tiny executor. Tokio’s guides go the other way, you wire up a real service and it runs. Both are genuinely useful. What is harder to find is the bridge between them, the part that connects understanding how async works to actually shipping with it.\n\nThat bridge is what this series is. You build the engine yourself, the future, the waker, the executor, until you understand every piece, and then you drive the real one, Tokio, and recognize those same pieces because you built them.\n\nThis series assumes two things. First, that you’ve written `async`\n\nand `await`\n\ncode in some language (JS, C#, Python) before. Second, that you’re comfortable with Rust’s basics: structs, enums, associated functions, and closures (all covered in the free chapters of [The Impatient Programmer’s Guide to Bevy and Rust](/books/the-impatient-programmers-guide-to-bevy-and-rust/#chapters)). I’ll use JavaScript for the comparisons throughout, but the same idea exists in C# and Python.\n\n*A quick note on the series: I plan to keep at least 5 to 8 chapters free to read here, with the rest collected in a paid ebook. The free chapters may not follow the numbering in order, so some will land out of sequence.*\n\n## Future\n\nIf you’ve shipped any modern JavaScript, you’ve done this a hundred times:\n\n``` js\nasync function getUser() { /* ... */ }const user = await getUser();   // and it just... works\n```\n\nYou write `async function`\n\n, add `await`\n\n, and it runs. You never had to think about *what* runs it, because something always did. The `node`\n\nprocess ships a built-in **event loop**: a hidden engine that picks up your Promises and pushes each one forward until it’s done.\n\nIn JavaScript you never had to ask who keeps that loop running. But what about Rust? **Who actually runs your async code?** Or, in Rust’s own terms: **who runs your future?**\n\nRust does the opposite, on purpose. It ships **no event loop at all.** Nothing in the language is sitting around waiting to run your async code. If you want one, you bring it in. Or, the way we’re going to learn it, you *build* one yourself.\n\nThat sounds like a missing feature. By the end of this series it’ll feel like the opposite. But before we ask *who runs it*, let’s be precise about what `async`\n\nis.\n\nA normal function is the kind you write every day. You call it, it runs then and there, and by the next line its return value is already sitting in your variable:\n\n```\nA normal functionYou call it, it runs immediately, and hands back the finished value.fn add(a: i32, b: i32) -> i32 { a + b }let sum = add(2, 3);+Runs right now. sum is 5 before the next line even starts.\n```\n\nAn **async function** doesn’t do that. You call it and it hands you back a placeholder instead of a result. Not the value, but a thing that *represents* a value that isn’t ready yet, stamped *“I’ll be done later.”* Every async language has this placeholder; they just use different names for it:\n\n- JavaScript calls it a\n.`Promise`\n\n- Rust calls it a\n.`Future`\n\nDifferent words, identical idea: **a value that represents work that isn’t finished yet.**\n\n**Hang on, doesn’t JavaScript have a Future too?**\n\nNot as a type. You’ll still hear both words, and they’re not interchangeable: there’s a write side, filled with the value once the work finishes, and a read side, the handle you hold and await. In JavaScript the `Promise`\n\nyou await is the read side, and that read side is what Rust (along with C#, Java, and the rest) calls a `Future`\n\n. `Future`\n\nis the common name for it; JavaScript’s `Promise`\n\nis the outlier.\n\n```\nThe write sideCode doing the work calls resolve to put the value in, when it is ready.const promise = new Promise((resolve) => {  setTimeout(() => resolve(\"the data\"), 1000);+Value goes IN here, one second later.});The read sideYou await the promise itself. The event loop runs it; await just hands you the value.const data = await promise;+Value comes OUT here, with no work from you.\n```\n\nRust names the handle you hold a `Future`\n\n, because in Rust you are the one who reads it, by polling, as you’ll see in a moment. So whenever you read `Future`\n\nin this series, picture a `Promise`\n\nthat you have to read yourself.\n\nIn Node, the instant you *call* an async function, the placeholder is **live**. The hidden event loop grabs it and drives it to completion whether you’re watching or not. `await`\n\nis just you asking for the value once it’s ready. The work was always going to happen.\n\nIn Rust, calling an `async fn`\n\ndoes **nothing**:\n\n```\nCalling it does nothingIn Rust an async fn call builds a Future and stops. The body has not run.async fn get_user() -> User { /* ... */ }let f = get_user();+f is a Future. Not one line of the body has executed yet.\n```\n\n`get_user()`\n\nhanded you a `Future`\n\nand then stopped. The body hasn’t executed. It’s **lazy**: it just sits there, fast asleep, parked in a variable. It will do absolutely nothing, forever, until something **polls** it, the technical word for tapping it on the shoulder and asking *“can you make any progress?”* And since Rust ships no event loop, the thing doing that tapping has to be a runtime like Tokio that you pull in, or, the way we’ll learn it, code you write yourself.\n\nYou are holding this future. What can you actually *do* with it to get the work done and pull the value out?\n\n## Polling\n\nA `Future`\n\ngives you exactly one method. You can `poll`\n\nit. Polling is you asking the future a single question, *“can you make any progress right now?”* The future runs as far as it can, then answers one of two ways: `Ready(value)`\n\nif it finished, or `Pending`\n\nif it had to stop and wait for something.\n\nThat phrase, *“runs as far as it can,”* is the whole idea, so let’s make it concrete with a future that has a real job: checking out a shopping cart. The `async fn`\n\nloads the cart from the database, then calls a payment provider’s API to charge the card, and finally returns a confirmed order. Neither the database nor the payment API answers instantly, so the future has to stop and wait at each of those two calls. So let’s poll it, and watch how each poll drives the future forward:\n\n**Poll #1.** The future starts and fires off the database query to load the cart. The database hasn’t replied yet, so it can go no further. It sleeps right there and answers`Pending`\n\n.**Poll #2**, once the database replies. It resumes from exactly where it paused, takes the cart, and runs on until it calls the payment API. The charge is still in flight, so it sleeps again and answers`Pending`\n\n.**Poll #3**, once the payment goes through. It resumes one last time, runs clean to the end of the function, builds the confirmed order, and answers`Ready(order)`\n\n.\n\nSo *“runs as far as it can”* means this: pick up from wherever you last paused, and go forward until you either finish or hit the next thing you have to wait on. The early polls each end at a wait, so they hand back `Pending`\n\n. The last poll has nothing left to wait for, so it runs off the end of the function and hands back `Ready`\n\n. Most polls hit a wall and say *“not yet”*. One poll, eventually, runs off the end and says *“done”*. Async is just that loop: hitting walls, waiting, trying again, until the last poll finally makes it through.\n\nAnd here is the part that trips people up coming from Node. `poll`\n\nis not just *checking* on the future, it is what *runs* it. Calling the `async fn`\n\nran none of that body. The first `poll`\n\nis what starts it, and each poll after carries it one stretch further. So the rule is blunt: no poll, no progress. A future that is never polled never runs a single line. Nothing is running it in the background. The only thing that moves it forward is you calling `poll`\n\nagain.\n\n*“Cool, so I just have to call poll. That sounds easy”*. Well, let’s have a look at the function signature.\n\nLet’s understand these one by one.\n\n### The Future Itself\n\n`self: Pin<&mut Self>`\n\nBack at the start I asked you to picture a future like a `Promise`\n\n, a representation of work that is not finished yet.\n\nSo what is that representation, concretely? It is a piece of *data*. When you write an `async fn`\n\n, the compiler turns your code into a value that holds everything it needs to remember to carry on later. The future *is* that value: your async code, in data form. And as the future runs, poll by poll, that data is what moves forward.\n\nSo a future is just a small `struct`\n\n, and every field it holds is its state, the stuff it has to remember between polls.\n\nThe diagram below is a way to picture the future. Treat it as a mental model to build your intuition, not the exact, byte-level layout of a real future in memory:\n\nOur checkout future, paused mid-job, is holding things like where it stopped (at “charge card”), the `cart`\n\nit already loaded from the database, and an `item`\n\nit is still pointing at inside that cart. Each `poll`\n\npicks this state up and runs it forward, and since we need to update those fields we ask for writable access through `&mut`\n\nin the first argument.\n\nBut there’s a problem. `item`\n\npoints back into the future itself, at the `cart`\n\nfield sitting right beside it. A value pointing at its own insides. You have probably never written a struct like that, and that is no accident: normal Rust quietly steers you away from self-references. So you have never had to care where a value lives in memory. Move it into a function, push it into a `Vec`\n\n, hand it to a caller, it all just works. But if this future is moved to a new place in memory, `cart`\n\ngoes along to the new spot while `item`\n\nkeeps pointing at the old one. It is now pointing at empty space. A dangling pointer.\n\n**Why would the future move by itself to a new place in memory?**\n\nIt doesn’t move itself. Your code moves it, the same way any value gets moved in Rust:\n\n**Calling the**`async fn`\n\nitself.`checkout()`\n\nreturns the future to you. That return is already a move.**Passing it around.** A future is a value, so handing it to any function passes it by value. That is a move.**Storing it.** Push it into a`Vec`\n\n, or keep it in a struct field. All moves.\n\nI know this is getting tricky. These concepts are genuinely hard to wrap your head around, and they deserve a fuller explanation than I’ll give here. I’ll come back to all of it in a later chapter: why a future ends up pointing into itself at all, how the compiler lays it out, and why values move around in memory in the first place.\n\nFor now, just hold onto this. That `Pin`\n\nin `self: Pin<&mut Self>`\n\nis a guarantee that the future won’t move in memory while it’s being polled. That’s all you need for now.\n\n### The Waker\n\n`cx: &mut Context`\n\nSo imagine our checkout future, waiting on the payment provider. The initial poll you made starts the work and you get `Pending`\n\n. So, when do you poll again?\n\nRight away is pointless: the provider has not answered, so the future would only hand you another `Pending`\n\n. You really have two options.\n\n- First, keep polling in a tight loop until it finally says\n`Ready`\n\n. It works, but it burns a whole CPU core asking*“done yet? done yet?”*thousands of times a second while the provider is still processing. - Second, go to sleep, and let something wake you the moment the answer lands. This is what a real runtime does, and that brings us to the question: who tells you the moment has come?\n\nThat is the whole job of the `Waker`\n\n. The future knows what it is waiting on: it made the database call, it owns the socket, meaning it holds the actual network connection the reply will arrive on. Your poller does not; to it a future is a black box with a single `poll`\n\nbutton, so it has no way to know when polling is worth it again.\n\nSo `poll`\n\nhands the future a `Waker`\n\n, tucked inside the `Context`\n\n. The `Waker`\n\nis a callback that means *“wake me when I can make progress.”* The future registers it with whatever it is blocked on, the timer or the network connection the reply will arrive on, and returns `Pending`\n\n. The moment that thing is ready, it fires the waker. The waker wakes the poller, and the poller polls the future again.\n\n### The Output\n\nEvery `poll`\n\nends in one of exactly two answers: `Ready(value)`\n\n, where the value is the future’s `Output`\n\n, whatever it finally produces (an `Order`\n\n, a `u32`\n\n, a `String`\n\n), or `Pending`\n\n, meaning it is not done yet. `Ready`\n\nends the loop; `Pending`\n\nsends you back to sleep until the next wake.\n\n## Build a Oneshot Channel\n\nWe have met async’s three moving parts: the `Future`\n\n, the `poll`\n\nthat drives it, and the `Waker`\n\nthat signals when to poll again. Now let’s put them to work on a real problem.\n\nImagine a web server backed by a single database connection. A connection like that cannot be used from two places at once, so instead of letting every request grab it, you hand it to one background worker: a task whose whole job is to hold the connection and run queries on it.\n\nNow requests pour in, each needing a lookup. Say Handler A is serving `GET /products/:id`\n\nand needs that product, while Handler B is serving `GET /users/:id`\n\nand needs that user. Neither can touch the connection directly, and neither can just call the worker like a normal function, because the worker is shared: every handler drops its query into one queue, and the worker pulls them off and runs them against the connection one at a time. Getting queries *in* is easy.\n\nThe hard part is getting each answer back *out*, to the exact handler that asked and not some other one. There is no single caller to return to anymore.\n\nThe solution is to give each request a way to send its answer back. When a handler builds its request, it creates a one-time link with two ends: a sending end and a receiving end. It keeps the receiving end and tucks the sending end into the message. While the worker pulls the request off the queue and runs the query, the handler waits on its receiving end. When the result is ready, the worker pushes it through the sending end, and it arrives at that handler’s receiving end and nowhere else; waiting on it returns the result. This one-time, one-value, one-destination link is called a oneshot channel.\n\n**Is the Sender like our Waker?**\n\nYes and no. They look similar: both are handed off to someone else, and both signal *“I am done.”* But they do different jobs. The `Sender`\n\ncarries the actual value. When the worker is done, it sends the result through it. The `Waker`\n\ncarries no value at all. It just taps the poller on the shoulder and says *“poll again.”*\n\nSoon you will see how they fit together: the `Sender`\n\nwill deliver the value, and as part of doing that, it will fire the `Waker`\n\nto wake the poller up.\n\nLet’s build the Oneshot channel by hand, with the standard library and the three pieces you just met: the `Future`\n\n, its `poll`\n\n, and the `Waker`\n\n.\n\nCreate a new rust project.\n\n```\ncargo new oneshotcd oneshot\n```\n\n### Sender and Receiver\n\nStart by writing the two components of the Oneshot channel, the `Sender`\n\nand the `Receiver`\n\n. For a value dropped in at the `Sender`\n\nto come back out at the `Receiver`\n\n, both ends have to reach the *same* piece of memory: one writes the value there, the other reads it from there. Let’s call this shared piece of memory `Inner`\n\n.\n\n**Why Inner and why not SharedState?**\n\n`SharedState`\n\nwould be a fair name for it, but think about where it lives: the `Sender`\n\nand `Receiver`\n\nare the parts your program holds and passes around, and this struct hides *inside* them, behind those two handles, the bit nobody touches directly. It is the channel’s inner workings, so we will follow the usual convention and call it `Inner`\n\n.\n\nWhat should `Inner`\n\nhold? Two things, and you have already met both:\n\n**The value**, in a slot that sits empty until something is sent.** A waker.**When the`Receiver`\n\nfuture is polled and the value has not arrived yet, the future has to sleep. But something needs to wake it up the moment the value lands. So before sleeping, it leaves its`Waker`\n\nin`Inner`\n\n.\n\nLet’s start with the implementation of the `Sender`\n\nand the `Receiver`\n\n.\n\n```\nuse std::future::Future;use std::pin::{pin, Pin};use std::sync::{Arc, Mutex};use std::task::{Context, Poll, Waker};The two endsEach holds a handle to the same shared Inner.struct Sender   { inner: Arc<Mutex<Inner>> }struct Receiver { inner: Arc<Mutex<Inner>> }Inner, the shared stateOne slot for the value, one for the receiver's waker.struct Inner {    value: Option<String>,+the value, once it has been sent (None until then)    waker: Option<Waker>,+the receiver's waker, so send can wake it (None until it waits)}\n```\n\n**So what is that Arc<Mutex<…>> wrapped around Inner for?**\n\nRemember, `Inner`\n\nhas to be shared between the `Sender`\n\nand the `Receiver`\n\n, and they usually sit on separate threads. `Arc`\n\n(a reference-counted pointer) is what lets them share it: both ends hold a handle to the same `Inner`\n\n, across threads.\n\nBut `Arc`\n\nalone only gives a read-only view, and both ends need to *write* into `Inner`\n\ntoo (the sender drops in the value, the receiver leaves its waker). That is what `Mutex`\n\nadds: a lock, so one end at a time can open `Inner`\n\nand change it safely.\n\nSafely here means without the `Sender`\n\nand `Receiver`\n\nwriting at the same moment and corrupting the data.\n\nLet’s write the constructor, `oneshot`\n\n, that sets up the channel.\n\nNow, the trick for sharing memory between the `Sender`\n\nand the `Receiver`\n\nis to clone the `inner`\n\nhandle: cloning an `Arc`\n\ndoes not duplicate the `Inner`\n\n, it just hands back another pointer to the same one, exactly as the diagram above shows.\n\n``` php\nfn oneshot() -> (Sender, Receiver) {    let inner = Arc::new(Mutex::new(Inner { value: None, waker: None }));    (Sender { inner: inner.clone() }, Receiver { inner })  }\n```\n\nNow let’s write `send`\n\n. What does it need to do? Get the value into the shared `Inner`\n\nso the receiver can find it. And, if the `Receiver`\n\nfuture is already asleep and left its `Waker`\n\nin `Inner`\n\n, fire that waker so it gets polled again.\n\nLet’s build that up.\n\n``` js\nimpl Sender {    fn send(self, value: String) {        let mut inner = self.inner.lock().unwrap();+lock the Inner; nobody else can touch it while we do        inner.value = Some(value);+put the value into its slot        if let Some(waker) = inner.waker.take() {+did the receiver already sleep and leave a waker?            waker.wake();+fire it: \"there is progress, poll me again\"        }    }}\n```\n\nWe have `send`\n\ntake `self`\n\nby value, not `&self`\n\n, and that is on purpose: a oneshot fires exactly once, and taking `self`\n\nlets the compiler enforce it for us, after one call the `Sender`\n\nis consumed, so a second send will not even compile.\n\nThen we lock the `Inner`\n\n, put the value into the `value`\n\nslot, and check the `waker`\n\nslot. If the receiver already polled and left a waker, we fire it (*“there is progress, poll me again”*). If it has not polled yet, there is nothing to wake, so the value just waits in the slot for the next poll to pick it up.\n\n**Why lock?**\n\nBecause the sender and the receiver sit on different threads and both reach into the same `Inner`\n\n, so the lock makes sure they take turns instead of corrupting each other mid-write.\n\n### Poll\n\nNow let’s implement the `poll`\n\nfunction. It needs to look for the value, and react to whether it is there. So it locks the same `Inner`\n\nand checks the `value`\n\nslot.\n\nIf the value has arrived, we are done: hand it back as `Poll::Ready(value)`\n\n. If it has not, the receiver cannot finish yet, so it clones the waker out of `cx`\n\n, leave it in the `waker`\n\nslot for `send`\n\nto fire later, and return `Poll::Pending`\n\n.\n\n```\nimpl Future for Receiver {    type Output = String;    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<String> {        let mut inner = self.inner.lock().unwrap();        if let Some(value) = inner.value.take() {            Poll::Ready(value)+the value is here: hand it back, we are done        } else {            inner.waker = Some(cx.waker().clone());+not yet: leave a waker so send can reach us            Poll::Pending+and report that we are still waiting        }    }}\n```\n\nOur `Receiver`\n\ndoes nothing until something polls it, and so far nothing does. That something is a **runner**: a loop that drives a future to completion. Let’s build the smallest one that works and call it `block_on`\n\n. You hand it a future, it polls until `Ready`\n\n, and hands you the value. (A runner that juggles *many* futures at once has a name, the executor, and we build that one a few chapters from now. `block_on`\n\nis the simplest version of that.)\n\n**Why block_on and not loop?**\n\nThe loop is just the *how* on the inside. The name says what it does to you, the caller: it **blocks** the current thread (your `main`\n\n) on a future until that future resolves, then hands back the value. It is the doorway from ordinary synchronous code into async, and it is what real runtimes like Tokio call this same function, so the name will already be familiar when you meet it out in the wild.\n\n**But isn’t async supposed to be non blocking?**\n\nIt is, on the inside. Imagine a web server, whose job is to keep running and answer requests, but `main`\n\nis ordinary synchronous code: left to itself it runs to the bottom and the program exits. `block_on`\n\nis what keeps `main`\n\nalive, you hand it the server as one big future and it parks right there, driving the server for as long as it runs. That is the single block, at the top, and every request the server handles inside it stays non-blocking. That is the non-blocking you were promised.\n\nSo how should `block_on`\n\nwork? The obvious version is a tight loop: poll, and if it comes back `Pending`\n\n, poll again.\n\nBut imagine what that does while the worker is still off on its database query: `block_on`\n\n, running on the calling thread, would spin the CPU at millions of polls a second, every one of them coming back `Pending`\n\nbecause the worker has not sent yet.\n\nWe can do better. The channel already has the waker mechanism wired in, so on `Pending`\n\nwe can put the thread to sleep, as long as we hand it a waker that actually wakes the thread.\n\n### Waker\n\nBuilding a `Waker`\n\nby hand is low-level work, and we will do exactly that, from scratch, in a later chapter. For now the standard library gives us a shortcut: the `Wake`\n\ntrait. You write a single `wake`\n\nmethod on a type of your own, and `Waker::from`\n\nturns it into a real `Waker`\n\n.\n\nSo what should `wake`\n\ndo? Wake our sleeping thread, and Rust threads already have a built-in pair for exactly that. `thread::park()`\n\nputs the current thread to sleep, and calling `.unpark()`\n\non that thread’s handle wakes it back up.\n\n**If we have .unpark() then why not call it directly? Why implement the Wake trait?**\n\nBecause the future has no idea a thread is involved. Think about what `poll`\n\ncan see: it gets a `Context`\n\n, pulls a `Waker`\n\nout of it, and stores it in Inner. That is all. It does not know who is polling it.\n\nToday it is our `block_on`\n\nsleeping on a parked thread, but the same future could be polled by some other runtime that wakes things up in a completely different way. The future cannot call `.unpark()`\n\nbecause it does not know there is anything to unpark.\n\nThe Waker is the common language. The poller, whoever it is, packs *“here is how to wake me”* into a Waker and hands it in. The future just stores it and fires it, no questions asked.\n\nTime for implementation.\n\n```\nuse std::task::Wake;use std::thread::Thread;\n```\n\nNow let’s implement the wake method.\n\n```\nstruct ThreadWaker(Thread);impl Wake for ThreadWaker {    fn wake(self: Arc<Self>) {        self.0.unpark();                                                       }}\n```\n\n### Block On\n\nNow let’s write `block_on`\n\nitself. Here is what it needs to do, step by step:\n\n- Pin the future, because poll requires it (Look at the function signature of the\n`poll`\n\n). - Grab a handle to the current thread and wrap it in a\n`ThreadWaker`\n\n. This is us telling the future:*“when you are done and need to wake someone up, here is the thread to unpark.”* - Wrap that\n`Waker`\n\nin a`Context`\n\n, because that is what poll accepts. - Call\n`poll`\n\n. If it comes back`Ready`\n\n, return the value and stop. If it comes back`Pending`\n\n, park the thread and go to sleep. - When\n`send`\n\nfires the waker, the waker calls`.unpark()`\n\non the thread. The thread wakes up and goes back to step 4.\n\nSteps 1 to 3 are setup, done once. Steps 4 and 5 are the loop: poll, sleep if not ready, wake up, poll again, until the future finishes.\n\n``` php\nfn block_on<F: Future>(future: F) -> F::Output {    let mut future = pin!(future);+pin it: poll() only accepts a pinned future    let waker = Waker::from(Arc::new(ThreadWaker(thread::current())));+a waker tied to this thread, so waking it unparks us    let mut cx = Context::from_waker(&waker);+the Context that poll() reads the waker out of    loop {        match future.as_mut().poll(&mut cx) {            Poll::Ready(value) => return value,+the value is here: hand it back, done            Poll::Pending      => thread::park(),+not yet: sleep until the waker unparks us        }    }}\n```\n\n### Putting it Together\n\nNow we have every piece: the channel, the receiver that implements a future, the waker, and the runner. Let’s put them together in `main`\n\nand run it.\n\n`oneshot()`\n\nhands us back a pair, `(tx, rx)`\n\n, the two ends of the channel: `tx`\n\nis the `Sender`\n\n(`tx`\n\nis the usual name for *transmit*) and `rx`\n\nis the `Receiver`\n\n(for *receive*), which is the future we drive. We give `tx`\n\nto a worker thread that stands in for a slow database query, a half-second `sleep`\n\nplays the part of the real query here, and once it is done it sends the row back through `tx`\n\n. On the main thread, `block_on(rx)`\n\ndrives the receiver and parks until that row arrives.\n\n```\nuse std::thread;use std::time::Duration;fn main() {    let (tx, rx) = oneshot();+open the channel: tx is the Sender, rx is the Receiver (our future)    thread::spawn(move || {        thread::sleep(Duration::from_millis(500));+pretend the query takes a moment        tx.send(\"a fresh database row\".to_string());+the worker sends the result back    });    let row = block_on(rx);+park here until the row arrives    println!(\"{row}\");}\n```\n\nThe one new thing here is `move`\n\n. The worker runs on a separate thread that can keep going after `main`\n\nmoves on, so its closure cannot simply borrow `tx`\n\nfrom the outside, the borrow might end while the thread is still using it. `move`\n\ntells the closure to *take ownership* of `tx`\n\n: it carries the sender onto the thread and does its work there, calling `tx.send(...)`\n\nonce the row is ready.\n\n```\ncargo run\n```\n\nAfter about half a second it prints:\n\n```\na fresh database row\n```\n\nThere it is: a future you wrote, run to completion by `block_on`\n\n, a runner you wrote, out of nothing but `std`\n\n. That is the same poll, park, wake loop a real runtime runs on, just smaller. The complete code for this chapter is in the [series repo on GitHub](https://github.com/jamesfebin/ImpatientProgrammerAsyncRust).\n\nIn the next chapter, we take apart `async fn`\n\nitself and write the state machine the compiler normally builds for us. That model explains why values live across `.await`\n\n, why some futures become large, and why certain compiler errors point back to what the future has to store. After that, we use the same engine pieces to understand `Pin`\n\n, tasks, executors, real I/O wakeups, timers, channels, and the Tokio versions you use in production.", "url": "https://wpnews.pro/news/who-runs-your-rust-future-hands-on-intro-to-async-rust", "canonical_source": "https://aibodh.com/posts/async-rust-chapter-1-hands-on-intro-to-async-rust/", "published_at": "2026-06-06 18:30:00+00:00", "updated_at": "2026-06-30 13:53:21.016545+00:00", "lang": "en", "topics": ["large-language-models", "ai-tools"], "entities": ["Rust", "Tokio", "JavaScript", "C#", "Python", "Node", "Discord"], "alternates": {"html": "https://wpnews.pro/news/who-runs-your-rust-future-hands-on-intro-to-async-rust", "markdown": "https://wpnews.pro/news/who-runs-your-rust-future-hands-on-intro-to-async-rust.md", "text": "https://wpnews.pro/news/who-runs-your-rust-future-hands-on-intro-to-async-rust.txt", "jsonld": "https://wpnews.pro/news/who-runs-your-rust-future-hands-on-intro-to-async-rust.jsonld"}}