<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Rust in Plain English - Medium]]></title>
        <description><![CDATA[Rust in Plain English simplifies the Rust programming language for everyone. Explore ownership, async, systems programming, WebAssembly, embedded Rust, performance, open source, and real-world projects — all explained in clear, practical language. - Medium]]></description>
        <link>https://medium.com/rust-in-plain-english?source=rss----09cddfde6342---4</link>
        <image>
            <url>https://cdn-images-1.medium.com/proxy/1*TGH72Nnw24QL3iV9IOm4VA.png</url>
            <title>Rust in Plain English - Medium</title>
            <link>https://medium.com/rust-in-plain-english?source=rss----09cddfde6342---4</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Thu, 05 Nov 2026 04:44:44 GMT</lastBuildDate>
        <atom:link href="https://medium.com/feed/rust-in-plain-english" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Async Rust Finally Made Sense]]></title>
            <link>https://medium.com/rust-in-plain-english/async-rust-finally-made-sense-5357e9a6a974?source=rss----09cddfde6342---4</link>
            <guid isPermaLink="false">https://medium.com/p/5357e9a6a974</guid>
            <category><![CDATA[rustlang]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[rust-programming-language]]></category>
            <category><![CDATA[rust]]></category>
            <category><![CDATA[programming]]></category>
            <dc:creator><![CDATA[Riz Baruah]]></dc:creator>
            <pubDate>Thu, 21 May 2026 16:16:00 GMT</pubDate>
            <atom:updated>2026-05-21T16:16:00.573Z</atom:updated>
            <content:encoded><![CDATA[<h4>Async Rust from a Beginner’s Lens</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*yuL3bDlqttOtq9hwX8hGYA.png" /><figcaption>AI-generated image by the author</figcaption></figure><p>The crab language is my first programming language.</p><p>Which means every concept I encounter, I am encountering it cold — no familiar analogies, no “oh, this is just like how X works in Y.” So when I got to asynchronous programming, I did not just have to understand <em>async</em>. I had to build the entire mental model from scratch.</p><p>This is that mental model, written as clearly as I can manage.</p><h3>The Problem Async Is Solving</h3><p>Let’s start with something concrete. Imagine you are downloading a file. The download takes five seconds. If your program is <em>synchronous</em>, it will sit there, doing absolutely nothing, for those five entire seconds. The CPU is idle. Your program is blocked. Nothing else can happen.</p><p>This is called <strong>I/O-bound work</strong> — work where the bottleneck is not computation, it is waiting. Waiting for a network response, waiting for a disk to read data, waiting for a timer to finish.</p><p>The naive fix is threads. Spawn a new thread for each task. But OS threads are expensive. Each one needs its own memory stack, its own kernel bookkeeping, its own scheduling overhead. A web server handling ten thousand simultaneous connections cannot spin up ten thousand threads. That would be catastrophic.</p><p><strong>Asynchronous programming</strong> exists to solve this. It lets you handle thousands of tasks concurrently on a handful of threads — or even one — by making tasks <em>cooperative</em>. Instead of the OS forcibly switching between threads, async tasks voluntarily pause themselves at specific points and let the runtime run something else in the meantime.</p><p>That is the core idea. Everything else is just the machinery that makes it work.</p><h3>The Fast-Food Lobby Analogy</h3><p>Here is the best analogy I found.</p><p>Think of a fast-food restaurant with a lobby and buzzers. You walk up, place your order, and the cashier hands you a buzzer. You go sit down. You check your phone, talk to friends, do whatever. You are not <em>blocked</em> at the counter. When your food is ready, the buzzer goes off and you walk back up.</p><p>This is async. The cashier is the runtime. The buzzer is the Waker. And .await is the moment you sat down.</p><p>Contrast this with a single-lane drive-thru. The car at the window has a complicated order. Every car behind it has to wait. That is synchronous programming.</p><h3>What Does “Async” Actually Mean in Rust?</h3><p>When you write async fn something(), the function no longer runs immediately when called. It returns a <strong>Future</strong> — a paused computation that represents work that <em>will eventually produce a result</em>. Nothing in the function body executes until something drives that future forward.</p><p>This is what people mean when they say <strong>futures are lazy</strong>. Calling an async function is like writing a recipe. The food does not cook itself just because you wrote the recipe down.</p><pre>let my_future = async {<br>    println!(&quot;This will NEVER print!&quot;);<br>    42<br>};<br>// Nothing has happened yet. The print statement has not run.<br>// my_future is just a paused state machine sitting in memory.</pre><p>To actually run the future, you either .await it inside another async context, or hand it to a runtime executor like trpl::block_on. Until then, it is inert.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*seVeMK1wD8AzUCzXhHK9Ag.png" /><figcaption>Infographics generated using NotebookLM</figcaption></figure><h3>The State Machine the Compiler Builds For You</h3><p>Here is where it gets genuinely interesting.</p><p>When the Rust compiler sees an async function, it does not compile it as a normal function. It transforms it into a <strong>state machine</strong> — typically an enum — that tracks exactly where the function paused and what local variables it had at that moment.</p><p>Take this simple async function:</p><pre>async fn download(id: i32) {<br>    println!(&quot;Downloading file...&quot;);<br>    tokio::time::sleep(Duration::from_secs(2)).await;<br>    println!(&quot;Finished file {}&quot;, id);<br>}</pre><p>The compiler turns this into something conceptually like:</p><pre>enum DownloadStateMachine {<br>    Start,<br>    WaitingOnSleep { id: i32 },<br>    Done,<br>}</pre><p>Every .await point is a potential pause point — a place where the state machine saves its current variables, returns Poll::Pending to whoever is polling it, and waits to be woken up again. When the runtime eventually polls it again, the state machine picks up from exactly where it left off.</p><p>This is why a future is sometimes described as a <strong>paused computation</strong>. It is not a thread. It is not a callback. It is a struct in memory, sitting at a known checkpoint, waiting for its turn.</p><h3>What .await Actually Is</h3><p>This confused me early on. .await looks like a method but it does not have parentheses. That is because it is not a method. It is a <strong>compiler-level keyword</strong> that gets transformed into something like this at compile time:</p><pre>// CONCEPTUAL: What the compiler writes when it sees .await<br>loop {<br>    match future.poll(cx) {<br>        Poll::Ready(value) =&gt; break value,  // Done! Return the value.<br>        Poll::Pending =&gt; yield,             // Not ready. Give the CPU back.<br>    }<br>}</pre><p>When you write something.await, you are saying: <em>&quot;Pause this current async block. Repeatedly poll this future until it is ready. Give control to the runtime in between each check.&quot;</em></p><p>The key mental model:</p><blockquote><strong><em>.await means: &quot;Suspend ME until THAT future makes progress.&quot;</em></strong></blockquote><p>And crucially — the await point is both where you <em>hand control to the runtime</em> and where you <em>take it back</em> when the result is ready.</p><h3>The Runtime: The Stage Manager</h3><p>Rust does not ship with an async runtime. This is intentional. A web server running on a beefy multi-core machine needs a very different runtime than a microcontroller with no heap. Rather than forcing one runtime on everyone, Rust lets you pick — or even write your own.</p><p>The most common choice is <strong>Tokio</strong>.</p><p>A runtime has two main components:</p><p><strong>The Executor</strong> is a thread pool. It maintains a queue of runnable tasks, picks one, and polls it. If the task returns Poll::Pending, the executor drops it and pulls the next one.</p><p><strong>The Reactor</strong> is the bridge to the operating system. It talks to low-level OS event systems — epoll on Linux, kqueue on macOS, IOCP on Windows. When your task is waiting for a network socket, the reactor registers interest with the OS: <em>&quot;Tell me when this socket becomes readable.&quot;</em> The OS watches the hardware. When data arrives, it notifies the reactor. The reactor calls the <strong>Waker</strong>, which tells the executor to put that task back in the queue.</p><p>Meanwhile, no thread is blocked. No CPU core is wasting time spinning. The entire mechanism is driven by OS notifications.</p><p>The #[tokio::main] macro that you see in Tokio programs is just syntactic sugar. Under the hood, it rewrites your async main into:</p><pre>fn main() {<br>    let mut runtime = tokio::runtime::Runtime::new().unwrap();<br>    runtime.block_on(async {<br>        // Your code runs here<br>    });<br>}</pre><p>Somewhere, something has to wait. block_on blocks exactly one thread — the main thread — and inside that one blocked thread, thousands of async tasks can run cooperatively without blocking each other.</p><h3>Why main() Cannot Be Async</h3><p>This catches a lot of people. You cannot write async fn main() in Rust without an attribute macro like #[tokio::main]. The reason is that main() is where the runtime <em>starts</em>. If main itself were async, something else would need to drive it — which means <em>another</em> runtime underneath, all the way down. The synchronous main is the anchor point. It starts the runtime. The runtime drives everything else.</p><h3>Concurrency vs. Parallelism (and Where Async Fits)</h3><p>These two words get conflated constantly. Here is the real difference:</p><ul><li><strong>Parallelism</strong> means multiple CPU cores doing work <em>at the same time</em>.</li><li><strong>Concurrency</strong> means multiple tasks making progress <em>during overlapping time periods</em>. It does not specify <em>how</em>.</li></ul><p>Threads are one way to achieve concurrency. Async is another. The difference is in <em>who manages the scheduling</em>.</p><p>With threads, the <strong>OS</strong> decides which thread runs when, using preemptive scheduling — it can forcibly pause a thread mid-instruction and hand the CPU to another one.</p><p>With async, the <strong>runtime</strong> manages scheduling using <em>cooperative</em> scheduling. A task runs until it voluntarily yields at an .await point. The runtime cannot forcibly pause it mid-execution. This is efficient, but it also means that if a task never yields — say, it is doing heavy CPU computation with no .await points — it will freeze the entire thread and starve every other task on it.</p><p>The mental model:</p><pre>many async tasks<br>       ↓<br>runtime scheduler<br>       ↓<br>few OS threads</pre><p>Async does not <em>replace</em> threads. It sits <em>on top</em> of them. The OS schedules threads. Tokio schedules async tasks inside those threads. This is called <strong>layered scheduling</strong>.</p><h3>Futures and the Future Trait</h3><p>At the graduate level, a future is defined by this trait:</p><pre>pub trait Future {<br>    type Output;<br><br>    fn poll(self: Pin&lt;&amp;mut Self&gt;, cx: &amp;mut Context&lt;&#39;_&gt;) -&gt; Poll&lt;Self::Output&gt;;<br>}</pre><p>Output is the associated type — the value the future eventually produces. poll is the method the runtime calls repeatedly to ask: <em>&quot;Are you done yet?&quot;</em></p><p>The future responds with either:</p><pre>Poll::Ready(value)  // &quot;Yes, here is your result.&quot;<br>Poll::Pending       // &quot;Not yet. I have registered my Waker. Come back later.&quot;</pre><p>The cx: &amp;mut Context parameter carries the Waker — the mechanism that lets the future tell the runtime <em>when</em> to come back. Without the Waker, the runtime would have to blindly poll every future in a busy loop, which would be massively wasteful.</p><p>When a future calls cx.waker().wake(), it is saying: <em>&quot;I have registered with the OS reactor. When my data arrives, the reactor will call this, which tells you to put me back in the runnable queue.&quot;</em></p><h3>Pinning: Why Futures Cannot Move in Memory</h3><p>This one requires some careful thinking.</p><p>Async state machines are structs. Sometimes those structs contain references to <em>their own fields</em> — a future that holds a reference to a string it is working on, where both the string and the reference live inside the same struct. These are called <strong>self-referential types</strong>.</p><p>A reference is a memory address. When you move a struct to a new location in memory, the struct’s bytes get copied bit-for-bit to the new address. But the old addresses stored inside those bytes are not updated. If a field pointed to another field inside the same struct, after the move, it now points to where that other field <em>used to be</em>. Dangling pointer. Memory unsafety.</p><p>To prevent this, Rust uses Pin&lt;&amp;mut T&gt;. A pinned value is <em>guaranteed to never move in memory</em> while it is being polled. When you write pin!(my_future) using the macro, Rust staples the future to its current stack slot. The future cannot be moved. The internal pointers stay valid.</p><p>This is why poll takes self: Pin&lt;&amp;mut Self&gt; rather than &amp;mut self. If you could poll a future through a regular mutable reference, you could move it, which would invalidate those internal pointers.</p><h3>Streams: Async Iterators</h3><p>An iterator gives you items immediately, one by one. A stream gives you items <em>over time</em> — each next() call returns a future, which might resolve instantly or might have to wait for a database query, a network packet, or some other async operation.</p><pre>while let Some(value) = stream.next().await {<br>    println!(&quot;The value was {value}&quot;);<br>}</pre><p>The trait behind a stream looks like a combination of Iterator and Future:</p><pre>fn poll_next(self: Pin&lt;&amp;mut Self&gt;, cx: &amp;mut Context&lt;&#39;_&gt;) -&gt; Poll&lt;Option&lt;Self::Item&gt;&gt;;</pre><p>It returns:</p><ul><li>Poll::Pending — not ready yet, come back</li><li>Poll::Ready(Some(item)) — here is the next item</li><li>Poll::Ready(None) — the stream is finished</li></ul><p>Note that poll_next also takes Pin&lt;&amp;mut Self&gt; for the same reason poll does — the stream&#39;s internal state machine must not move.</p><h3>The Most Important Mental Model</h3><p>Here is the table I keep coming back to:</p><p>Thing Mental Model async {} Creates a paused computation Future A state machine .await Repeatedly polls the state machine poll() Asks: &quot;Can you make progress?&quot; Pending &quot;Not ready yet, come back later&quot; Ready &quot;I am done&quot;</p><p>And the flow:</p><pre>async block<br>    ↓<br>compiled into state machine<br>    ↓<br>.await repeatedly polls it<br>    ↓<br>future advances step by step<br>    ↓<br>sleep().await pauses it<br>    ↓<br>executor switches elsewhere<br>    ↓<br>future gets polled again later</pre><h3>Async Within One Block Is Still Sequential</h3><p>This is a mistake that is very easy to make.</p><p>Simply writing async and await does not automatically make things run <em>at the same time</em>. If your sending loop and your receiving loop are inside the <em>same</em> async block, they still execute one after the other.</p><pre>trpl::block_on(async {<br>    // Sends all messages first...<br>    for val in vals {<br>        tx.send(val).unwrap();<br>        trpl::sleep(Duration::from_millis(500)).await;<br>    }<br><br>    // ...then receives them all.<br>    while let Some(value) = rx.recv().await {<br>        println!(&quot;received &#39;{value}&#39;&quot;);<br>    }<br>});</pre><p>This entire block is one future. One state machine. The .await points let the executor pause <em>this block</em> momentarily, but there is nothing else to run — it just waits out the timer and comes back. The result is sequential, not concurrent.</p><p>To run things concurrently, you need to either spawn separate tasks with spawn_task, or join separate futures with trpl::join or join!. Each future becomes independently schedulable, and the executor can interleave them.</p><pre>let tx_fut = async move { /* send things */ };<br>let rx_fut = async { /* receive things */ };<br>trpl::join(tx_fut, rx_fut).await;</pre><p>Now the executor can run tx_fut until it hits an .await, switch to rx_fut, switch back, and so on. That is real concurrency.</p><h3>Starving Other Futures</h3><p>Because async is cooperative, a task that never yields will freeze every other task on its thread. This is called <strong>future starvation</strong>.</p><p>If you write a CPU-heavy loop inside an async block with no .await points, it will hog the thread indefinitely:</p><pre>let a = async {<br>    slow(&quot;a&quot;, 30); // This blocks. No await point.<br>    slow(&quot;a&quot;, 10); // Still blocking.<br>    slow(&quot;a&quot;, 20); // Still blocking.<br>    trpl::sleep(Duration::from_millis(50)).await; // Only now does it yield.<br>};</pre><p>During those three slow() calls, no other future on this thread can make any progress.</p><p>The naive fix is to insert a tiny trpl::sleep(1ms).await between each slow operation. This works, but it introduces artificial delay — you are not actually sleeping, you just want to share the CPU.</p><p>The correct fix is yield_now():</p><pre>slow(&quot;a&quot;, 30);<br>trpl::yield_now().await; // &quot;I am pausing voluntarily. Run something else.&quot;<br>slow(&quot;a&quot;, 10);<br>trpl::yield_now().await;</pre><p>yield_now() immediately returns control to the executor without any artificial sleep. As soon as the other tasks have had their turn, execution comes back. It is the difference between being a good citizen and pretending to be asleep.</p><h3>Threads vs. Async: When to Use Which</h3><p>Feature Threads Async Managed by The OS The runtime Cost Higher (stack memory, kernel overhead) Lower (a few bytes per task) Best for CPU-bound work I/O-bound work Scheduling Preemptive (OS decides) Cooperative (task decides)</p><p>These are not mutually exclusive. Real applications often mix them. You might spawn a standard OS thread to handle a blocking CPU-heavy operation while async handles all the network I/O around it.</p><pre>thread::spawn(move || {<br>    // CPU-heavy or blocking work goes here<br>    for i in 1..11 {<br>        tx.send(i).unwrap();<br>        thread::sleep(Duration::from_secs(1)); // Blocking is fine in a thread<br>    }<br>});<br><br>trpl::block_on(async {<br>    while let Some(message) = rx.recv().await {<br>        println!(&quot;Received: {message}&quot;);<br>    }<br>});</pre><p>The thread does its blocking work. The async side receives results without blocking its runtime. Both models doing what they are best at.</p><h3>The Precise Summary</h3><p>Here is the one paragraph I would have wanted on day one:</p><blockquote><em>Async programming avoids needing one OS thread per task by transforming async functions into compiler-generated state machines called Futures. These futures store their paused execution state in memory. The runtime repeatedly polls these futures to make progress. When a future cannot continue, it returns </em><em>Pending and provides a </em><em>Waker, which allows the runtime to be notified later — often through OS event systems like </em><em>epoll or </em><em>IOCP — when the task is ready to resume. Many tasks share a small number of OS threads. The OS schedules threads. The runtime schedules tasks inside those threads.</em></blockquote><p>That is it. That is the whole thing.</p><p><em>This blog post is based on my notes from reading The Rust Programming Language book. Rust is my first programming language, which means every mental model here was built without prior knowledge of how other languages handle async. If that context is useful to someone else also starting out — that is the point.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=5357e9a6a974" width="1" height="1" alt=""><hr><p><a href="https://medium.com/rust-in-plain-english/async-rust-finally-made-sense-5357e9a6a974">Async Rust Finally Made Sense</a> was originally published in <a href="https://medium.com/rust-in-plain-english">Rust in Plain English</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The Thing That Made Async Rust Finally Click For Me]]></title>
            <link>https://medium.com/rust-in-plain-english/the-thing-that-made-async-rust-finally-click-for-me-7ef98bd5daef?source=rss----09cddfde6342---4</link>
            <guid isPermaLink="false">https://medium.com/p/7ef98bd5daef</guid>
            <category><![CDATA[rust-programming-language]]></category>
            <category><![CDATA[rust]]></category>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[rustlang]]></category>
            <dc:creator><![CDATA[Riz Baruah]]></dc:creator>
            <pubDate>Thu, 21 May 2026 11:40:01 GMT</pubDate>
            <atom:updated>2026-05-21T11:40:01.200Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*fNvD_JdEO-yR9ffWT_p_Qw.png" /><figcaption>AI generated image by the Author</figcaption></figure><p>Before I even wrote my first line of the crab language, I came across a video from ThePrimeagen where he casually implied that async Rust is notoriously difficult to wrap your head around. I filed that away as a warning. So when I finally got to it — as someone for whom Rust is their first programming language — I braced for the worst.</p><p>It didn’t go the way I expected. Instead of hitting a wall, I found myself genuinely fascinated. The concepts weren’t just challenging — they were elegant. Understanding how async Rust works under the hood is actually what made async programming as a whole click for me. This post is my attempt to walk you through that same understanding, from the inside out.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*DZZ269iFMMA3PbG8zwVAmQ.png" /><figcaption>Infographics generated via. NotebookLM</figcaption></figure><h3>The Future Trait: The Foundation</h3><p>Everything in async Rust sits on top of one core abstraction: the Future trait. Here is how Rust defines it:</p><pre>use std::pin::Pin;<br>use std::task::{Context, Poll};<br>pub trait Future {<br>    type Output; // associated type<br>    fn poll(self: Pin&lt;&amp;mut Self&gt;, cx: &amp;mut Context&lt;&#39;_&gt;) -&gt; Poll&lt;Self::Output&gt;;<br>}</pre><p>Let’s unpack this piece by piece, because every single part here is doing something important.</p><h3>Associated Types vs. Generics</h3><p>The first thing that might catch your eye is type Output. This is an <em>associated type</em>, and it answers a question you might have asked yourself: why not just write Future&lt;T&gt; like a generic?</p><p>Here is the problem with generics in this case. If the trait were defined as Future&lt;T&gt;, a single struct could potentially implement <em>both</em> Future&lt;String&gt; and Future&lt;i32&gt; at the same time:</p><pre>struct MyWork;<br><br>impl Future&lt;String&gt; for MyWork {<br>    fn poll(&amp;mut self) -&gt; String { &quot;Done&quot;.into() }<br>}<br><br>impl Future&lt;i32&gt; for MyWork {<br>    fn poll(&amp;mut self) -&gt; i32 { 100 }<br>}</pre><p>Now imagine writing let result = MyWork.await. The compiler has absolutely no idea whether result should be a String or an i32. It&#39;s ambiguous, and Rust does not tolerate ambiguity.</p><p>With an associated type (type Output), each implementing type can only define Output once. One type, one future, one output. The compiler always knows what it&#39;s dealing with.</p><h3>Poll&lt;T&gt;: The Answer the Runtime Gets</h3><p>When the runtime asks a future “are you done yet?”, the future answers with a Poll:</p><pre>pub enum Poll&lt;T&gt; {<br>    Ready(T),<br>    Pending,<br>}</pre><p>It’s beautifully simple. Either the future has finished and here is the result (Ready(T)), or it&#39;s still working (Pending). Think of it like Option&lt;T&gt; — one variant carries a value, one doesn&#39;t.</p><p>One important thing worth knowing: once a future returns Ready, you should not call poll on it again. Most futures will panic if you do. This behavior mirrors Iterator::next — once the iterator signals it&#39;s done, continuing to call next is on you.</p><h3>How .await Works</h3><p>The .await keyword feels magical, but it is really just a loop with a poll call at its heart. A rough mental model looks like this:</p><pre>let page_title_fut = page_title(url);<br>loop {<br>    match page_title_fut.poll {<br>        Ready(value) =&gt; {<br>            // we have the value, break out<br>        }<br>        Pending =&gt; {<br>            continue,<br>        }<br>    }<br>}</pre><p>Of course, a real runtime is far smarter than a busy loop. The cx: &amp;mut Context&lt;&#39;_&gt; parameter is the key to that. The Context carries a <em>waker</em> — a mechanism that lets the future tell the runtime &quot;hey, I&#39;m still waiting, but wake me up when I&#39;m ready.&quot; The runtime can then park that future and go do other work, instead of spinning in place wasting CPU cycles.</p><h3>The Pin Type: The Part Everyone Struggles With</h3><p>This is where most people hit a wall, myself included. Let’s go slow.</p><h3>Why Does Pinning Exist?</h3><p>When Rust compiles an async block, it doesn&#39;t generate some magic thread. It generates a <strong>state machine</strong> — a struct that remembers where in the async function we currently are, and all the local variables needed to resume from that point.</p><p>Here is the thing about that struct: it can be <em>self-referential</em>. One field of the struct might hold a <em>reference to another field in the same struct</em>. For example, if your async function creates a buffer and then borrows from it, both the buffer and the reference to it live inside the same generated struct.</p><p>Now imagine moving that struct to a new location in memory (say, from the stack to the heap). In Rust, moving something is a bitwise copy. The bytes get copied faithfully to the new address — including the pointer that was pointing to a field within the <em>old</em> location. That field has now moved too, but the pointer still holds the <em>old address</em>. You just created a dangling pointer.</p><p>This is the core problem Pin is designed to solve.</p><h3>The Memory Geography of Box&lt;dyn Future&gt; vs. pin!</h3><p>To make this concrete, consider a case where we want to run several futures concurrently and store them in a Vec. A first attempt might look like:</p><pre>let futures: Vec&lt;Box&lt;dyn Future&lt;Output = ()&gt;&gt;&gt; =<br>    vec![Box::new(tx1_fut), Box::new(rx_fut), Box::new(tx_fut)];</pre><p>Here is what’s happening in memory with this approach:</p><ul><li>futures is a stack variable.</li><li>It points to heap-allocated memory for the Vec itself.</li><li>Each <em>item</em> in the Vec is a Box — another pointer — to the actual future on the heap.</li><li>The future (state machine struct) was moved from the stack to the heap during the Box::new(...) call.</li></ul><p>That move is the problem. When the state machine is copied bit-for-bit from the stack to the heap, any self-referential pointers inside it now point to the old stack memory. That memory is gone. Dangling pointer.</p><p>The error message Rust gives you is illuminating:</p><pre>error[E0277]: `dyn Future&lt;Output = ()&gt;` cannot be unpinned</pre><p>The solution is pin!:</p><pre>let tx1_fut = pin!(async move { ... });<br>let rx_fut  = pin!(async { ... });<br>let tx_fut  = pin!(async move { ... });<br><br>let futures: Vec&lt;Pin&lt;&amp;mut dyn Future&lt;Output = ()&gt;&gt;&gt; =<br>    vec![tx1_fut, rx_fut, tx_fut];</pre><p>Here is what changes:</p><p>The pin! macro &quot;staples&quot; the state machine to the current stack frame. It uses a shadowing trick — it replaces your variable with a Pin&lt;&amp;mut Future&gt; that points to the same stack slot. The actual future data <em>does not move</em>. The Vec then stores references to these pinned stack locations, not owned copies on the heap.</p><p>Let’s make this comparison explicit:</p><p><strong>Box&lt;dyn Future&gt;</strong> <strong>pin!</strong> Future Location Heap (inside the Box) Stack (local variables) Indirection Vec → Box → Future Vec → Reference → Future Safety Check Runtime Compile-time Heap Allocations 3+ (Vec + each Box) 1 (just the Vec)</p><p>The pin! approach is faster, and more importantly, it is <em>safe at compile time</em>. The Rust compiler can statically verify that you will never move the future after pinning it.</p><h3>pin! vs. Box::pin</h3><p>Once you understand pinning, the difference between the two becomes obvious:</p><ul><li><strong>pin! (macro)</strong> — Stack pinning. The future stays on the stack, anchored to the current function&#39;s frame. Use this when your future is a local variable and you don&#39;t need it to outlive the current scope. Cheaper, since it avoids a heap allocation for the future itself.</li><li><strong>Box::pin (function)</strong> — Heap pinning. The future is moved to the heap, but pinned there. Use this when the future needs to outlive the current function, or when you&#39;re storing it in a data structure that requires ownership (not just a reference).</li></ul><p>And as for why the poll method takes Pin&lt;&amp;mut Self&gt; rather than Pin&lt;&amp;Self&gt; — the state machine changes state. It transitions from Pending to Ready. That requires mutability. &amp;mut is non-negotiable here.</p><h3>The Stream Trait: Futures That Keep Giving</h3><p>Once you have a solid understanding of Future, Stream is a natural extension. The best way to think about it is in three levels.</p><p><strong>The intuition</strong>: A Future is like ordering a single package online. You wait, and eventually it arrives. An Iterator is like a local shelf — you reach in and grab items instantly, one after another. A Stream is like a subscription box. Items arrive over time, and you have to wait for each one.</p><ul><li><strong>Iterator</strong>: Give me the next item. (Instant)</li><li><strong>Future</strong>: Give me one thing, but I’ll have to wait for it. (Wait once)</li><li><strong>Stream</strong>: Give me a sequence of things, and I’ll have to wait for <em>each one</em>. (Wait many times)</li></ul><p><strong>The mechanics</strong>: The Stream trait is essentially a hybrid of Iterator and Future. When you poll a stream, it returns one of three things:</p><ol><li>Poll::Pending — &quot;I&#39;m still waiting for the next item.&quot;</li><li>Poll::Ready(Some(item)) — &quot;Here is the next item.&quot;</li><li>Poll::Ready(None) — &quot;The stream is exhausted.&quot;</li></ol><p>The signature of poll_next reflects this:</p><pre>fn poll_next(self: Pin&lt;&amp;mut Self&gt;, cx: &amp;mut Context&lt;&#39;_&gt;) -&gt; Poll&lt;Option&lt;Self::Item&gt;&gt;;</pre><p>Notice that poll_next also takes self: Pin&lt;&amp;mut Self&gt;. The same reasoning applies — the stream is a state machine, it mutates its state between polls, and it must not be moved mid-operation.</p><p><strong>In practice</strong>, you rarely call poll_next directly. You use StreamExt, which gives you the familiar .next().await ergonomics:</p><pre>while let Some(msg) = message_stream.next().await {<br>    println!(&quot;Received: {msg}&quot;);<br>}</pre><p>That .next().await is hiding a poll_next call inside a loop, with the waker mechanism handling the &quot;come back when there&#39;s data&quot; part for you.</p><h3>Putting It All Together</h3><p>Here is the mental model I now carry around:</p><p>Every async block compiles into a struct — a state machine. That struct implements Future. Its poll method is called by the runtime. If the work is done, it returns Ready. If not, it registers a waker via the Context and returns Pending. The runtime parks the future and comes back when the waker fires.</p><p>Because those state machine structs can be self-referential, moving them is dangerous. Pin exists to make the compiler aware of this constraint and enforce it. The pin! macro keeps futures on the stack; Box::pin moves them to the heap but pins them there.</p><p>Stream extends this entire model to a sequence of values that each arrive asynchronously — Iterator + Future composed into one trait.</p><p>What struck me most about all of this is how deeply <em>compositional</em> the design is. There is no special scheduler baked into the language. There is no hidden thread pool in the compiler. There is just a chain of well-designed traits, each solving one specific problem, that together produce a complete async system. The runtime (whether Tokio or async-std) plugs into this system through the Context and the waker mechanism — and the rest follows.</p><p>It took me a while to appreciate that, but I think it’s genuinely beautiful.</p><p><em>This blog post is based on my notes from reading The Rust Programming Language book. Any feedback and corrections are welcomed</em></p><p><em>Thanks for reading.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=7ef98bd5daef" width="1" height="1" alt=""><hr><p><a href="https://medium.com/rust-in-plain-english/the-thing-that-made-async-rust-finally-click-for-me-7ef98bd5daef">The Thing That Made Async Rust Finally Click For Me</a> was originally published in <a href="https://medium.com/rust-in-plain-english">Rust in Plain English</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[I Learned Functional Programming in Rust Before I Knew What “Functional” Even Meant]]></title>
            <link>https://medium.com/rust-in-plain-english/i-learned-functional-programming-in-rust-before-i-knew-what-functional-even-meant-870111011149?source=rss----09cddfde6342---4</link>
            <guid isPermaLink="false">https://medium.com/p/870111011149</guid>
            <category><![CDATA[rust-programming-language]]></category>
            <category><![CDATA[rustlang]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[rust]]></category>
            <category><![CDATA[functional-programming]]></category>
            <dc:creator><![CDATA[Riz Baruah]]></dc:creator>
            <pubDate>Wed, 13 May 2026 14:07:35 GMT</pubDate>
            <atom:updated>2026-05-13T14:07:36.972Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*wDC0Hu9B5_8NqSLFY_1h1w.png" /><figcaption>AI generated image</figcaption></figure><p><em>And honestly, it took longer than I’d like to admit.</em></p><p>Rust was my first programming language. Not Python. Not JavaScript. Rust — the language that apparently makes experienced developers cry. So when the Rust book threw “functional programming” at me, I didn’t have the luxury of thinking, “oh, this is just like how we do it in Python.” I had to figure it out from scratch.</p><p>What follows is everything I learned, structured the way I wish someone had explained it to me: starting simple, then building up. If you’re new to programming, or new to Rust, I hope this saves you a few hours of staring blankly at your screen.</p><h3>What is Functional Programming, Actually?</h3><p>Before diving into any code, here’s the honest one-line answer: <strong>functional programming is a style of writing code where you describe <em>what</em> you want, not <em>how</em> to do it, step by step.</strong></p><p>That’s it. Everything else — immutability, closures, iterators — is just what that philosophy looks like in practice.</p><h3>Level 1: The “No-Touchy” Rule</h3><p>The most foundational idea in functional programming is <strong>immutability</strong>. The idea is simple: instead of reaching into a variable and changing it, you leave it alone and create something new from it.</p><p>Think of it like this — if your friend lends you their notes, you photocopy them and write on the copy. You don’t scribble on the original. Immutability is that instinct, but for code.</p><p>Here’s what that looks like in Rust:</p><pre>fn main() {<br>    let scores = vec![10, 20, 30];</pre><pre>    // Instead of a for loop that changes scores,<br>    // we create a new collection: doubled_scores.<br>    let doubled_scores: Vec&lt;i32&gt; = scores.iter()<br>        .map(|x| x * 2)  // for each x, give me x * 2<br>        .collect();       // put them into a new vector</pre><pre>    println!(&quot;{:?}&quot;, doubled_scores); // [20, 40, 60]<br>}</pre><p>scores.iter() goes over each value one at a time. .map() takes each value and applies the rule x * 2. .collect() gathers everything into a brand new vector. The original scores is never touched.</p><p><strong>Why does this matter?</strong> If you never change a variable, you can never accidentally break it somewhere else in your program. It’s a safety guarantee baked right into the style.</p><h3>“But Wait — Won’t Copying Everything Kill My Memory?”</h3><p>This was the first question I asked. And it’s the right one.</p><p>If you’re copying a massive list 100 times across 100 iterations, you’d crash a server. But here’s the trick: <strong>Rust’s iterators are lazy.</strong></p><p>When you write .map(|x| x * 2), Rust does not immediately create a new list. It sets up a conveyor belt. The multiplication only happens when something downstream actually <em>asks</em> for a value — like .collect(). The data moves through the pipeline exactly once.</p><p>If you use .into_iter() instead of .iter(), the original vector is consumed and its values move into the new one. No copying at all:</p><pre>let big_vec = vec![1, 2, 3];<br>let new_vec: Vec&lt;i32&gt; = big_vec.into_iter() // big_vec ends here<br>    .map(|x| x + 1)<br>    .collect(); // new_vec begins here</pre><p>So: functional programming is not slow. It’s not memory-hungry. Rust calls this <strong>zero-cost abstraction</strong> — the compiled code is just as fast as a hand-written loop, sometimes faster.</p><h3>Level 2: Pipelines and Higher-Order Functions</h3><p>Now that we understand the “no-touchy” rule, let’s talk about what makes functional programming genuinely elegant: <strong>chaining operations together like a pipeline</strong>.</p><p>Instead of writing out every step manually, you describe the transformations you want. This is called <strong>declarative programming</strong>.</p><pre>fn main() {<br>    let nums = vec![1, 2, 3, 4, 5, 6];</pre><pre>    let result: i32 = nums.into_iter()<br>        .filter(|x| x % 2 == 0) // keep only even numbers<br>        .map(|x| x * x)         // square them<br>        .sum();                  // add them all up</pre><pre>    println!(&quot;The result is {}&quot;, result); // 56<br>}</pre><p>Read that code like a sentence: <em>“Take the numbers, keep the even ones, square them, and sum everything up.”</em> That’s what declarative means — you say what you want, and Rust figures out the how.</p><p>The key feature enabling this is <strong>higher-order functions</strong>: functions that take other functions as arguments. .filter() and .map() both accept closures — small anonymous functions you pass in. Which brings us to the heart of the whole thing.</p><h3>Closures: Anonymous Functions That Carry a Backpack</h3><p>A <strong>closure</strong> is a function with no name. Like Python’s lambdas, but more powerful, because closures in Rust can <em>capture</em> variables from the scope around them.</p><p>Think of a closure as a bundle of instructions that also carries a “backpack” of data from wherever it was created.</p><p>Here’s a simple example:</p><pre>fn main() {<br>    let greeting = &quot;Welcome&quot;;</pre><pre>    let greet = |name| {<br>        println!(&quot;{} {}&quot;, greeting, name)<br>    };</pre><pre>    greet(&quot;Riz&quot;); // output: Welcome Riz<br>}</pre><p>greet is a function disguised as a variable. The parameter name is passed in when you call it. But greeting? That comes from the surrounding scope. The closure reached out and grabbed it. That&#39;s the &quot;backpack.&quot;</p><h3>How Closures Capture: Three Ways</h3><p>When a closure captures a variable from its environment, it does so in one of three ways:</p><p><strong>1. Borrowing (read-only)</strong></p><pre>let list = vec![1, 2, 3];<br>let only_borrows = || println!(&quot;From closure: {:?}&quot;, list);<br>only_borrows();<br>println!(&quot;Still usable: {:?}&quot;, list); // works fine</pre><p><strong>2. Mutably Borrowing (read and write)</strong></p><pre>let mut list = vec![1, 2, 3];<br>let mut add_to_list = || list.push(4);<br>add_to_list();<br>println!(&quot;{:?}&quot;, list); // [1, 2, 3, 4]</pre><p><strong>3. Taking Ownership (the </strong><strong>move keyword)</strong></p><pre>let list = vec![1, 2, 3];<br>let takes_ownership = move || println!(&quot;I own the list now: {:?}&quot;, list);<br>// list is no longer accessible here</pre><p>The move keyword is essential when passing closures to threads — you don&#39;t want your data to disappear while another thread is running with it.</p><h3>The Secret Underneath: Closures Are Structs</h3><p>Here’s the graduate-level truth: when you write a closure, Rust doesn’t create a “naked” function. It quietly generates a hidden struct that holds whatever the closure captured, and implements one of three traits on it.</p><p>These traits are the job descriptions a closure must fulfil:</p><p>Trait Contract Callable FnOnce Takes ownership of captured variables Only once FnMut Mutably borrows captured variables Multiple times, can change them Fn Immutably borrows captured variables Multiple times, read-only</p><p>They nest inside each other: Fn implies FnMut, which implies FnOnce. FnOnce is the most general — it only promises the closure can be called at least once.</p><h3>How Closures and Traits Fit Together: A Job Analogy</h3><p>The best way I found to understand all of this at once is the job analogy.</p><ul><li><strong>Traits</strong> (Fn, FnMut, FnOnce) are <strong>job descriptions</strong> — they define what requirements a candidate must meet.</li><li><strong>Generic type parameters</strong> (like &lt;F&gt;) are <strong>job openings</strong> — slots waiting to be filled.</li><li><strong>Closures</strong> are <strong>applicants</strong> — they come with their own internal struct and data.</li></ul><p>Look at the definition of unwrap_or_else on the Option enum:</p><pre>impl&lt;T&gt; Option&lt;T&gt; {<br>    pub fn unwrap_or_else&lt;F&gt;(self, f: F) -&gt; T<br>    where<br>        F: FnOnce() -&gt; T<br>    {<br>        match self {<br>            Some(x) =&gt; x,<br>            None =&gt; f(),<br>        }<br>    }<br>}</pre><p>Step by step:</p><ul><li>&lt;F&gt; is a generic type: &quot;this function works with any type, as long as we call it F.&quot;</li><li>f: F is the actual closure you pass in — the hidden struct the compiler built.</li><li>where F: FnOnce() -&gt; T is the requirement: &quot;I don&#39;t care what your struct is called internally, as long as it implements FnOnce.&quot;</li><li>f() is syntax sugar. The compiler sees it and calls f.call_once() under the hood.</li></ul><h3>Why where Instead of Inline Bounds?</h3><p>You can write trait bounds inline — &lt;F: FnOnce() -&gt; T&gt; — and it works. But when you have multiple type parameters with multiple bounds, inline gets painful to read:</p><pre>fn do_something&lt;T: Display + Clone, U: Clone + Debug, V: Into&lt;String&gt;&gt;(t: T, u: U, v: V) -&gt; i32 {<br>    // ...<br>}</pre><p>The where clause cleans this up considerably:</p><pre>fn do_something&lt;T, U, V&gt;(t: T, u: U, v: V)<br>where<br>    T: Display + Clone,<br>    U: Clone + Debug,<br>    V: Into&lt;String&gt;<br>{<br>    // ...<br>}</pre><p>Same meaning. Much easier on the eyes.</p><h3>Iterators: The Real Engine</h3><p>Now let’s talk about the thing that makes pipelines actually work: <strong>iterators</strong>.</p><p>Every iterator in Rust implements the Iterator trait, which boils down to one core method:</p><pre>pub trait Iterator {<br>    type Item;<br>    fn next(&amp;mut self) -&gt; Option&lt;Self::Item&gt;;<br>}</pre><p>type Item is an <strong>associated type</strong> — a placeholder that each specific iterator fills in. A vector of i32 values gives an iterator where Item = i32. The next() method hands you values one at a time, wrapped in Some, until there&#39;s nothing left and it returns None.</p><p>You can call next() directly:</p><pre>let v1 = vec![1, 2, 3];<br>let mut v1_iter = v1.iter();</pre><pre>assert_eq!(v1_iter.next(), Some(&amp;1));<br>assert_eq!(v1_iter.next(), Some(&amp;2));<br>assert_eq!(v1_iter.next(), Some(&amp;3));<br>assert_eq!(v1_iter.next(), None);</pre><p>Note that v1_iter must be mut. The iterator keeps an internal bookmark of where it is. Calling next() moves that bookmark forward — the iterator&#39;s own data changes, so the variable holding it must be mutable.</p><h3>iter() vs into_iter() vs &amp;v</h3><p>Syntax Ownership of original Type of each item for val in v1.iter() Preserved (borrowed) &amp;T for val in v1 Consumed (moved) T for val in &amp;v1 Preserved (borrowed) &amp;T</p><p>Use .iter() when you want to keep the original. Use .into_iter() when you&#39;re done with it.</p><h3>Consuming Adapters vs. Iterator Adapters</h3><p>Iterator methods split into two camps:</p><p><strong>Consuming adapters</strong> call next() and use up the iterator. sum() is the classic example — it pulls every value out, adds them up, and the iterator is gone afterward.</p><pre>let v1 = vec![1, 2, 3];<br>let total: i32 = v1.iter().sum();<br>assert_eq!(total, 6);</pre><p><strong>Iterator adapters</strong> don’t consume. They produce a new iterator with some transformation applied. map() and filter() are iterator adapters. They are lazy — nothing happens until a consuming adapter comes along and demands values.</p><pre>let v1: Vec&lt;i32&gt; = vec![1, 2, 3];<br>v1.iter().map(|x| x + 1); // warning: does nothing</pre><p>Rust will actually warn you here. The iterator is set up, the closure is defined, but without a .collect() or .sum() at the end, no values ever flow through. This is lazy evaluation in its most visible form.</p><p>The correct version:</p><pre>let v2: Vec&lt;i32&gt; = v1.iter().map(|x| x + 1).collect();</pre><p>Here’s the sequence of what actually happens:</p><ol><li>collect() asks map() for the first item.</li><li>map() asks v1.iter() for the first item.</li><li>v1.iter() hands back Some(&amp;1).</li><li>map() runs the closure: 1 + 1 = 2. Hands 2 to collect().</li><li>collect() stores it. Asks again.</li><li>This repeats until v1.iter() returns None.</li><li>collect() stops and hands back the finished vector.</li></ol><p>Data travels through the chain exactly once. No intermediate collections are created.</p><h3>Closures Capturing the Environment: A Real Example</h3><p>Iterator adapters often take closures as arguments, and those closures can reach into the surrounding environment. This is where the two ideas — closures and iterators — combine into something genuinely useful.</p><pre>fn shoes_in_size(shoes: Vec&lt;Shoe&gt;, shoe_size: u32) -&gt; Vec&lt;Shoe&gt; {<br>    shoes.into_iter().filter(|s| s.size == shoe_size).collect()<br>}</pre><p>The closure |s| s.size == shoe_size captures shoe_size from the function&#39;s parameters. The filter() adapter uses that closure to decide which shoes survive. into_iter() consumes the original vector. collect() assembles the result.</p><p>Clean. Readable. No mutation anywhere.</p><h3>Handling “Nothingness” the Functional Way</h3><p>One more pattern worth understanding: functional programming handles the absence of a value as gracefully as it handles the presence of one.</p><p>In Rust, this is the Option enum. And the real power comes when you chain operations on it:</p><pre>fn get_user_id(username: &amp;str) -&gt; Option&lt;u32&gt; {<br>    if username == &quot;admin&quot; { Some(1) } else { None }<br>}</pre><pre>fn main() {<br>    let access_level = get_user_id(&quot;admin&quot;)<br>        .map(|id| id * 10)<br>        .and_then(|id| Some(id + 5));</pre><pre>    println!(&quot;Access level: {:?}&quot;, access_level); // Some(15)<br>}</pre><p>If &quot;admin&quot; is passed:</p><ul><li>get_user_id returns Some(1)</li><li>.map() unwraps it, applies * 10, rewraps: Some(10)</li><li>.and_then() unwraps it, applies + 5, and <em>we</em> wrap it: Some(15)</li></ul><p>If any other username is passed:</p><ul><li>get_user_id returns None</li><li>.map() sees None, skips</li><li>.and_then() sees None, skips</li><li>Result: None</li></ul><p>The key difference between .map() and .and_then():</p><ul><li>.map() says: &quot;I&#39;ll wrap the result for you.&quot;</li><li>.and_then() says: &quot;You wrap it yourself.&quot;</li></ul><p>That’s why we wrote Some(id + 5) inside and_then — if we&#39;d written just id + 5, the types wouldn&#39;t match and the compiler would rightly complain.</p><h3>The Bigger Picture</h3><p>By the end of all this, the pattern becomes clear.</p><p>Functional programming in Rust is not a separate universe from the rest of the language — it’s baked in. Iterators, closures, Option, map, filter, collect — these aren&#39;t exotic add-ons. They&#39;re the idiomatic way Rust expects you to process data.</p><p>The reason it feels unfamiliar at first isn’t because it’s complicated. It’s because most people learning programming are taught the imperative style — loops, mutations, step-by-step instructions — before they ever see the declarative alternative. Once the pipeline model clicks, reading Rust code starts to feel less like deciphering instructions and more like reading a sentence.</p><p>That shift in how you read code? That’s when it gets fun.</p><p><em>This post was written while working through The Rust Programming Language book. If you’re starting from zero like I was, I’d recommend reading it alongside writing actual code — nothing makes the concepts land faster than watching the compiler yell at you.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=870111011149" width="1" height="1" alt=""><hr><p><a href="https://medium.com/rust-in-plain-english/i-learned-functional-programming-in-rust-before-i-knew-what-functional-even-meant-870111011149">I Learned Functional Programming in Rust Before I Knew What “Functional” Even Meant</a> was originally published in <a href="https://medium.com/rust-in-plain-english">Rust in Plain English</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Smart Pointers in Rust, A Beginner’s Perspective]]></title>
            <link>https://medium.com/rust-in-plain-english/i-learned-rust-smart-pointers-as-a-first-time-programmer-heres-what-finally-made-it-click-d522855081d9?source=rss----09cddfde6342---4</link>
            <guid isPermaLink="false">https://medium.com/p/d522855081d9</guid>
            <category><![CDATA[rust]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[rust-programming-language]]></category>
            <category><![CDATA[rustlang]]></category>
            <dc:creator><![CDATA[Riz Baruah]]></dc:creator>
            <pubDate>Wed, 13 May 2026 14:07:03 GMT</pubDate>
            <atom:updated>2026-05-13T14:07:04.609Z</atom:updated>
            <content:encoded><![CDATA[<p>A beginner’s honest attempt at understanding one of Rust’s most powerful — and initially baffling — concepts.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*TxXpP7ktyiO12F4Gu64rrw.png" /></figure><p>Most resources about Rust smart pointers assume you already know what a pointer <em>is</em>. They assume you’ve spent time in C or C++, that you’ve felt the pain of a dangling pointer or a segfault, and that you’re now here looking for a safer alternative.</p><p>I had none of that context. Rust is my first programming language.</p><p>So when Chapter 15 of <em>The Rust Programming Language</em> book opened with “A pointer is a variable which stores the memory address of a chunk of memory,” I had to stop, stare at that sentence for a while, and actually think about what memory even is.</p><p>What follows is what I pieced together. Not just what smart pointers <em>are</em>, but why they exist the way they do — and the mental models that finally made everything feel less like magic and more like deliberate, sensible design.</p><h3>First: What Even Is a Pointer?</h3><p>Think of your computer’s RAM as a massive row of numbered mailboxes. Each box can hold a small piece of data. A <strong>pointer</strong> is a variable that doesn’t hold actual data — it holds the <em>address</em> (the box number) of where the real data lives.</p><p>A <strong>reference</strong> in Rust (&amp;) is essentially a pointer, but with rules attached: ownership rules, borrowing rules, lifetime rules. Rust enforces that these rules are never broken, and it does so at compile time — before the program even runs.</p><p><strong>Smart pointers</strong> are data structures that act like pointers but carry additional capabilities on top. They’re implemented as structs that implement two special traits: Deref and Drop.</p><ul><li>Deref lets you write code that works the same way whether you&#39;re working with a regular reference or a smart pointer.</li><li>Drop lets you define cleanup logic — what happens when the smart pointer goes out of scope.</li></ul><p>Here’s something that surprised me: we’ve already used smart pointers without realizing it. String and Vec&lt;T&gt; are smart pointers.</p><h3>Box&lt;T&gt;: The Most Boring Smart Pointer (In the Best Way)</h3><p>Box&lt;T&gt; is the most stripped-down smart pointer in Rust. All it does is allocate a value on the <strong>heap</strong> instead of the <strong>stack</strong>. No reference counting, no shared ownership, nothing fancy. Just a pointer to some heap memory that it owns.</p><p>You use it in three situations:</p><h3>1. When you don’t know a type’s size at compile time</h3><p>Rust needs to know exactly how much space every type takes at compile time so it can allocate the right amount of stack space. This becomes a problem with <strong>recursive types</strong> — types that contain themselves.</p><pre>// This won&#39;t compile<br>enum List {<br>    Cons(i32, List), // Rust asks: how big is List? Well, it contains a List... infinite!<br>    Nil,<br>}</pre><p>The fix is elegant. Since a pointer always has a fixed size (8 bytes on a 64-bit system), you just put the recursive part behind a Box:</p><pre>enum List {<br>    Cons(i32, Box&lt;List&gt;), // Fixed size now: one i32 + one pointer<br>    Nil,<br>}</pre><h3>2. When moving large data would be expensive</h3><p>When you move a value in Rust, if it lives on the stack, Rust copies all of it to a new location. For large data, that’s expensive. If instead the data lives on the heap behind a Box, moving ownership only copies the pointer — 8 bytes regardless of how big the data is.</p><h3>3. When you want to store different types together</h3><p>Sometimes you want a Vec that can hold different types — say, a Button struct and a Text struct — that share a common trait. Since different structs have different sizes, a plain Vec can&#39;t hold them. But a Vec&lt;Box&lt;dyn Draw&gt;&gt; can, because every element is just a pointer of the same size, regardless of what it points to.</p><h3>The Deref Trait: Teaching Rust What * Means for Your Type</h3><p>When you write *y in Rust, you&#39;re asking: &quot;give me whatever y is pointing at.&quot; For a standard reference (&amp;T), the compiler knows exactly how to do this. But for a custom struct? The compiler has no idea.</p><p>This is why the Deref trait exists. It lets you say: &quot;when someone uses * on my type, here is how to get to the underlying data.&quot;</p><p>When you implement Deref for MyBox&lt;T&gt;:</p><pre>use std::ops::Deref;</pre><pre>impl&lt;T&gt; Deref for MyBox&lt;T&gt; {<br>    type Target = T;</pre><pre>    fn deref(&amp;self) -&gt; &amp;Self::Target {<br>        &amp;self.0 // return a reference to the inner value<br>    }<br>}</pre><p>Under the hood, *y becomes *(y.deref()). Rust calls your deref() method to get a reference, and then applies the standard dereference to that. The compiler handles all of this invisibly.</p><h3>Deref Coercion: Rust’s Quality-of-Life Gift</h3><p>Once you implement Deref, Rust gives you something extra: <strong>deref coercion</strong>. This is an automatic conversion that happens when you pass a reference of one type to a function expecting a reference of another type — as long as a chain of Deref implementations connects them.</p><p>For example, if you have a &amp;MyBox&lt;String&gt; and a function that expects a &amp;str, Rust will:</p><ol><li>Call deref() on &amp;MyBox&lt;String&gt; → gets &amp;String</li><li>Call deref() on &amp;String → gets &amp;str</li><li>Match found. Done.</li></ol><p>Without this, you’d write:</p><pre>hello(&amp;(*m)[..]);</pre><p>With it, you just write:</p><pre>hello(&amp;m);</pre><p>The chain happens entirely at compile time, so there’s zero runtime cost.</p><h3>The Drop Trait: Automating Cleanup</h3><p>Drop lets you define what happens when a value goes out of scope. For smart pointers, this is where memory gets freed. For file handles, this is where the file gets closed. For network connections, this is where you tell the server you&#39;re done.</p><p>In languages like C, forgetting to close a file can corrupt it. Forgetting to free memory causes your program to gradually consume more and more RAM. The Drop trait makes all of this automatic in Rust.</p><pre>impl Drop for MyFile {<br>    fn drop(&amp;mut self) {<br>        println!(&quot;Closing file and saving data: {}&quot;, self.filename);<br>    }<br>}</pre><p>The moment the value goes out of scope, this runs. You don’t call it. You can’t forget it.</p><p>One thing to know: you cannot call .drop() directly on a value. Rust won&#39;t let you, because it would cause a <strong>double free</strong> — Rust would try to clean up the same memory twice. If you want to drop something early, use the drop(value) function from std::mem::drop, which transfers ownership and drops it safely.</p><p>Values are dropped in <em>reverse order of creation</em>, by the way. The last thing created is the first thing cleaned up.</p><h3>Rc&lt;T&gt;: When One Owner Isn&#39;t Enough</h3><p>Rust’s default rule is single ownership: exactly one variable owns a piece of data at any time. That’s usually fine. But sometimes it genuinely isn’t.</p><p>Think of a graph data structure. A node might be pointed to by multiple edges. If you delete one edge, the node shouldn’t disappear — the other edges still need it. The node should only be cleaned up when <em>nobody</em> is pointing to it anymore.</p><p>This is what Rc&lt;T&gt; — Reference Counted — handles. It keeps a count of how many owners a piece of heap-allocated data has. Every time you create a new owner (Rc::clone(&amp;value)), the count goes up. Every time an owner goes out of scope, the count goes down. When the count hits zero, the data is dropped.</p><pre>let a = Rc::new(Node { ... });       // count: 1<br>let b = Rc::clone(&amp;a);              // count: 2<br>let c = Rc::clone(&amp;a);              // count: 3<br>// c goes out of scope             // count: 2<br>// b goes out of scope             // count: 1<br>// a goes out of scope             // count: 0 → data is freed</pre><p>Two things to remember about Rc&lt;T&gt;:</p><p>First, Rc::clone() is <em>not</em> like a regular .clone(). It doesn&#39;t copy any data. It just increments the counter and gives you another pointer to the same data. This is cheap.</p><p>Second, Rc&lt;T&gt; only allows <strong>immutable</strong> access to the data. If multiple owners could all mutate the same value, you&#39;d have chaos. This immutability is intentional and enforced.</p><p>Rc&lt;T&gt; is also single-threaded only. For multiple threads, you&#39;d use Arc&lt;T&gt; (Atomic Reference Counting), which handles the counter increments safely across threads.</p><h3>RefCell&lt;T&gt;: Mutating the Immutable (Carefully)</h3><p>Here’s where things get genuinely interesting — and where Rust’s philosophy shows its teeth.</p><p>RefCell&lt;T&gt; enables something called <strong>interior mutability</strong>: the ability to mutate data even when you only have an immutable reference to it. This sounds like it should be illegal. In most of Rust, it is. But RefCell&lt;T&gt; does it safely by moving the borrow-checking from compile time to runtime.</p><p>The borrowing rules still apply — you can still only have one mutable borrow at a time, or any number of immutable borrows, but not both. If you violate these rules with RefCell&lt;T&gt;, your program doesn&#39;t fail to compile. It panics at runtime instead.</p><p>Why would you ever want this? Consider the most common reason: traits.</p><p>Sometimes a library defines a trait method with &amp;self — immutable access. You implement that trait, and you&#39;re locked into that signature. But what if your implementation needs to mutate some internal state to do its job? You can&#39;t change the method signature. The Drop trait is a perfect example. So is any logging or caching mechanism.</p><p>RefCell&lt;T&gt; solves this by letting you hide the mutability:</p><pre>struct MockMessenger {<br>    sent_messages: RefCell&lt;Vec&lt;String&gt;&gt;,<br>}</pre><pre>impl Messenger for MockMessenger {<br>    fn send(&amp;self, message: &amp;str) {<br>        self.sent_messages.borrow_mut().push(String::from(message));<br>    }<br>}</pre><p>From the outside, MockMessenger looks immutable. Internally, borrow_mut() gives you temporary mutable access to the Vec, and the borrow-checking happens at runtime.</p><p>If you try to take two mutable borrows at once, the program panics. This is the tradeoff you accept: runtime errors instead of compile-time ones, in exchange for flexibility the compiler can’t reason about on its own.</p><p>RefCell&lt;T&gt; is also single-threaded. The thread-safe equivalent is Mutex&lt;T&gt;.</p><h3>Combining Rc&lt;T&gt; and RefCell&lt;T&gt;</h3><p>Each of these types solves one problem:</p><ul><li>Rc&lt;T&gt;: multiple owners, but only immutable access</li><li>RefCell&lt;T&gt;: single owner, but mutable access even from an immutable reference</li></ul><p>What if you need <em>both</em>? Multiple owners <em>and</em> mutability?</p><p>You combine them: Rc&lt;RefCell&lt;T&gt;&gt;.</p><pre>let value = Rc::new(RefCell::new(5));</pre><pre>let a = Rc::clone(&amp;value);<br>let b = Rc::clone(&amp;value);</pre><pre>*value.borrow_mut() += 10;</pre><pre>// Both a and b now see the updated value: 1</pre><p>This pattern comes up frequently when building data structures where multiple parts of your program need to share and modify the same piece of data.</p><h3>Reference Cycles: The One Way Rust Can Leak Memory</h3><p>Rust’s ownership system makes memory leaks nearly impossible. Nearly.</p><p>If you use Rc&lt;T&gt; and RefCell&lt;T&gt; together carelessly, you can create a <strong>reference cycle</strong> — where object A points to object B, and object B points back to object A. Both have a reference count of 2. When they go out of scope, the stack variables drop, bringing each count to 1. But the count never reaches 0, because each object is holding the other alive.</p><p>The data sits in the heap forever — no longer reachable from your code, never freed. That’s a memory leak.</p><p>The solution is Weak&lt;T&gt;. A weak reference says: &quot;I know this data exists, but I am not claiming ownership. If everything else decides to drop it, don&#39;t wait for me.&quot;</p><p>Weak references don’t increment the strong count. They don’t keep data alive. And before you use a weak reference, you have to call .upgrade() on it, which returns an Option — because the data might have already been dropped.</p><p>The correct pattern for parent-child relationships is:</p><ul><li><strong>Parent owns children</strong> → Rc&lt;T&gt; (strong reference)</li><li><strong>Child knows about parent</strong> → Weak&lt;T&gt; (weak reference, no ownership)</li></ul><p>This way, when the parent is dropped, the strong count hits zero, and everything cleans up. The child’s weak reference to the parent simply returns None when upgraded.</p><h3>Putting It All Together</h3><p>Here’s a quick map of when to reach for each type:</p><p>You need…Use…Heap allocation, one ownerBox&lt;T&gt;Multiple owners, immutable accessRc&lt;T&gt;Single owner, interior mutabilityRefCell&lt;T&gt;Multiple owners, mutable accessRc&lt;RefCell&lt;T&gt;&gt;Non-owning reference (to prevent cycles)Weak&lt;T&gt;</p><p>Smart pointers are Rust’s way of giving you precise control over memory without making it your full-time job. Box&lt;T&gt; handles the common case. Rc&lt;T&gt; handles shared ownership. RefCell&lt;T&gt; handles the cases where the compiler can&#39;t statically reason about your borrowing patterns but you know — and can prove at runtime — that they&#39;re safe.</p><p>The compiler is always conservative. It would rather reject correct code than accept dangerous code. Smart pointers are the tools Rust gives you to say: “I understand the rules. I’ll enforce them myself.”</p><p>Coming from no prior programming experience, this chapter was a lot. But the more I sat with each type and really asked <em>why does this exist</em>, <em>what problem did someone have before writing this</em>, the clearer it got. The designs are not arbitrary. Every constraint, every limitation — the immutability of Rc, the runtime panics of RefCell, the non-ownership of Weak — is a deliberate, considered tradeoff.</p><p>That’s the thing about Rust. Once you stop fighting the rules and start asking why they exist, it starts to make a lot of sense.</p><p><em>Written as part of learning Rust from The Rust Programming Language book. First-time programmer trying to make sense of the heap, one smart pointer at a time.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d522855081d9" width="1" height="1" alt=""><hr><p><a href="https://medium.com/rust-in-plain-english/i-learned-rust-smart-pointers-as-a-first-time-programmer-heres-what-finally-made-it-click-d522855081d9">Smart Pointers in Rust, A Beginner’s Perspective</a> was originally published in <a href="https://medium.com/rust-in-plain-english">Rust in Plain English</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>