<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Parikalp Bhardwaj</title>
    <description>The latest articles on DEV Community by Parikalp Bhardwaj (@parikalp_bhardwaj_9e9d812).</description>
    <link>https://dev.to/parikalp_bhardwaj_9e9d812</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3959021%2F99f6d7e9-a05d-4967-b0b3-aca81a0fab4a.jpg</url>
      <title>DEV Community: Parikalp Bhardwaj</title>
      <link>https://dev.to/parikalp_bhardwaj_9e9d812</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/parikalp_bhardwaj_9e9d812"/>
    <language>en</language>
    <item>
      <title>What Does Arc Actually Do? Atomic Reference Counting Under the Hood</title>
      <dc:creator>Parikalp Bhardwaj</dc:creator>
      <pubDate>Wed, 26 Aug 2026 19:26:05 +0000</pubDate>
      <link>https://dev.to/parikalp_bhardwaj_9e9d812/what-does-arc-actually-do-atomic-reference-counting-under-the-hood-47eo</link>
      <guid>https://dev.to/parikalp_bhardwaj_9e9d812/what-does-arc-actually-do-atomic-reference-counting-under-the-hood-47eo</guid>
      <description>&lt;p&gt;&lt;em&gt;Following &lt;code&gt;Arc::clone&lt;/code&gt; from a line of Rust down to a single machine instruction. Article 2 of **Below the Abstraction&lt;/em&gt;* — a series that takes everyday Rust abstractions and follows them down to the kernel. Every number and every trace below was produced by running the programs shown, on the machine described in the appendix.*&lt;/p&gt;




&lt;h2&gt;
  
  
  Why &lt;code&gt;Arc&lt;/code&gt; Exists
&lt;/h2&gt;

&lt;p&gt;In the last article we created OS threads and watched Linux build them with &lt;code&gt;clone3&lt;/code&gt;. One detail from that investigation matters here more than anything else. Four threads in one process had byte-identical memory maps:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;diff &amp;lt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /proc/&lt;span class="nv"&gt;$PID&lt;/span&gt;/task/&lt;span class="nv"&gt;$PID&lt;/span&gt;/maps&lt;span class="o"&gt;)&lt;/span&gt; &amp;lt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /proc/&lt;span class="nv"&gt;$PID&lt;/span&gt;/task/&lt;span class="nv"&gt;$TID&lt;/span&gt;/maps&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"identical"&lt;/span&gt;
identical
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every thread sees the same address space. A pointer in one thread is a valid pointer in all of them.&lt;/p&gt;

&lt;p&gt;Which raises the question this article is about: &lt;strong&gt;if several threads can reach the same heap allocation, who is allowed to free it?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Start with a value that has exactly one owner:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;String&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"hello from Rust"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Move it into a thread:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;thread&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;String&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"hello"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nn"&gt;thread&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;spawn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;move&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{message}"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="nf"&gt;.join&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The worker now owns &lt;code&gt;message&lt;/code&gt;. The main thread cannot use it any more. That is not the threading system being awkward — that is Rust's ownership model doing exactly its job. One owner, one &lt;code&gt;drop&lt;/code&gt;, no ambiguity.&lt;/p&gt;

&lt;p&gt;But real systems need something ownership alone does not give you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A configuration object read by twenty worker threads.&lt;/li&gt;
&lt;li&gt;Routing state shared across a server's request handlers.&lt;/li&gt;
&lt;li&gt;A cache used by many handlers at once.&lt;/li&gt;
&lt;li&gt;A database connection pool referenced from every task.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                     Shared Config
                          │
           ┌──────────────┼──────────────┐
           │              │              │
           ▼              ▼              ▼
       Worker 1       Worker 2       Worker 3
           │              │              │
           ▼              ▼              ▼
       Requests        Requests       Requests
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We do not want to copy the whole configuration for each worker. We want several parts of the program to &lt;em&gt;share&lt;/em&gt; ownership of one allocation, and for that allocation to be freed exactly once, when the last of them is finished.&lt;/p&gt;

&lt;p&gt;That is the problem &lt;code&gt;Arc&amp;lt;T&amp;gt;&lt;/code&gt; solves.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Is &lt;code&gt;Arc&amp;lt;T&amp;gt;&lt;/code&gt;?
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;Arc&lt;/code&gt; stands for:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Atomically Reference Counted
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is a pointer that provides &lt;strong&gt;shared ownership&lt;/strong&gt; of a heap-allocated value. The idea in one picture:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;multiple Arc handles
        ↓
same heap allocation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;Arc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Arc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;String&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"hello"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Arc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;clone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Arc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;clone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{value}"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{a}"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{b}"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This does &lt;strong&gt;not&lt;/strong&gt; create three &lt;code&gt;String&lt;/code&gt;s. It creates one, and three handles to it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Stack

value ───────┐
a ───────────┼───────┐
b ───────────┘       │
                     ▼
                 Heap allocation
              ┌──────────────────┐
              │ strong count = 3 │
              │ weak count       │
              │                  │
              │ String           │
              │ "hello"          │
              └──────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That diagram is the mental model. Later in this article we will read those two counts out of memory directly and check that the picture is accurate — it turns out to be right in shape and slightly wrong in one detail.&lt;/p&gt;

&lt;h3&gt;
  
  
  A note on vocabulary
&lt;/h3&gt;

&lt;p&gt;Since this series goes low-level, let me define terms as they come up rather than assume them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Heap allocation&lt;/strong&gt; — a block of memory obtained at runtime (ultimately via &lt;code&gt;malloc&lt;/code&gt;, which as we saw in article 1 sometimes becomes an &lt;code&gt;mmap&lt;/code&gt; syscall). It lives until something explicitly frees it, unlike stack memory which disappears when the function returns. This is why sharing across threads needs the heap: a spawned thread can outlive the function that created it, so it cannot borrow that function's stack.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Handle&lt;/strong&gt; — a small value you hold that refers to something bigger elsewhere. An &lt;code&gt;Arc&amp;lt;T&amp;gt;&lt;/code&gt; is a handle; the &lt;code&gt;T&lt;/code&gt; is elsewhere.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Not Just Use &lt;code&gt;&amp;amp;T&lt;/code&gt;?
&lt;/h2&gt;

&lt;p&gt;Before reaching for &lt;code&gt;Arc&lt;/code&gt;, it is fair to ask whether a plain reference would do.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;String&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"hello"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;reference&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This compiles because the compiler can prove &lt;code&gt;reference&lt;/code&gt; cannot outlive &lt;code&gt;value&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;thread::spawn&lt;/code&gt; breaks that proof. A spawned thread is an independent OS task — article 1 showed Linux scheduling it separately, with its own TID and its own 2 MiB stack. It can still be running long after the function that spawned it has returned and its stack frame is gone. So Rust will not let you hand it a borrow of that stack.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Arc&amp;lt;T&amp;gt;&lt;/code&gt; sidesteps the problem by giving each participant something it genuinely &lt;em&gt;owns&lt;/em&gt;, while the actual value sits on the heap where nobody's stack frame can take it away.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Not &lt;code&gt;Rc&amp;lt;T&amp;gt;&lt;/code&gt;?
&lt;/h2&gt;

&lt;p&gt;Rust has a second reference-counting pointer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Rc&amp;lt;T&amp;gt;   =  Reference Counted
Arc&amp;lt;T&amp;gt;  =  Atomically Reference Counted
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;They do the same job. The difference is one word: &lt;strong&gt;atomically&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Rc&lt;/code&gt; updates its count with an ordinary CPU increment. That is fine inside one thread and unsafe across several. &lt;code&gt;Arc&lt;/code&gt; updates its count with an &lt;em&gt;atomic&lt;/em&gt; instruction, which is safe across threads and costs more.&lt;/p&gt;

&lt;p&gt;The compiler enforces the distinction. Here is the same program written with &lt;code&gt;Rc&lt;/code&gt; and handed to &lt;code&gt;thread::spawn&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;error[E0277]: `Rc&amp;lt;Config&amp;gt;` cannot be sent between threads safely
  --&amp;gt; threads_rc.rs:18:36
   |
18 |           handles.push(thread::spawn(move || {
   |                        ------------- ^------
   |                        |             |
   |  ______________________|_____________within this `{closure@threads_rc.rs:18:36: 18:43}`
   | |                      |
   | |                      required by a bound introduced by this call
...
   = help: within `{closure@threads_rc.rs:18:36: 18:43}`, the trait `Send` is not implemented for `Rc&amp;lt;Config&amp;gt;`
note: required because it's used within this closure
note: required by a bound in `spawn`

error: aborting due to 1 previous error
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(Trimmed in the middle where it echoes the closure body; the rest is verbatim.)&lt;/p&gt;

&lt;h3&gt;
  
  
  What that error is really saying
&lt;/h3&gt;

&lt;p&gt;The error mentions &lt;code&gt;Send&lt;/code&gt;. It is worth stopping here, because &lt;code&gt;Send&lt;/code&gt; and its partner &lt;code&gt;Sync&lt;/code&gt; are how Rust decides what is allowed near a thread — and most explanations of them are one sentence long and help nobody.&lt;/p&gt;

&lt;p&gt;Here is the plain version.&lt;/p&gt;

&lt;p&gt;Rust needs to answer two questions about every type:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Question one: is it safe to hand this value to another thread?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That is &lt;code&gt;Send&lt;/code&gt;. If a type is &lt;code&gt;Send&lt;/code&gt;, you can move it from thread A to thread B and thread B may use it. Most types are fine here. A &lt;code&gt;String&lt;/code&gt; is &lt;code&gt;Send&lt;/code&gt; — hand it over, the receiving thread now owns it, the sending thread has given it up, nobody is confused.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Question two: is it safe for two threads to look at this value at the same time?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That is &lt;code&gt;Sync&lt;/code&gt;. If a type is &lt;code&gt;Sync&lt;/code&gt;, two threads may hold &lt;code&gt;&amp;amp;T&lt;/code&gt; references to the same value simultaneously and nothing bad happens. A &lt;code&gt;u32&lt;/code&gt; sitting behind a shared reference is &lt;code&gt;Sync&lt;/code&gt; — both threads can read it all day, because reading cannot break anything.&lt;/p&gt;

&lt;p&gt;You never write &lt;code&gt;impl Send for MyType&lt;/code&gt;. The compiler works it out for you: if everything inside your type is &lt;code&gt;Send&lt;/code&gt;, your type is &lt;code&gt;Send&lt;/code&gt;. It only becomes something you think about when it goes wrong, which is exactly what happened above.&lt;/p&gt;

&lt;p&gt;So why is &lt;code&gt;Rc&lt;/code&gt; not &lt;code&gt;Send&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;Because of the counter. &lt;code&gt;Rc&lt;/code&gt; bumps its count with a plain, non-atomic increment. If &lt;code&gt;Rc&lt;/code&gt; were &lt;code&gt;Send&lt;/code&gt;, you could move one to another thread, and then two threads would be incrementing and decrementing the same ordinary integer at the same time. That is the lost-update problem from the previous section, and it ends with the allocation being freed while somebody still holds a pointer to it.&lt;/p&gt;

&lt;p&gt;So the library says: &lt;strong&gt;not &lt;code&gt;Send&lt;/code&gt;.&lt;/strong&gt; Not "discouraged", not "be careful" — the type simply cannot cross a thread boundary, and the compiler stops you.&lt;/p&gt;

&lt;p&gt;Compare the two:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;
&lt;code&gt;Send&lt;/code&gt;?&lt;/th&gt;
&lt;th&gt;
&lt;code&gt;Sync&lt;/code&gt;?&lt;/th&gt;
&lt;th&gt;meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Rc&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;one thread only, both for moving and for sharing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Arc&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;yes*&lt;/td&gt;
&lt;td&gt;yes*&lt;/td&gt;
&lt;td&gt;can be moved between threads and shared by them&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;* when &lt;code&gt;T&lt;/code&gt; itself is &lt;code&gt;Send + Sync&lt;/code&gt; — &lt;code&gt;Arc&lt;/code&gt; can only be as thread-safe as the thing inside it. This is the same point as the "&lt;code&gt;Arc&lt;/code&gt; does not make &lt;code&gt;T&lt;/code&gt; thread-safe" section coming up.&lt;/p&gt;

&lt;p&gt;And this is the part I find genuinely impressive. The distinction between "safe to share across threads" and "not safe" is not a convention in a style guide, or a comment, or a lint you can ignore. It is in the type system. The unsafe version does not compile. You cannot ship this bug.&lt;/p&gt;

&lt;p&gt;Keep that error in mind, because it is going to close a loop. By the end of this article you will have seen the exact machine instruction that is the whole difference between the type that compiles and the type that does not. It is one word long.&lt;/p&gt;




&lt;h2&gt;
  
  
  What "Reference Counting" Actually Means
&lt;/h2&gt;

&lt;p&gt;Reference counting is a very old idea and a simple one. The allocation carries a number: how many owners currently exist. Owners appearing bump it up; owners leaving bump it down; whoever takes it to zero cleans up.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Arc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;        &lt;span class="c1"&gt;// strong = 1&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;data2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Arc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;clone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// strong = 2&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;data3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Arc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;clone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// strong = 3&lt;/span&gt;

&lt;span class="nf"&gt;drop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;                     &lt;span class="c1"&gt;// strong = 2&lt;/span&gt;
&lt;span class="nf"&gt;drop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;                     &lt;span class="c1"&gt;// strong = 1&lt;/span&gt;
&lt;span class="nf"&gt;drop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;                      &lt;span class="c1"&gt;// strong = 0  -&amp;gt;  destroy the value&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Arc::new(T)
    ↓
strong = 1

Arc::clone()
    ↓
strong += 1

drop(Arc)
    ↓
strong -= 1

strong reaches zero
    ↓
drop T
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing surprising so far. The surprising part is that &lt;code&gt;+= 1&lt;/code&gt; and &lt;code&gt;-= 1&lt;/code&gt; cannot be ordinary arithmetic once more than one thread is involved.&lt;/p&gt;




&lt;h2&gt;
  
  
  What "Atomic" Actually Means
&lt;/h2&gt;

&lt;p&gt;Say the count is &lt;code&gt;2&lt;/code&gt;, and two threads clone at the same moment. An ordinary increment is not one operation — the CPU has to read the value, add one, and write it back. Three steps. Two threads can interleave them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Thread A reads 2
Thread B reads 2
Thread A writes 3
Thread B writes 3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two new owners were created. The count says &lt;code&gt;3&lt;/code&gt;. It should say &lt;code&gt;4&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is called a &lt;strong&gt;lost update&lt;/strong&gt;, and for a reference count it is fatal. The count is now permanently one too low, so the allocation will be freed while a real owner still holds a pointer to it. That owner then reads freed memory — a use-after-free, the exact class of bug Rust exists to prevent.&lt;/p&gt;

&lt;p&gt;An &lt;strong&gt;atomic read-modify-write&lt;/strong&gt; (RMW) fixes it. The hardware performs read, add, and write as one indivisible step: no other core can observe or interleave with the middle of it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CPU 0                 shared count                 CPU 1

fetch_add(1)   ───────►     2      ◄─────── fetch_add(1)

                            ↓

                            4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Arc&lt;/code&gt; uses atomic RMW for both directions. &lt;code&gt;Rc&lt;/code&gt; does not. That is the whole difference — and it is a real one, not a formality.&lt;/p&gt;

&lt;p&gt;How that atomicity is achieved differs by CPU architecture, and we will look at the actual instruction later in this article rather than speculate about it.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;code&gt;Arc&lt;/code&gt; Does Not Make &lt;code&gt;T&lt;/code&gt; Thread-Safe
&lt;/h2&gt;

&lt;p&gt;This is the most common misconception about &lt;code&gt;Arc&lt;/code&gt;, and it is worth being blunt about.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Arc&amp;lt;T&amp;gt;&lt;/code&gt; gives you &lt;strong&gt;shared ownership&lt;/strong&gt;. It does not give you &lt;strong&gt;synchronized mutation&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Arc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You cannot write &lt;code&gt;*counter += 1&lt;/code&gt; from several threads. &lt;code&gt;Arc&lt;/code&gt; hands out &lt;code&gt;&amp;amp;T&lt;/code&gt; — a shared reference — and shared references do not permit arbitrary mutation. That is not &lt;code&gt;Arc&lt;/code&gt; being restrictive; it is the only sound thing it can do, because &lt;code&gt;Arc&lt;/code&gt; has no idea how to make &lt;em&gt;your&lt;/em&gt; type safe to mutate concurrently.&lt;/p&gt;

&lt;p&gt;For shared mutable state you combine &lt;code&gt;Arc&lt;/code&gt; with something that does know:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nb"&gt;Arc&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Mutex&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;span class="nb"&gt;Arc&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;RwLock&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;span class="nb"&gt;Arc&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AtomicU64&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The separation of responsibility is clean, and worth memorising:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Arc
│
└── Who owns this allocation, and when is it freed?

Mutex / RwLock / Atomic
│
└── How is concurrent access to the value synchronized?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do not merge those two jobs in your head. Nearly every confused question about &lt;code&gt;Arc&lt;/code&gt; comes from merging them.&lt;/p&gt;

&lt;h3&gt;
  
  
  The compiler checks this too
&lt;/h3&gt;

&lt;p&gt;Remember the asterisk on the &lt;code&gt;Send&lt;/code&gt;/&lt;code&gt;Sync&lt;/code&gt; table earlier — &lt;code&gt;Arc&amp;lt;T&amp;gt;&lt;/code&gt; is only thread-safe &lt;em&gt;if &lt;code&gt;T&lt;/code&gt; is&lt;/em&gt;. Here is what that looks like when you get it wrong.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;RefCell&amp;lt;T&amp;gt;&lt;/code&gt; gives you mutation through a shared reference, which sounds like exactly what we want. It enforces the borrow rules at runtime with a counter instead of at compile time. But that counter is an ordinary integer, not an atomic one — the same problem as &lt;code&gt;Rc&lt;/code&gt;. So &lt;code&gt;RefCell&lt;/code&gt; is not &lt;code&gt;Sync&lt;/code&gt;: two threads must not look at one at the same time.&lt;/p&gt;

&lt;p&gt;Wrap it in an &lt;code&gt;Arc&lt;/code&gt; and try to send it to a thread anyway:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;shared&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Arc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;RefCell&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Arc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;clone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nn"&gt;thread&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;spawn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;move&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="nf"&gt;.borrow_mut&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;error[E0277]: `RefCell&amp;lt;i32&amp;gt;` cannot be shared between threads safely
   |
   = help: the trait `Sync` is not implemented for `RefCell&amp;lt;i32&amp;gt;`
   = note: if you want to do aliasing and mutation between multiple threads,
           use `std::sync::RwLock` instead
   = note: required for `Arc&amp;lt;RefCell&amp;lt;i32&amp;gt;&amp;gt;` to implement `Send`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read that last line slowly, because it is the whole idea in one sentence. &lt;code&gt;Arc&amp;lt;RefCell&amp;lt;i32&amp;gt;&amp;gt;&lt;/code&gt; is not &lt;code&gt;Send&lt;/code&gt; — you cannot move it to a thread — &lt;em&gt;because&lt;/em&gt; &lt;code&gt;RefCell&amp;lt;i32&amp;gt;&lt;/code&gt; is not &lt;code&gt;Sync&lt;/code&gt;. The &lt;code&gt;Arc&lt;/code&gt; did not fix anything. It faithfully passed the question through to the type inside it, and the answer came back no.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Arc&lt;/code&gt; is a wrapper around ownership, not a thread-safety spray. And the compiler even tells you the fix: use &lt;code&gt;RwLock&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why &lt;code&gt;Arc&amp;lt;Mutex&amp;lt;T&amp;gt;&amp;gt;&lt;/code&gt; is everywhere
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nb"&gt;Arc&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Mutex&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;HashMap&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two mechanisms, stacked:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Thread 1 ── Arc ──┐
Thread 2 ── Arc ──┼──► Mutex ──► HashMap
Thread 3 ── Arc ──┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Arc&lt;/code&gt; lets several independently spawned threads own the same &lt;code&gt;Mutex&lt;/code&gt;. The &lt;code&gt;Mutex&lt;/code&gt; controls exclusive access to the &lt;code&gt;HashMap&lt;/code&gt;. Remove the &lt;code&gt;Arc&lt;/code&gt; and you cannot share it; remove the &lt;code&gt;Mutex&lt;/code&gt; and you cannot mutate it safely.&lt;/p&gt;

&lt;p&gt;It is a common pattern and a reasonable default. It is not automatically the right architecture — article 3 benchmarks when it is and when &lt;code&gt;RwLock&lt;/code&gt; or sharding beats it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Question
&lt;/h2&gt;

&lt;p&gt;For this article:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What actually changes in memory when we create, clone, move, and drop an &lt;code&gt;Arc&amp;lt;T&amp;gt;&lt;/code&gt;? And what changes when several OS threads clone and drop the same &lt;code&gt;Arc&lt;/code&gt; at once?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Concretely, the things to find out:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Arc allocation layout
strong reference count
weak reference count
pointer identity
clone behaviour
drop behaviour
atomic increments and decrements
memory ordering
cache-line effects
cross-thread ownership
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Hypothesis
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;Arc::clone&lt;/code&gt; does not clone the inner &lt;code&gt;T&lt;/code&gt;. It creates another owning pointer to the same allocation and atomically increments a shared strong count. The last owner to drop destroys the value and frees the memory. Being atomic, those updates cost more than ordinary ones — and cost more still when several CPU cores touch the same count.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now let's test each clause.&lt;/p&gt;




&lt;h2&gt;
  
  
  Experiment 1 — Prove Shared Allocation and Reference Counting
&lt;/h2&gt;

&lt;p&gt;Start with the smallest program that can settle it. No dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cargo new rust-arc-internals
&lt;span class="nb"&gt;cd &lt;/span&gt;rust-arc-internals
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;src/main.rs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;Arc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;#[derive(Debug)]&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Data&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="nb"&gt;Drop&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;Data&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;drop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Dropping Data {{ value: {} }}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;original&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Arc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Data&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"after Arc::new"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;print_arc_state&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"original"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;original&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;clone_a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Arc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;clone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;original&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;after clone_a"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;print_arc_state&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"original"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;original&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;print_arc_state&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"clone_a"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;clone_a&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;clone_b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Arc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;clone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;original&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;after clone_b"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;print_arc_state&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"original"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;original&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;print_arc_state&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"clone_a"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;clone_a&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;print_arc_state&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"clone_b"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;clone_b&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nf"&gt;drop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clone_a&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;after dropping clone_a"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;print_arc_state&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"original"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;original&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;print_arc_state&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"clone_b"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;clone_b&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nf"&gt;drop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clone_b&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;after dropping clone_b"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;print_arc_state&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"original"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;original&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;leaving main"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;print_arc_state&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;arc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;Arc&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Data&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"{name}: ptr={:p}, strong={}, weak={}, value={}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nn"&gt;Arc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;as_ptr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arc&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nn"&gt;Arc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;strong_count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arc&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nn"&gt;Arc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;weak_count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arc&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;arc&lt;/span&gt;&lt;span class="py"&gt;.value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three details in that program are deliberate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;Arc::clone(&amp;amp;original)&lt;/code&gt; rather than &lt;code&gt;original.clone()&lt;/code&gt;.&lt;/strong&gt; Both work. The first makes it obvious at the call site that you are cloning the &lt;em&gt;handle&lt;/em&gt;, not the data. In concurrent code that distinction is the difference between a pointer copy and a deep copy, and it is worth spelling out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;Arc::as_ptr&lt;/code&gt;&lt;/strong&gt; returns a raw pointer to the inner value. We only print it — never dereference it. If three handles print the same address, they refer to one allocation, and the "three &lt;code&gt;Data&lt;/code&gt; values" theory is dead.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A custom &lt;code&gt;Drop&lt;/code&gt;.&lt;/strong&gt; This is the strongest evidence available. Reference counts are just numbers we are being told; a destructor running is an event we can observe. If &lt;code&gt;Dropping Data&lt;/code&gt; prints exactly once, at exactly the right moment, the model is confirmed by behaviour rather than by report.&lt;/p&gt;

&lt;h3&gt;
  
  
  Predict before you run
&lt;/h3&gt;

&lt;p&gt;Worth answering for yourself first:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Will all three &lt;code&gt;Arc&lt;/code&gt;s print the same pointer?&lt;/li&gt;
&lt;li&gt;What is &lt;code&gt;strong_count&lt;/code&gt; after each clone?&lt;/li&gt;
&lt;li&gt;Does cloning invoke &lt;code&gt;Data&lt;/code&gt;'s own clone? (It cannot — &lt;code&gt;Data&lt;/code&gt; does not implement &lt;code&gt;Clone&lt;/code&gt;.)&lt;/li&gt;
&lt;li&gt;When does &lt;code&gt;Data::drop&lt;/code&gt; run?&lt;/li&gt;
&lt;li&gt;How many times does it run?&lt;/li&gt;
&lt;li&gt;What does &lt;code&gt;weak_count&lt;/code&gt; show when we never created a &lt;code&gt;Weak&lt;/code&gt;?&lt;/li&gt;
&lt;li&gt;Does &lt;code&gt;drop(clone_a)&lt;/code&gt; free the allocation?&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  The output
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;after Arc::new
original: ptr=0x563fea01ed70, strong=1, weak=0, value=42

after clone_a
original: ptr=0x563fea01ed70, strong=2, weak=0, value=42
clone_a: ptr=0x563fea01ed70, strong=2, weak=0, value=42

after clone_b
original: ptr=0x563fea01ed70, strong=3, weak=0, value=42
clone_a: ptr=0x563fea01ed70, strong=3, weak=0, value=42
clone_b: ptr=0x563fea01ed70, strong=3, weak=0, value=42

after dropping clone_a
original: ptr=0x563fea01ed70, strong=2, weak=0, value=42
clone_b: ptr=0x563fea01ed70, strong=2, weak=0, value=42

after dropping clone_b
original: ptr=0x563fea01ed70, strong=1, weak=0, value=42

leaving main
Dropping Data { value: 42 }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every clause of the hypothesis, confirmed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One address&lt;/strong&gt;, &lt;code&gt;0x563fea01ed70&lt;/code&gt;, printed by all three handles at every stage. One allocation, three pointers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The count tracks handles exactly&lt;/strong&gt;: 1, 2, 3, then 2, then 1.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Dropping Data&lt;/code&gt; prints once&lt;/strong&gt;, after &lt;code&gt;leaving main&lt;/code&gt; — when &lt;code&gt;original&lt;/code&gt;, the final handle, goes out of scope at the end of &lt;code&gt;main&lt;/code&gt;. Dropping the two clones destroyed nothing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;weak_count&lt;/code&gt; is 0&lt;/strong&gt; throughout, since we never made a &lt;code&gt;Weak&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note the ordering of the last two lines. &lt;code&gt;leaving main&lt;/code&gt; is the last statement in the function; the destructor runs &lt;em&gt;after&lt;/em&gt; it, as &lt;code&gt;original&lt;/code&gt; goes out of scope. Rust drops locals at the end of the scope, in reverse declaration order. Small thing, but if you have ever wondered why a "shutting down" log line appears before the cleanup it is describing, that is why.&lt;/p&gt;




&lt;h2&gt;
  
  
  Experiment 2 — The Same &lt;code&gt;Arc&lt;/code&gt; Across OS Threads
&lt;/h2&gt;

&lt;p&gt;Single-threaded proof is nice; the whole point of &lt;code&gt;Arc&lt;/code&gt; is threads. So:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;Arc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;thread&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Config&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="nb"&gt;Drop&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;Config&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;drop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"  Config::drop ran for {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;cfg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Arc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"prod-eu-west-1"&lt;/span&gt;&lt;span class="nf"&gt;.into&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"main thread: handle at {:p} on the stack, points to {:p} on the heap"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
             &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;cfg&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;Arc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;as_ptr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"strong before spawning = {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;Arc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;strong_count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;handles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Vec&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;cfg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Arc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;clone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;          &lt;span class="c1"&gt;// one increment, once per thread&lt;/span&gt;
        &lt;span class="n"&gt;handles&lt;/span&gt;&lt;span class="nf"&gt;.push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;thread&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;spawn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;move&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"  worker {i}: tid={:?}  handle at {:p}  heap {:p}  strong(snapshot)={}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                     &lt;span class="nn"&gt;thread&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;current&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.id&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                     &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;cfg&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                     &lt;span class="nn"&gt;Arc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;as_ptr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                     &lt;span class="nn"&gt;Arc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;strong_count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="p"&gt;}));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;handles&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="nf"&gt;.join&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"strong after all workers joined = {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;Arc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;strong_count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"dropping the last handle now:"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;main thread: handle at 0x7ffedfc480b8 on the stack, points to 0x55a027533af0 on the heap
strong before spawning = 1
  worker 1: tid=ThreadId(3)  handle at 0x7fe28effdcf0  heap 0x55a027533af0  strong(snapshot)=4
  worker 0: tid=ThreadId(2)  handle at 0x7fe28f1fecf0  heap 0x55a027533af0  strong(snapshot)=3
  worker 2: tid=ThreadId(4)  handle at 0x7fe28edfccf0  heap 0x55a027533af0  strong(snapshot)=2
  worker 3: tid=ThreadId(5)  handle at 0x7fe28ebfbcf0  heap 0x55a027533af0  strong(snapshot)=2
strong after all workers joined = 1
dropping the last handle now:
  Config::drop ran for prod-eu-west-1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is a lot in there.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Each thread's handle lives on its own stack; the value lives in one shared place.&lt;/strong&gt; The stack addresses are &lt;code&gt;0x7fe28effdcf0&lt;/code&gt;, &lt;code&gt;0x7fe28f1fecf0&lt;/code&gt;, &lt;code&gt;0x7fe28edfccf0&lt;/code&gt;, &lt;code&gt;0x7fe28ebfbcf0&lt;/code&gt; — spread about 2 MiB apart, which is exactly the per-thread stack spacing article 1 measured. The heap address is &lt;code&gt;0x55a027533af0&lt;/code&gt; for all four. Four private handles, one shared allocation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The workers did not run in order.&lt;/strong&gt; Worker 1 printed first. Nothing schedules threads in spawn order — the kernel picks, as article 1's &lt;code&gt;PSR&lt;/code&gt; column showed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The counts are 4, 3, 2, 2 — and this is the important one.&lt;/strong&gt; They are not "wrong". Each is a truthful reading of the count at the instant that thread looked, while other threads were concurrently dropping their handles. Two threads happened to read &lt;code&gt;2&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is the first genuinely concurrent lesson of the article: &lt;strong&gt;&lt;code&gt;Arc::strong_count&lt;/code&gt; is a snapshot, not a fact.&lt;/strong&gt; By the time the value reaches your variable it may already be stale. It is useful in tests and while debugging; any logic of the form &lt;code&gt;if Arc::strong_count(&amp;amp;x) == 1 { ...assume exclusive... }&lt;/code&gt; is a race. (Rust gives you &lt;code&gt;Arc::get_mut&lt;/code&gt; and &lt;code&gt;Arc::try_unwrap&lt;/code&gt; for that, which do the check atomically.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The last drop is still exactly one drop.&lt;/strong&gt; Four threads incremented, four decremented as they exited, &lt;code&gt;main&lt;/code&gt; dropped the last handle, and &lt;code&gt;Config::drop&lt;/code&gt; ran once. No coordination, no locking, no leader election — just a counter that every participant agrees on.&lt;/p&gt;

&lt;p&gt;That is a distributed-systems idea running inside a single process: no node knows who is last, but the shared counter means exactly one of them finds out.&lt;/p&gt;




&lt;h2&gt;
  
  
  Experiment 3 — What Is Actually in That Allocation?
&lt;/h2&gt;

&lt;p&gt;We have proved there is one allocation and that a number inside it tracks owners. Now let's look at it directly.&lt;/p&gt;

&lt;p&gt;The counters are private fields, but the standard library declares the header with &lt;code&gt;#[repr(C)]&lt;/code&gt;, which fixes the field order. That means we can compute where they are from the data pointer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[repr(C)]&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;FakeArcInner&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;strong&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;AtomicUsize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;weak&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;AtomicUsize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="n"&gt;counts&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;Arc&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;unsafe&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Arc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;as_ptr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;inner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="nb"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;FakeArcInner&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;inner&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="py"&gt;.strong&lt;/span&gt;&lt;span class="nf"&gt;.load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Ordering&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Relaxed&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;inner&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="py"&gt;.weak&lt;/span&gt;&lt;span class="nf"&gt;.load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Ordering&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Relaxed&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To be clear: this is layout-dependent pointer arithmetic that depends on an implementation detail. It is fine for an experiment and has no place in real code. We are doing it because reading the bytes is more convincing than reading the docs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sizes / alignment
  size_of::&amp;lt;Arc&amp;lt;u32&amp;gt;&amp;gt;()          = 8
  size_of::&amp;lt;Arc&amp;lt;[u8; 4096]&amp;gt;&amp;gt;()   = 8
  size_of::&amp;lt;Rc&amp;lt;u32&amp;gt;&amp;gt;()           = 8
  size_of::&amp;lt;&amp;amp;u32&amp;gt;()              = 8
  size_of::&amp;lt;Arc&amp;lt;[u32]&amp;gt;&amp;gt;()        = 16  (unsized: fat pointer)
  size_of::&amp;lt;Option&amp;lt;Arc&amp;lt;u32&amp;gt;&amp;gt;&amp;gt;()  = 8  (niche optimisation)
  size_of::&amp;lt;AtomicUsize&amp;gt;()       = 8
  align_of::&amp;lt;AtomicUsize&amp;gt;()      = 8

one Arc&amp;lt;u32&amp;gt; holding 42
  Arc::as_ptr(&amp;amp;a)   = 0x560b747d1d70   (points at the DATA)
  &amp;amp;a as *const _    = 0x7ffdc796b830   (the Arc handle itself, on the stack)
  header starts at  = 0x560b747d1d60
  strong/weak read from the header = (1, 1)
  Arc::strong_count / weak_count   = (1, 0)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Four things to take from this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;An &lt;code&gt;Arc&amp;lt;T&amp;gt;&lt;/code&gt; is eight bytes — the size of a pointer.&lt;/strong&gt; &lt;code&gt;Arc&amp;lt;[u8; 4096]&amp;gt;&lt;/code&gt; is also eight bytes. The handle does not grow with the payload, because the payload is not in the handle. Moving an &lt;code&gt;Arc&lt;/code&gt; moves one machine word.&lt;/p&gt;

&lt;p&gt;The one exception in that list is &lt;code&gt;Arc&amp;lt;[u32]&amp;gt;&lt;/code&gt; at sixteen bytes. A slice has no fixed length known at compile time, so the pointer has to carry the length alongside the address. Two words instead of one. Rust calls that a &lt;em&gt;fat pointer&lt;/em&gt;, and you get one whenever the thing you are pointing at has a size only known at runtime — slices and trait objects, mostly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;Option&amp;lt;Arc&amp;lt;T&amp;gt;&amp;gt;&lt;/code&gt; is also eight bytes.&lt;/strong&gt; The compiler knows an &lt;code&gt;Arc&lt;/code&gt;'s pointer is never null, so it uses null to represent &lt;code&gt;None&lt;/code&gt;. This is called a &lt;em&gt;niche optimisation&lt;/em&gt;: the type already had an impossible value lying around, so the enum tag can hide inside it. &lt;code&gt;Option&amp;lt;Arc&amp;lt;T&amp;gt;&amp;gt;&lt;/code&gt; costs nothing over &lt;code&gt;Arc&amp;lt;T&amp;gt;&lt;/code&gt;, which is why idiomatic Rust uses it freely.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The pointer aims at the data, not at the start of the allocation.&lt;/strong&gt; &lt;code&gt;as_ptr&lt;/code&gt; gives &lt;code&gt;0x…d70&lt;/code&gt;; the header begins 16 bytes earlier at &lt;code&gt;0x…d60&lt;/code&gt;. Dereferencing an &lt;code&gt;Arc&lt;/code&gt; therefore needs no arithmetic at all — the pointer is already at the value. The bookkeeping sits &lt;em&gt;behind&lt;/em&gt; you. That is a deliberate design choice favouring the common operation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The raw weak count is 1 while &lt;code&gt;Arc::weak_count()&lt;/code&gt; reports 0.&lt;/strong&gt; This is the one place my mental model was wrong, and it is not a bug. All the strong handles collectively hold a single &lt;em&gt;implicit&lt;/em&gt; weak reference. It exists because the value and the memory are freed at two different moments.&lt;/p&gt;

&lt;p&gt;When the strong count hits zero, the value is destroyed — its destructor runs. But the memory block cannot go back to the allocator yet, because a &lt;code&gt;Weak&lt;/code&gt; might still be pointing at it, and that &lt;code&gt;Weak&lt;/code&gt; needs somewhere valid to look when someone calls &lt;code&gt;upgrade()&lt;/code&gt;. So the block is only released when the weak count hits zero too. The implicit weak reference is what holds the block open for as long as any strong handle exists. &lt;code&gt;Arc::weak_count&lt;/code&gt; subtracts that implicit one so the number matches the count of &lt;code&gt;Weak&lt;/code&gt;s &lt;em&gt;you&lt;/em&gt; created.&lt;/p&gt;

&lt;p&gt;Watching both counters move confirms it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;after two clones
  Arc::as_ptr(&amp;amp;a) = 0x560b747d1d70
  Arc::as_ptr(&amp;amp;b) = 0x560b747d1d70
  Arc::as_ptr(&amp;amp;c) = 0x560b747d1d70   &amp;lt;- all three point at the same allocation
  header counts   = (3, 1)

after one downgrade (Weak)
  header counts = (3, 2)   (strong, weak)
  strong_count=3  weak_count=1
after dropping the two clones
  header counts = (1, 2)
after dropping the Weak
  header counts = (1, 1)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The standard library's own definition matches the bytes we just read:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[repr(C,&lt;/span&gt; &lt;span class="nd"&gt;align(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="nd"&gt;))]&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;ArcInner&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="nb"&gt;Sized&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;strong&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Atomic&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;weak&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Atomic&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the diagram from the top of the article was right, with one correction — the weak count starts at 1, not 0:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                 Heap allocation
              ┌──────────────────┐
   offset 0   │ strong = 3       │
   offset 8   │ weak   = 1       │   &amp;lt;- the implicit one
   offset 16  │ String "hello"   │   &amp;lt;- Arc::as_ptr points HERE
              └──────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Experiment 4 — What Does &lt;code&gt;Arc::new&lt;/code&gt; Allocate?
&lt;/h2&gt;

&lt;p&gt;Rather than reason about the size, let's log it.&lt;/p&gt;

&lt;p&gt;Rust lets you swap out the global allocator — the thing that actually hands out heap memory. If we replace it with one that prints every allocation and free, we can watch &lt;code&gt;Arc::new&lt;/code&gt; do its work:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;unsafe&lt;/span&gt; &lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="n"&gt;GlobalAlloc&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;Logger&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;unsafe&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;alloc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Layout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="nb"&gt;u8&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;unsafe&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="nf"&gt;.alloc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;LOG&lt;/span&gt;&lt;span class="nf"&gt;.load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Ordering&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Relaxed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;format_args!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"    alloc  size={:&amp;lt;5} align={:&amp;lt;3} -&amp;gt; {:p}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                              &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="nf"&gt;.size&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="nf"&gt;.align&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;p&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;// dealloc likewise&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One trap, which I fell into: &lt;strong&gt;the logger must not allocate.&lt;/strong&gt; My first version used &lt;code&gt;format!&lt;/code&gt;, which allocates, which re-enters the logger, which allocates again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Arc&amp;lt;u8&amp;gt;  (payload 1 bytes, align 1)

thread 'main' (2577) has overflowed its stack
fatal runtime error: stack overflow, aborting
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the guard page from article 1 catching runaway recursion — the same &lt;code&gt;PROT_NONE&lt;/code&gt; page, doing the same job. The fix is to format into a fixed stack buffer and call &lt;code&gt;write&lt;/code&gt; directly.&lt;/p&gt;

&lt;p&gt;With that, here is the real cost of &lt;code&gt;Arc::new&lt;/code&gt;. Each block creates an &lt;code&gt;Arc&lt;/code&gt;, clones it, drops the clone, then drops the original:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Arc&amp;lt;()&amp;gt;: payload 0 bytes, align 1
    alloc  size=16    align=8   -&amp;gt; 0x560a0e179d60
    free   size=16    align=8   &amp;lt;- 0x560a0e179d60

Arc&amp;lt;u32&amp;gt;: payload 4 bytes, align 4
    alloc  size=24    align=8   -&amp;gt; 0x560a0e179d60
    free   size=24    align=8   &amp;lt;- 0x560a0e179d60

Arc&amp;lt;[u8; 100]&amp;gt;: payload 100 bytes, align 1
    alloc  size=120   align=8   -&amp;gt; 0x560a0e179480
    free   size=120   align=8   &amp;lt;- 0x560a0e179480

Arc&amp;lt;Aligned64&amp;gt;: payload 64 bytes, align 64
    alloc  size=128   align=64  -&amp;gt; 0x560a0e179d80
    free   size=128   align=64  &amp;lt;- 0x560a0e179d80

Arc&amp;lt;str&amp;gt; from "hello, arc" (10 bytes, unsized)
    alloc  size=32    align=8   -&amp;gt; 0x560a0e179ae0
    free   size=32    align=8   &amp;lt;- 0x560a0e179ae0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Collected:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;code&gt;Arc&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;payload&lt;/th&gt;
&lt;th&gt;allocated&lt;/th&gt;
&lt;th&gt;overhead&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;u8&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;td&gt;23&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;u32&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;u64&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;[u8; 100]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;120&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;Aligned64&lt;/code&gt; (&lt;code&gt;align(64)&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;64&lt;/td&gt;
&lt;td&gt;128&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;64&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;str&lt;/code&gt; (&lt;code&gt;"hello, arc"&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Sixteen bytes of header, then the payload, rounded up for alignment.&lt;/strong&gt; An &lt;code&gt;Arc&amp;lt;u32&amp;gt;&lt;/code&gt; allocates 24 bytes to hold 4 — 83% of it is bookkeeping. That is fine for a config object and expensive for a million small nodes. If you find yourself writing &lt;code&gt;Arc&amp;lt;u32&amp;gt;&lt;/code&gt; at scale, consider one &lt;code&gt;Arc&lt;/code&gt; over a slab of values instead of one &lt;code&gt;Arc&lt;/code&gt; per value.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The &lt;code&gt;align(64)&lt;/code&gt; row is the one to remember.&lt;/strong&gt; If you mark a struct &lt;code&gt;#[repr(align(64))]&lt;/code&gt; — a common trick to give it its own cache line, which article 8 is about — the data field has to start at offset 64, so the header padding grows from 16 bytes to 64. You quadrupled the per-object overhead. That may well be the right trade. Just know you made it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cloning allocates nothing.&lt;/strong&gt; Every block shows exactly one &lt;code&gt;alloc&lt;/code&gt; and one &lt;code&gt;free&lt;/code&gt; despite a clone in between. The hypothesis said clone copies a pointer and bumps a number; the allocator log agrees.&lt;/p&gt;




&lt;h2&gt;
  
  
  Experiment 5 — What Does &lt;code&gt;clone&lt;/code&gt; Compile To?
&lt;/h2&gt;

&lt;p&gt;Now the part I actually wanted to see.&lt;/p&gt;

&lt;p&gt;A quick note on method, since this is where the article goes properly low-level. &lt;strong&gt;Disassembly&lt;/strong&gt; means taking the compiled binary and printing the machine instructions it contains, which is what &lt;code&gt;objdump&lt;/code&gt; does. It is the ground truth: whatever the source says, this is what the CPU will run.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Arc::clone&lt;/code&gt; is generic and normally gets inlined into its caller, which leaves no separate function to look at. So we force one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[unsafe(no_mangle)]&lt;/span&gt;
&lt;span class="nd"&gt;#[inline(never)]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;extern&lt;/span&gt; &lt;span class="s"&gt;"C"&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;arc_clone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;Arc&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Arc&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nn"&gt;Arc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;clone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;#[unsafe(no_mangle)]&lt;/span&gt;
&lt;span class="nd"&gt;#[inline(never)]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;extern&lt;/span&gt; &lt;span class="s"&gt;"C"&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;rc_clone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;Rc&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Rc&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nn"&gt;Rc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;clone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Compile with optimisations and disassemble:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;rustc &lt;span class="nt"&gt;-O&lt;/span&gt; &lt;span class="nt"&gt;-C&lt;/span&gt; &lt;span class="nv"&gt;panic&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;abort disasm.rs &lt;span class="nt"&gt;-o&lt;/span&gt; disasm
&lt;span class="nv"&gt;$ &lt;/span&gt;objdump &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--no-show-raw-insn&lt;/span&gt; &lt;span class="nt"&gt;-M&lt;/span&gt; intel disasm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0000000000013e80 &amp;lt;arc_clone&amp;gt;:
   13e80:   mov    rax,QWORD PTR [rdi]
   13e83:   lock inc QWORD PTR [rax]
   13e87:   jle    13e8a &amp;lt;arc_clone+0xa&amp;gt;
   13e89:   ret
   13e8a:   ud2

0000000000013eb0 &amp;lt;rc_clone&amp;gt;:
   13eb0:   mov    rax,QWORD PTR [rdi]
   13eb3:   inc    QWORD PTR [rax]
   13eb6:   je     13eb9 &amp;lt;rc_clone+0x9&amp;gt;
   13eb8:   ret
   13eb9:   ud2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Line them up:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Rc::clone     inc      QWORD PTR [rax]
Arc::clone    lock inc QWORD PTR [rax]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The entire difference between &lt;code&gt;Rc&lt;/code&gt; and &lt;code&gt;Arc&lt;/code&gt; is the four-letter &lt;code&gt;lock&lt;/code&gt; prefix.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Same instruction. Same operand. Same three-instruction function. &lt;code&gt;Rc&lt;/code&gt; is not a simpler algorithm — it is the same algorithm with the atomicity removed. Everything written about &lt;code&gt;Arc&lt;/code&gt; being "the thread-safe one", and that whole &lt;code&gt;Send&lt;/code&gt; compile error from earlier, reduces on x86-64 to one prefix byte.&lt;/p&gt;

&lt;p&gt;Reading the instructions one at a time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;mov rax, QWORD PTR [rdi]&lt;/code&gt; — load the pointer out of the &lt;code&gt;Arc&lt;/code&gt; handle. &lt;code&gt;rdi&lt;/code&gt; holds the function's first argument.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;lock inc QWORD PTR [rax]&lt;/code&gt; — atomically add one to the value at that address. That address is the strong count, at offset 0 of the header, exactly where experiment 3 found it. The &lt;code&gt;lock&lt;/code&gt; prefix tells the CPU to make this read-modify-write indivisible with respect to other cores.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;jle&lt;/code&gt; / &lt;code&gt;ud2&lt;/code&gt; — the overflow guard. &lt;code&gt;ud2&lt;/code&gt; is an &lt;em&gt;undefined instruction&lt;/em&gt;: executing it crashes the process immediately. If the count ever exceeded &lt;code&gt;isize::MAX&lt;/code&gt;, wrapping around toward zero would free memory that other threads still hold, so the library chooses to abort instead.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;From the source:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;MAX_REFCOUNT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;isize&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note it is &lt;code&gt;isize::MAX&lt;/code&gt;, not &lt;code&gt;usize::MAX&lt;/code&gt;. Half the range is deliberately given away so the check can be a single cheap signed comparison — the &lt;code&gt;jle&lt;/code&gt; above. You cannot hit this by accident; you could hit it with &lt;code&gt;mem::forget&lt;/code&gt; in a loop.&lt;/p&gt;

&lt;p&gt;And &lt;code&gt;drop&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0000000000013e90 &amp;lt;arc_drop&amp;gt;:
   13e90:   mov    rax,QWORD PTR [rdi]
   13e93:   lock dec QWORD PTR [rax]
   13e97:   jne    13ea1 &amp;lt;arc_drop+0x11&amp;gt;
   13e99:   mov    rdi,QWORD PTR [rdi]
   13e9c:   jmp    13d30 &amp;lt;...Arc$LT$T$C$A$GT$9drop_slow...&amp;gt;
   13ea1:   ret
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Four instructions in the common case. Decrement; if the result is not zero, return. Only when it reaches zero does it jump to &lt;code&gt;drop_slow&lt;/code&gt;, which runs the value's destructor and releases the memory.&lt;/p&gt;

&lt;p&gt;That structure — tiny hot path, expensive path moved out of line — is what you would hand-write if you were implementing this yourself in C. The compiler produced it for you.&lt;/p&gt;

&lt;p&gt;So the full picture of a clone:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Arc::clone(&amp;amp;a)
   ↓
load the pointer                      (1 instruction)
   ↓
lock inc the strong count             (1 instruction, atomic)
   ↓
check for overflow, return            (2 instructions)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three or four instructions, no branches taken, no function calls, no allocation. On paper, nearly free. The rest of this article is about why "on paper" is doing a lot of work in that sentence.&lt;/p&gt;




&lt;h2&gt;
  
  
  Experiment 6 — The Memory Orderings
&lt;/h2&gt;

&lt;p&gt;Here is something that always looked arbitrary to me. Increment and decrement are mirror images, but &lt;code&gt;Arc&lt;/code&gt; treats them differently:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;clone&lt;/code&gt; increments with &lt;strong&gt;&lt;code&gt;Relaxed&lt;/code&gt;&lt;/strong&gt; ordering.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;drop&lt;/code&gt; decrements with &lt;strong&gt;&lt;code&gt;Release&lt;/code&gt;&lt;/strong&gt; ordering, then performs an &lt;strong&gt;&lt;code&gt;Acquire&lt;/code&gt;&lt;/strong&gt; fence.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What is a memory ordering? Modern CPUs and compilers reorder memory operations for speed. An ordering is a constraint you attach to an atomic operation telling them what they may &lt;em&gt;not&lt;/em&gt; reorder around it. Three of them matter here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Relaxed&lt;/code&gt;&lt;/strong&gt; — no constraint at all. Just make the arithmetic atomic and nothing else.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Release&lt;/code&gt;&lt;/strong&gt; — "everything I did before this point must be visible to whoever observes this operation."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Acquire&lt;/code&gt;&lt;/strong&gt; — the matching half. "Everything the other side did before releasing is now visible to me."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;Release&lt;/code&gt; and &lt;code&gt;Acquire&lt;/code&gt; come in pairs. One thread publishes, another thread picks up. Neither is useful alone.&lt;/p&gt;

&lt;p&gt;Crucially, ordering is not about the count being correct — atomicity handles that. It is about &lt;em&gt;the data the count protects&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The standard library explains the increment:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Using a relaxed ordering is alright here, as knowledge of the original reference prevents other threads from erroneously deleting the object... Increasing the reference counter can always be done with &lt;code&gt;memory_order_relaxed&lt;/code&gt;: New references to an object can only be formed from an existing reference, and passing an existing reference from one thread to another must already provide any required synchronization."&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;old_size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="nf"&gt;.inner&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="py"&gt;.strong&lt;/span&gt;&lt;span class="nf"&gt;.fetch_add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Relaxed&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The argument is worth restating slowly, because it is genuinely subtle. To clone an &lt;code&gt;Arc&lt;/code&gt;, you must already &lt;strong&gt;hold&lt;/strong&gt; one. However you got it — a channel, a &lt;code&gt;Mutex&lt;/code&gt;, a &lt;code&gt;thread::spawn&lt;/code&gt; closure — that handover already established the necessary ordering. The increment therefore publishes nothing new that another thread must observe in a particular order. So it can be as weak as an atomic gets.&lt;/p&gt;

&lt;p&gt;Dropping is not like that. When you drop, you are announcing "I am finished touching this value" to whichever thread happens to drop last — because &lt;em&gt;that&lt;/em&gt; thread is going to run the destructor. Everything you did to the value must be visible to it before it starts tearing the value down. That is precisely a release. And the thread that sees the count hit zero needs the matching acquire before it may safely destroy anything:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[cfg(not(sanitize&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"thread"&lt;/span&gt;&lt;span class="nd"&gt;))]&lt;/span&gt;
&lt;span class="nd"&gt;macro_rules!&lt;/span&gt; &lt;span class="n"&gt;acquire&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$x:expr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nn"&gt;atomic&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;fence&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Acquire&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;#[cfg(sanitize&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"thread"&lt;/span&gt;&lt;span class="nd"&gt;)]&lt;/span&gt;
&lt;span class="nd"&gt;macro_rules!&lt;/span&gt; &lt;span class="n"&gt;acquire&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$x:expr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$x&lt;/span&gt;&lt;span class="nf"&gt;.load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Acquire&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(A nice detail: the thread-sanitizer build uses a load instead of a fence, because TSan models fences poorly and would report false positives. The ordering is chosen partly for the benefit of a debugging tool.)&lt;/p&gt;

&lt;p&gt;So what do these orderings cost? I isolated the two operations into a tiny &lt;code&gt;#![no_std]&lt;/code&gt; crate so I could see the generated code without the rest of &lt;code&gt;Arc&lt;/code&gt; around it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[unsafe(no_mangle)]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;clone_op&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strong&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;AtomicUsize&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;strong&lt;/span&gt;&lt;span class="nf"&gt;.fetch_add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;Ordering&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Relaxed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;#[unsafe(no_mangle)]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;drop_op&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strong&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;AtomicUsize&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;strong&lt;/span&gt;&lt;span class="nf"&gt;.fetch_sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;Ordering&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Release&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nf"&gt;fence&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Ordering&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Acquire&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On x86-64:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;clone_op:
    movl    $1, %eax
    lock        xaddq   %rax, (%rdi)
    retq

drop_op:
    lock        decq    (%rdi)
    sete    %al
    jne .LBB1_2
    #MEMBARRIER
.LBB1_2:
    retq
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Look closely at &lt;code&gt;#MEMBARRIER&lt;/code&gt;. That is an assembler &lt;strong&gt;comment&lt;/strong&gt;. The &lt;code&gt;Acquire&lt;/code&gt; fence generates &lt;strong&gt;zero instructions&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That is not the compiler ignoring you. x86-64 has a strongly ordered memory model (often called TSO, for Total Store Order): the hardware already refuses to reorder loads with loads or stores with stores, and a &lt;code&gt;lock&lt;/code&gt;-prefixed read-modify-write is already a full barrier. The fence is genuinely redundant &lt;em&gt;on this architecture&lt;/em&gt;, so it costs nothing.&lt;/p&gt;

&lt;p&gt;Compare against the strictest possible version to confirm:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;drop_op_seqcst:
    lock        decq    (%rdi)
    sete    %al
    retq
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;SeqCst&lt;/code&gt; and &lt;code&gt;Release&lt;/code&gt;-plus-fence emit the same real work here.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;An honest caveat.&lt;/strong&gt; This tells you something about x86-64, not about orderings in general. On a weakly ordered architecture — AArch64, RISC-V, POWER — &lt;code&gt;Relaxed&lt;/code&gt;, &lt;code&gt;Release&lt;/code&gt; and &lt;code&gt;SeqCst&lt;/code&gt; compile to genuinely different instructions with genuinely different costs, and the &lt;code&gt;Acquire&lt;/code&gt; fence becomes a real barrier. I wanted to show that contrast with actual output, but this machine has only the x86-64 standard library installed and I could not reach the toolchain server to add an AArch64 target. Rather than paste assembly I did not generate, I am marking it unmeasured. If you are on an ARM Mac, &lt;code&gt;rustc -O --emit asm --target aarch64-apple-darwin&lt;/code&gt; on that snippet will show you what I could not.&lt;/p&gt;

&lt;p&gt;What this &lt;em&gt;does&lt;/em&gt; establish: &lt;strong&gt;on x86-64, &lt;code&gt;Arc&lt;/code&gt;'s careful ordering choices are free.&lt;/strong&gt; The cost of &lt;code&gt;Arc&lt;/code&gt; is not the orderings. It is the &lt;code&gt;lock&lt;/code&gt; prefix — and specifically, what that prefix does to the other cores.&lt;/p&gt;




&lt;h2&gt;
  
  
  Experiment 7 — What Does It Cost?
&lt;/h2&gt;

&lt;p&gt;Single thread, nothing contended, 20 million iterations of each operation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;single thread, uncontended, 20000000 iterations each
  *arc                (deref)             0.44 ns/op
  Arc::strong_count   (load)              0.56 ns/op
  Rc::clone + drop                        4.60 ns/op
  Arc::clone + drop                      12.89 ns/op
  Arc::downgrade + drop Weak             17.96 ns/op
  Weak::upgrade + drop Arc               18.01 ns/op
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reading through an &lt;code&gt;Arc&lt;/code&gt; costs &lt;strong&gt;0.44 ns&lt;/strong&gt; — one pointer dereference, no atomic, effectively free. Cloning and dropping costs &lt;strong&gt;12.89 ns&lt;/strong&gt;, about &lt;strong&gt;29x&lt;/strong&gt; more than reading. And &lt;code&gt;Arc&lt;/code&gt; is &lt;strong&gt;2.8x&lt;/strong&gt; the cost of &lt;code&gt;Rc&lt;/code&gt; for the identical operation. That factor of 2.8 is the price of one &lt;code&gt;lock&lt;/code&gt; prefix.&lt;/p&gt;

&lt;p&gt;The ratios are the useful part. Thirteen nanoseconds is not much in absolute terms — article 1 measured thread creation at roughly 37 *micro*seconds, three thousand times more. But it is a lot compared with the thing people assume it is comparable to. &lt;code&gt;Arc::clone&lt;/code&gt; is not "basically free, like a borrow". It is thirty times a borrow.&lt;/p&gt;

&lt;p&gt;And that is the &lt;em&gt;uncontended&lt;/em&gt; number.&lt;/p&gt;




&lt;h2&gt;
  
  
  Stressing the Design — More Than One Core
&lt;/h2&gt;

&lt;p&gt;Everything so far was one thread. Now the question that decides whether your server scales: what happens when several threads clone the &lt;em&gt;same&lt;/em&gt; &lt;code&gt;Arc&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;Three cases. &lt;strong&gt;clone private&lt;/strong&gt;: each thread has its own separate &lt;code&gt;Arc&lt;/code&gt;, so each touches its own counter. &lt;strong&gt;clone shared&lt;/strong&gt;: all threads clone one &lt;code&gt;Arc&lt;/code&gt;, hammering one counter. &lt;strong&gt;deref shared&lt;/strong&gt;: all threads read through one shared &lt;code&gt;Arc&lt;/code&gt; without cloning.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;median of 5 runs, 6000000 clone+drop pairs per thread per run

 threads    clone private     clone shared      ratio     deref shared
       1         12.91 ns         12.93 ns       1.0x          0.64 ns
       2         13.39 ns         60.76 ns       4.5x          0.62 ns
       4         27.90 ns        119.73 ns       4.3x          1.54 ns
       8         53.03 ns        252.25 ns       4.8x          1.35 ns
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three findings, and the middle one is the point of the article.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sharing the counter costs about 4.5x
&lt;/h3&gt;

&lt;p&gt;At two threads, a clone goes from 13.4 ns to 60.8 ns. The instruction did not change. What changed is the cache line.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;cache line&lt;/strong&gt; is the unit in which CPUs move memory — 64 bytes on this machine (&lt;code&gt;getconf LEVEL1_DCACHE_LINESIZE&lt;/code&gt; says so). Cores do not fetch individual bytes; they fetch lines into their own private caches. To &lt;em&gt;write&lt;/em&gt; to a line, a core must hold it &lt;strong&gt;exclusively&lt;/strong&gt; — no other core may have a copy.&lt;/p&gt;

&lt;p&gt;So when two cores both want to &lt;code&gt;lock inc&lt;/code&gt; the same counter, the line containing it must bounce between them. Core 0 takes exclusive ownership, increments, then core 1 must take it away, and so on. Neither core can proceed while the other holds it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     Core 0 cache            Core 1 cache
   ┌──────────────┐        ┌──────────────┐
   │ strong count │ ◄────► │ strong count │
   └──────────────┘        └──────────────┘
          ▲                       ▲
          └── the line ping-pongs ─┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The atomic operation is fast. Acquiring the line is not. This is &lt;strong&gt;cache coherence&lt;/strong&gt; showing up in your latency numbers, and it is why "just wrap it in an &lt;code&gt;Arc&lt;/code&gt;" can quietly put a ceiling on how well a hot path scales.&lt;/p&gt;

&lt;h3&gt;
  
  
  Read-only sharing is free
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;deref shared&lt;/code&gt; column stays under 2 ns at every thread count. Many cores may hold the same line simultaneously as long as they are only &lt;em&gt;reading&lt;/em&gt; — coherence only forces exclusivity for writes.&lt;/p&gt;

&lt;p&gt;So sharing immutable data across threads costs nothing. Sharing a &lt;em&gt;counter&lt;/em&gt; costs a great deal. The awkward part is that &lt;code&gt;Arc&lt;/code&gt; makes you do the second in order to get the first.&lt;/p&gt;

&lt;h3&gt;
  
  
  The &lt;code&gt;clone private&lt;/code&gt; column rises too — and that is article 1's fault
&lt;/h3&gt;

&lt;p&gt;This machine has 2 cores. At 4 and 8 threads the private numbers roughly double and quadruple: 12.9, 13.4, 27.9, 53.0. That is not atomics. That is exactly the oversubscription curve from article 1 — throughput holds, per-unit latency degrades in proportion, because there are more runnable threads than cores.&lt;/p&gt;

&lt;p&gt;Which is why the &lt;code&gt;ratio&lt;/code&gt; column exists. Dividing shared by private cancels the scheduling effect and isolates the contention.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Mistake That Shows Up in Production
&lt;/h2&gt;

&lt;p&gt;Here is a shape I have written myself more than once. Some shared thing every request needs — config, a routing table, a connection pool, a compiled regex set — behind an &lt;code&gt;Arc&lt;/code&gt;. Then, inside the request loop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;per_msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Arc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;clone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;     &lt;span class="c1"&gt;// the mistake&lt;/span&gt;
&lt;span class="n"&gt;acc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;acc&lt;/span&gt;&lt;span class="nf"&gt;.wrapping_add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;per_msg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;instead of simply borrowing what you already own:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="n"&gt;acc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;acc&lt;/span&gt;&lt;span class="nf"&gt;.wrapping_add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both compile. Both are correct. One of them makes every worker thread write to the same cache line on every single message.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;3000000 messages per thread, shared Arc&amp;lt;Config&amp;gt;
 threads    clone per message       borrow the Arc   overhead
       1             12.60 ns              0.81 ns      15.6x
       2             80.79 ns              0.64 ns     126.1x
       4            165.67 ns              1.16 ns     142.8x
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At one thread the unnecessary clone costs 16x. At two threads, &lt;strong&gt;126x&lt;/strong&gt;. At four, &lt;strong&gt;143x&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Look at how the two columns behave differently. The borrow column is flat — it does not care how many threads are running, because reading shared immutable data scales perfectly. The clone column gets dramatically worse with concurrency.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The penalty for this mistake grows with the number of cores you add.&lt;/strong&gt; You will not find it on a laptop running a single-threaded test. You will find it when you deploy to a 32-core box and discover that scaling stops at four.&lt;/p&gt;

&lt;p&gt;The fix is not to avoid &lt;code&gt;Arc&lt;/code&gt;. It is to clone at the right granularity: &lt;strong&gt;once per task, not once per message.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="n"&gt;workers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;cfg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Arc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;clone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;      &lt;span class="c1"&gt;// one atomic increment, once&lt;/span&gt;
    &lt;span class="nn"&gt;thread&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;spawn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;move&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;inbox&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;        &lt;span class="c1"&gt;// borrow inside the loop&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One increment per thread instead of one per message. That is the difference between the two columns above.&lt;/p&gt;

&lt;p&gt;A habit that falls out of this: &lt;strong&gt;write functions that take &lt;code&gt;&amp;amp;T&lt;/code&gt;, not &lt;code&gt;Arc&amp;lt;T&amp;gt;&lt;/code&gt;.&lt;/strong&gt; If a function does not need to &lt;em&gt;keep&lt;/em&gt; the value after it returns, it does not need a reference count — it needs a borrow. Putting &lt;code&gt;Arc&amp;lt;T&amp;gt;&lt;/code&gt; in a signature is an instruction to every caller to perform an atomic read-modify-write, and most of them did not need to.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;code&gt;Weak&lt;/code&gt;, Cycles, and the Leak Rust Does Not Prevent
&lt;/h2&gt;

&lt;p&gt;Reference counting has one classic failure mode, and Rust does not save you from it.&lt;/p&gt;

&lt;p&gt;If two objects hold &lt;code&gt;Arc&lt;/code&gt;s to each other, they keep each other alive forever:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Parent ──Arc──► Child
   ▲              │
   └─────Arc──────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Neither count can reach zero, because each is held up by the other.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Node&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;'static&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;peer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Mutex&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;Arc&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="nb"&gt;Drop&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;Node&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;drop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"    Drop::drop ran for {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;two nodes pointing at each other with Arc:
    strong_count(a) = 2, strong_count(b) = 2
    ...leaving the scope now
    (nothing printed above? then neither destructor ran)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing printed. Both handles went out of scope, both counts fell from 2 to 1, neither reached zero, neither destructor ran, and that memory is gone for the life of the process. No &lt;code&gt;unsafe&lt;/code&gt;, no warning, no panic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rust's safety guarantees do not include "no leaks".&lt;/strong&gt; Leaking is safe. It is just not what you wanted.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Weak&amp;lt;T&amp;gt;&lt;/code&gt; is the fix. A &lt;code&gt;Weak&lt;/code&gt; points at the allocation without owning the value — it increments the weak count, not the strong one, so it cannot keep the value alive. Make the back-edge weak and the cycle breaks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;same shape, but the back-edge is a Weak:
    strong_count(a) = 1, weak_count(a) = 1
    ...leaving the scope now
    Drop::drop ran for B
    Drop::drop ran for A
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And a &lt;code&gt;Weak&lt;/code&gt; tells you honestly when the thing is gone:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;what a Weak sees after the last strong reference goes:
    while alive: w.upgrade() = Some(99)
    after drop:  w.upgrade() = None
    strong_count via Weak = 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;upgrade()&lt;/code&gt; is the entire point of &lt;code&gt;Weak&lt;/code&gt;: a fallible promotion back to an &lt;code&gt;Arc&lt;/code&gt; that returns &lt;code&gt;None&lt;/code&gt; rather than handing you a dangling pointer.&lt;/p&gt;

&lt;p&gt;That check is not free. From the cost table, &lt;code&gt;Weak::upgrade&lt;/code&gt; plus dropping the resulting &lt;code&gt;Arc&lt;/code&gt; costs &lt;strong&gt;18.01 ns&lt;/strong&gt; against 12.89 ns for a plain clone — because upgrade cannot be a blind increment. It has to atomically check that the strong count is non-zero &lt;em&gt;and&lt;/em&gt; increment it in one indivisible step, or it would lose the race against a concurrent final drop.&lt;/p&gt;

&lt;p&gt;Where this matters: parent/child trees, observer registries, caches holding handles to objects they must not keep alive, and graphs generally. &lt;strong&gt;If your object graph can contain a cycle, one direction must be &lt;code&gt;Weak&lt;/code&gt;.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Could Not Measure Here
&lt;/h2&gt;

&lt;p&gt;Two gaps, stated rather than papered over.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AArch64 code generation.&lt;/strong&gt; As above — no cross-target standard library, no reachable toolchain server. "The orderings are free" is a claim about x86-64 only.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Strong/weak false sharing.&lt;/strong&gt; The two counters sit at offsets 0 and 8, which I confirmed are inside the same 64-byte line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  strong at offset 0  -&amp;gt; 0x55c8d6339d60
  weak   at offset 8  -&amp;gt; 0x55c8d6339d68
  64-byte line of strong = 0x55c8d6339d40
  64-byte line of weak   = 0x55c8d6339d40
  same cache line?       true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In principle, then, a thread doing &lt;code&gt;downgrade&lt;/code&gt;/&lt;code&gt;upgrade&lt;/code&gt; should interfere with a thread doing &lt;code&gt;clone&lt;/code&gt;, even though they touch different words — that is what false sharing means. I built that benchmark and could not interpret the result.&lt;/p&gt;

&lt;p&gt;The problem: &lt;code&gt;downgrade&lt;/code&gt; is simply a more expensive operation than &lt;code&gt;clone&lt;/code&gt; — 18 ns against 13 ns even with no contention at all. My benchmark reported the slower of the two threads, so it was mostly measuring that difference rather than any interference between them. I could publish the numbers, but they would not mean what the heading claimed.&lt;/p&gt;

&lt;p&gt;Article 8 is about false sharing specifically and will do this properly, with matched operations. I would rather this article be shorter and correct.&lt;/p&gt;




&lt;h2&gt;
  
  
  Production Connection
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;API servers and gRPC services.&lt;/strong&gt; &lt;code&gt;Arc&amp;lt;AppState&amp;gt;&lt;/code&gt; is the standard pattern and it is a good one — until the state is cloned per &lt;em&gt;request&lt;/em&gt; rather than per connection or per worker. The 126x figure above is what that costs at two threads. Clone at task boundaries; borrow inside them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Databases, caches, vector stores.&lt;/strong&gt; &lt;code&gt;Arc&amp;lt;[u8]&amp;gt;&lt;/code&gt; or &lt;code&gt;Arc&amp;lt;str&amp;gt;&lt;/code&gt; for shared immutable pages, embeddings, or interned keys is excellent: reads scale perfectly and the payload is never copied. But mind the allocation table — 16 bytes of header means &lt;code&gt;Arc&lt;/code&gt; per &lt;em&gt;small&lt;/em&gt; object is heavy. Prefer one &lt;code&gt;Arc&lt;/code&gt; over a slab of values rather than an &lt;code&gt;Arc&lt;/code&gt; around each value.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Message brokers and Kafka-style consumers.&lt;/strong&gt; Passing &lt;code&gt;Arc&amp;lt;Message&amp;gt;&lt;/code&gt; down a pipeline is right — no copying regardless of payload size. Watch the fan-out: if N consumers each clone the same &lt;code&gt;Arc&lt;/code&gt; per message, that is N atomic RMWs on one cache line per message, and article 1's backpressure chain gains a new first link.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Low-latency and trading systems.&lt;/strong&gt; 13 ns uncontended, 61 ns contended, and the contended figure is &lt;em&gt;variable&lt;/em&gt; because it depends on which core last owned the line. If your tail-latency budget is in the hundreds of nanoseconds, a contended &lt;code&gt;Arc&lt;/code&gt; clone on the hot path is both a cost and a variance source. Clone once at setup; pass &lt;code&gt;&amp;amp;T&lt;/code&gt; thereafter.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Blockchain nodes and AI infrastructure.&lt;/strong&gt; Both are the same shape: one large immutable thing — a state snapshot, a loaded model, a mempool view — read by many workers. That is &lt;code&gt;Arc&lt;/code&gt; at its best, provided the clone count is proportional to &lt;em&gt;workers&lt;/em&gt;, not to &lt;em&gt;operations&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Everywhere.&lt;/strong&gt; If your object graph can contain a cycle, one direction must be &lt;code&gt;Weak&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The hypothesis mostly held. &lt;code&gt;Arc&lt;/code&gt; is a pointer to a heap header of two counters plus the value; &lt;code&gt;clone&lt;/code&gt; bumps a count without touching the data; the last drop frees. What the investigation added:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;An &lt;code&gt;Arc&amp;lt;T&amp;gt;&lt;/code&gt; is 8 bytes&lt;/strong&gt;, and so is &lt;code&gt;Option&amp;lt;Arc&amp;lt;T&amp;gt;&amp;gt;&lt;/code&gt;. The pointer aims at the &lt;em&gt;data&lt;/em&gt;; the counters live 16 bytes behind it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The header is 16 bytes&lt;/strong&gt; — 24 allocated for a &lt;code&gt;u32&lt;/code&gt;, so 83% overhead. An &lt;code&gt;align(64)&lt;/code&gt; payload pushes header overhead to &lt;strong&gt;64 bytes&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Arc::clone&lt;/code&gt; and &lt;code&gt;Rc::clone&lt;/code&gt; differ by exactly one thing&lt;/strong&gt;: &lt;code&gt;lock inc&lt;/code&gt; versus &lt;code&gt;inc&lt;/code&gt;. Three instructions each, plus an overflow guard that aborts at &lt;code&gt;isize::MAX&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Arc::drop&lt;/code&gt; is four instructions&lt;/strong&gt; in the common case. Destroying the value and freeing the memory happen in a separate function it jumps to only when the count hits zero.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;On x86-64 the memory orderings are free.&lt;/strong&gt; The &lt;code&gt;Acquire&lt;/code&gt; fence emits &lt;code&gt;#MEMBARRIER&lt;/code&gt; — a comment, not an instruction. The orderings still matter; they just do not cost anything on this architecture.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Uncontended: 12.89 ns&lt;/strong&gt; to clone and drop — &lt;strong&gt;2.8x&lt;/strong&gt; an &lt;code&gt;Rc&lt;/code&gt;, and &lt;strong&gt;29x&lt;/strong&gt; a plain deref at 0.44 ns.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Contended: about 4.5x worse&lt;/strong&gt;, and the penalty persists as threads increase. Read-only sharing, by contrast, costs nothing at any thread count.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Arc::strong_count&lt;/code&gt; is a snapshot, not a fact.&lt;/strong&gt; Four threads read 4, 3, 2, 2 from the same counter.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cloning per message instead of per task cost 126x at two threads&lt;/strong&gt;, and got worse with more cores — the worst possible property for a bug to have.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cycles of &lt;code&gt;Arc&lt;/code&gt; leak silently.&lt;/strong&gt; Safe, warning-free, permanent.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The thing I keep returning to is that &lt;code&gt;lock&lt;/code&gt; prefix. One prefix on one instruction is the entire difference between the single-threaded and the multi-threaded reference count. And the reason it is expensive has nothing to do with the instruction itself — it is that a cache line can only be owned by one core at a time.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Arc&lt;/code&gt; does not really cost you an atomic. It costs you &lt;strong&gt;exclusive ownership of a cache line, on every clone, contested with every other thread doing the same&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Next
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Article 3: Mutex vs RwLock in Rust — Benchmarking Real Contention.&lt;/strong&gt; We can now share ownership across threads, but everything shared so far has been immutable. The moment we want to &lt;em&gt;mutate&lt;/em&gt; shared state we need mutual exclusion — and the two obvious choices behave very differently under load.&lt;/p&gt;

&lt;p&gt;Note where &lt;code&gt;Arc&amp;lt;Mutex&amp;lt;T&amp;gt;&amp;gt;&lt;/code&gt; puts things: the lock sits in the same allocation as the reference count we just measured, quite possibly in the same cache line. The contention effects from this article do not go away when you add a lock. They compound.&lt;/p&gt;

&lt;p&gt;Then article 4 follows a blocked &lt;code&gt;Mutex&lt;/code&gt; into the kernel, and lands back on the same &lt;code&gt;futex&lt;/code&gt; syscall article 1 found inside &lt;code&gt;join()&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Appendix: Environment and Reproducibility
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kernel:  6.18.44-fc-v21 (x86_64)
distro:  Ubuntu 24.04.4 LTS
rustc:   1.95.0 (59807616e 2026-04-14)
glibc:   2.39-0ubuntu8.7
objdump: GNU binutils 2.42
cpu:     Intel(R) Xeon(R) Processor @ 2.80GHz, 2 cores
cache line: 64 bytes (getconf LEVEL1_DCACHE_LINESIZE)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;On comparing with article 1:&lt;/strong&gt; this is not the same host. Article 1 ran on a machine reporting 2.10 GHz; this one reports 2.80 GHz, and the container migrated mid-session. Every timing in &lt;em&gt;this&lt;/em&gt; article was re-run on the machine above so the numbers are internally consistent. Do not compare absolute nanoseconds across the two articles — ratios are comparable, raw times are not.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;File&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;exp1/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;the cargo project from experiment 1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;threads_arc.rs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;the same &lt;code&gt;Arc&lt;/code&gt; across four OS threads&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;threads_rc.rs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;the &lt;code&gt;Rc&lt;/code&gt; version, for the &lt;code&gt;Send&lt;/code&gt; compile error&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;layout.rs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;reads the strong/weak header directly; sizes and pointer identity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;alloc_log.rs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;logging &lt;code&gt;#[global_allocator]&lt;/code&gt;; real &lt;code&gt;Arc::new&lt;/code&gt; allocation sizes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;disasm.rs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;#[no_mangle]&lt;/code&gt; wrappers for arc_clone / arc_drop / rc_clone / rc_drop&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;orderings.rs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;#![no_std]&lt;/code&gt; isolation of the two atomic ops, for &lt;code&gt;--emit asm&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ops.rs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;per-operation cost table&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;bench3.rs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;contention: private vs shared vs read-only, median of 5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;realistic.rs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;clone-per-message vs borrow, across thread counts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cycle.rs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Arc&lt;/code&gt; cycle leak, &lt;code&gt;Weak&lt;/code&gt; back-edge, &lt;code&gt;upgrade()&lt;/code&gt; after drop&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;addrs.rs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;cache-line placement of the two counters&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;cargo run                                  &lt;span class="c"&gt;# experiment 1&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;rustc &lt;span class="nt"&gt;-O&lt;/span&gt; threads_arc.rs &lt;span class="nt"&gt;-o&lt;/span&gt; threads_arc &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; ./threads_arc
&lt;span class="nv"&gt;$ &lt;/span&gt;rustc &lt;span class="nt"&gt;-O&lt;/span&gt; &lt;span class="nt"&gt;-C&lt;/span&gt; &lt;span class="nv"&gt;panic&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;abort disasm.rs &lt;span class="nt"&gt;-o&lt;/span&gt; disasm
&lt;span class="nv"&gt;$ &lt;/span&gt;objdump &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--no-show-raw-insn&lt;/span&gt; &lt;span class="nt"&gt;-M&lt;/span&gt; intel disasm | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'/&amp;lt;arc_clone&amp;gt;:/,/^$/'&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;rustc &lt;span class="nt"&gt;-O&lt;/span&gt; &lt;span class="nt"&gt;--emit&lt;/span&gt; asm &lt;span class="nt"&gt;--target&lt;/span&gt; x86_64-unknown-linux-gnu orderings.rs &lt;span class="nt"&gt;-o&lt;/span&gt; -
&lt;span class="nv"&gt;$ MALLOC_ARENA_MAX&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 ./ops
&lt;span class="nv"&gt;$ MALLOC_ARENA_MAX&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 ./bench3
&lt;span class="nv"&gt;$ MALLOC_ARENA_MAX&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 ./realistic
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;MALLOC_ARENA_MAX=1&lt;/code&gt; is inherited from article 1: glibc otherwise gives each thread its own malloc arena, which distorts memory accounting. It does not affect the timings here, but it keeps the runs comparable.&lt;/p&gt;

&lt;p&gt;Caveats:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Two cores.&lt;/strong&gt; The contention numbers would likely be worse on a bigger machine. More cores means more competition for the line, and on a multi-socket server the two cores fighting over it may be on physically separate chips, which makes each transfer much more expensive.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Virtualised.&lt;/strong&gt; Timing under a hypervisor is noisier than bare metal, which is why the contention table is a median of five runs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;x86-64 only.&lt;/strong&gt; Everything about &lt;code&gt;lock&lt;/code&gt; prefixes and free fences is specific to this memory model.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The single-thread table is one run.&lt;/strong&gt; Re-running &lt;code&gt;ops.rs&lt;/code&gt; moves figures by roughly ±1% (&lt;code&gt;Arc::clone&lt;/code&gt; came out at 12.89 and 12.99 ns on consecutive runs). The contention table is a median because it is far noisier.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Layout poking is not an API.&lt;/strong&gt; The &lt;code&gt;sub(16)&lt;/code&gt; arithmetic works because &lt;code&gt;ArcInner&lt;/code&gt; is &lt;code&gt;repr(C)&lt;/code&gt; and I checked the source. It is fine for an experiment and wrong for real code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Addresses vary per run.&lt;/strong&gt; The pointer values in different sections come from different runs of different programs, so they will not match each other — only the &lt;em&gt;relationships&lt;/em&gt; within a single output block are meaningful.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Thanks for Reading
&lt;/h2&gt;

&lt;p&gt;If you got this far — thank you.&lt;/p&gt;

&lt;p&gt;Article 1 ended with me admitting I had the cost model backwards: I assumed thread &lt;em&gt;creation&lt;/em&gt; was the expensive part, and the measurements said &lt;em&gt;blocking&lt;/em&gt; was. This one had a smaller version of the same thing. I expected &lt;code&gt;Arc&lt;/code&gt;'s memory orderings to be where the cost lived, because that is the part everyone writes about. On x86-64 they are free, and the cost turned out to be somewhere much less glamorous: one cache line, and which core owns it.&lt;/p&gt;

&lt;p&gt;I also had the weak count wrong, and only found out by reading the bytes.&lt;/p&gt;

&lt;p&gt;If you spot something wrong here, I would much rather hear it now than leave it standing while eight more articles get built on top of it. And if you run the contention table on a machine with real core counts, I would like to see it — 4.5x on two cores is almost certainly the friendly version.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Next up — Article 3: Mutex vs RwLock in Rust: Benchmarking Real Contention.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/Parikalp-Bhardwaj" rel="noopener noreferrer"&gt;GITHUB_URL&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;LinkedIn: &lt;a href="https://www.linkedin.com/in/parikalp-bhardwaj/" rel="noopener noreferrer"&gt;LINKEDIN_URL&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>rust</category>
      <category>concurrency</category>
      <category>performance</category>
      <category>programming</category>
    </item>
    <item>
      <title>What Actually Happens When Rust Spawns an OS Thread?</title>
      <dc:creator>Parikalp Bhardwaj</dc:creator>
      <pubDate>Sat, 15 Aug 2026 12:38:06 +0000</pubDate>
      <link>https://dev.to/parikalp_bhardwaj_9e9d812/what-actually-happens-when-rust-spawns-an-os-thread-58n4</link>
      <guid>https://dev.to/parikalp_bhardwaj_9e9d812/what-actually-happens-when-rust-spawns-an-os-thread-58n4</guid>
      <description>&lt;p&gt;&lt;em&gt;Following &lt;code&gt;std::thread::spawn&lt;/code&gt; from Rust to Linux. Article 1 of **Below the Abstraction&lt;/em&gt;* — a series that takes everyday Rust abstractions and follows them down to the kernel. Every number below was measured on a real machine, not recalled.*&lt;/p&gt;




&lt;h2&gt;
  
  
  Why I'm Starting With Threads
&lt;/h2&gt;

&lt;p&gt;I have written a lot of Rust that creates threads. Backend services, gRPC servers, blockchain node components, async applications, background workers, infrastructure glue. &lt;code&gt;thread::spawn&lt;/code&gt; is one of those calls that becomes invisible after the first hundred times you type it.&lt;/p&gt;

&lt;p&gt;At some point that stopped being acceptable to me.&lt;/p&gt;

&lt;p&gt;I had used &lt;code&gt;thread::spawn&lt;/code&gt; many times before, but I had never actually verified what Linux saw when the call happened. I knew the shape of the answer — "it makes an OS thread" — but I could not have told you which syscall was issued, how much memory the call reserved, what the kernel scheduler now had to track, or why the same program behaves very differently at 4 threads and at 4,000.&lt;/p&gt;

&lt;p&gt;That gap matters more than it looks. Almost every hard problem I have hit in production — tail latency that appears only under load, memory that grows without a leak, a service that goes from fine to unusable somewhere between 500 and 2,000 concurrent connections — turned out to be a question about the layer below the one I was writing in.&lt;/p&gt;

&lt;p&gt;There is a second reason this exists. I like low-level work — it is the part of the stack where you can actually check the answer instead of arguing about it. And the fastest way I know to find out whether I really understand something is to try to explain it to someone else. Writing forces the gaps into the open: you can hand-wave your way through a conversation about threads, but you cannot hand-wave a syscall trace. So this series is a little bit me teaching, and mostly me learning in public.&lt;/p&gt;

&lt;p&gt;It has one rule, and it is the rule I want to apply to everything:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Don't just use the abstraction. Follow it down until you understand the system underneath it.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Threads come first because everything else in the series stands on them. &lt;code&gt;Arc&lt;/code&gt;, &lt;code&gt;Mutex&lt;/code&gt;, &lt;code&gt;RwLock&lt;/code&gt;, futexes, thread pools, Tokio's scheduler, &lt;code&gt;epoll&lt;/code&gt;, async tasks — all of them are, in the end, statements about what an OS thread is, what it costs, and when the kernel takes it away from you. You cannot meaningfully say "a Tokio task is cheaper than a thread" until you have measured what a thread costs.&lt;/p&gt;

&lt;p&gt;This article is the measurement.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Low-Level Knowledge Matters
&lt;/h2&gt;

&lt;p&gt;There is a version of this argument that is just nostalgia — "real engineers know assembly." That is not the argument. The argument is that modern backend and distributed systems fail in ways that are only legible one or two layers down.&lt;/p&gt;

&lt;p&gt;Here is the stack this series keeps returning to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Distributed System
        ↓
Network Communication
        ↓
Sockets
        ↓
Async Runtime / Threads
        ↓
Synchronization
        ↓
Memory
        ↓
System Calls
        ↓
Kernel
        ↓
CPU + Hardware
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You write at the top. You get paged at the bottom.&lt;/p&gt;

&lt;h3&gt;
  
  
  Performance
&lt;/h3&gt;

&lt;p&gt;Every high-level abstraction eventually decomposes into a small set of concrete, countable things:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;allocations
syscalls
locks
atomics
cache misses
context switches
network packets
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An API surface tells you nothing about how many of each you just bought. &lt;code&gt;Vec::push&lt;/code&gt; is amortized O(1) and also, sometimes, an &lt;code&gt;mmap&lt;/code&gt;. &lt;code&gt;thread::spawn&lt;/code&gt; is one line and also, as we are about to see, a &lt;code&gt;mmap&lt;/code&gt;, an &lt;code&gt;mprotect&lt;/code&gt;, a &lt;code&gt;clone3&lt;/code&gt;, and a new schedulable entity that the kernel must now consider on every scheduling decision for the life of that thread.&lt;/p&gt;

&lt;p&gt;You cannot reason about performance only from the API surface.&lt;/p&gt;

&lt;h3&gt;
  
  
  Concurrency
&lt;/h3&gt;

&lt;p&gt;A line like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nb"&gt;Arc&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Mutex&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;looks like a type. It is closer to a system:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;atomic reference counts
memory ordering
lock state
parking
waking
kernel interaction
cache coherence
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At low contention none of that is visible. At high contention it is the only thing that is visible — the difference between a lock that spins briefly in userspace and one that puts a thread to sleep in the kernel shows up as a CPU utilization graph that makes no sense, or a p99 that is 40x the p50. Articles 2, 3 and 4 take that apart.&lt;/p&gt;

&lt;h3&gt;
  
  
  Async Systems
&lt;/h3&gt;

&lt;p&gt;Most Rust developers write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nn"&gt;tokio&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;spawn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;long before they understand:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Future
Poll
Waker
runtime scheduler
reactor
epoll
kernel readiness notifications
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I think async Rust is genuinely confusing if you meet it first. It makes far more sense in the other order: OS threads, blocking, scheduling, and I/O readiness first — then async as a specific answer to a specific cost. That is why this series does not start with Tokio.&lt;/p&gt;

&lt;h3&gt;
  
  
  Distributed Systems
&lt;/h3&gt;

&lt;p&gt;This is the part I care most about, and it is the part that is usually skipped.&lt;/p&gt;

&lt;p&gt;Distributed systems problems become local systems problems, almost always. A latency graph in Grafana is a distributed artifact; the thing producing it is a single machine doing something specific.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Request latency
    ↓
network wait
    ↓
socket readiness
    ↓
runtime scheduling
    ↓
task wakeup
    ↓
lock acquisition
    ↓
cache/database access
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or, the shape I have personally chased more than once:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Kafka consumer slowdown
        ↓
backpressure
        ↓
queue growth
        ↓
memory growth
        ↓
scheduler pressure
        ↓
latency spikes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every arrow in those chains is a local-machine phenomenon. "The cluster is slow" is a summary; the mechanism is a scheduler, a lock, a page fault, or a socket buffer.&lt;/p&gt;

&lt;p&gt;A distributed system is not something separate from operating systems. It is &lt;strong&gt;multiple operating systems communicating over an unreliable network&lt;/strong&gt;. If you do not understand what one machine does under load, a hundred of them will not be easier.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Series Roadmap
&lt;/h2&gt;

&lt;p&gt;Each article is a single investigation, and each one depends on the one before it.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;What Actually Happens When Rust Spawns an OS Thread?&lt;/strong&gt; — establish what a thread &lt;em&gt;is&lt;/em&gt;, and what it costs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What Does &lt;code&gt;Arc&lt;/code&gt; Actually Do? Atomic Reference Counting Under the Hood&lt;/strong&gt; — now that threads share memory, how is ownership shared safely?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mutex vs RwLock in Rust: Benchmarking Real Contention&lt;/strong&gt; — shared memory needs mutual exclusion; which primitive, and when?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What Happens When a Rust Mutex Blocks? Parking, Waking, and Futexes&lt;/strong&gt; — the moment a lock stops being userspace-only and calls the kernel.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Building a Thread Pool in Rust From Scratch&lt;/strong&gt; — the direct consequence of article 1's cost measurements.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Why Tokio Tasks Aren't Threads: 10,000 Tasks vs OS Threads&lt;/strong&gt; — the same workload, re-measured against the numbers from article 1.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;From &lt;code&gt;epoll&lt;/code&gt; to Tokio: What Happens When Rust Waits for Network I/O?&lt;/strong&gt; — where the runtime actually blocks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;False Sharing in Rust: CPU Cache Lines and Multithreaded Performance&lt;/strong&gt; — going below the kernel, into the hardware.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Building a Concurrent LRU Cache in Rust&lt;/strong&gt; — everything above, applied to one realistic data structure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tracing Rust &lt;code&gt;async/await&lt;/code&gt; All the Way Down to &lt;code&gt;epoll&lt;/code&gt;&lt;/strong&gt; — the full path, end to end.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The progression is deliberate: threads → shared state → synchronization → blocking → pooling → async → I/O readiness → hardware → applied → full trace.&lt;/p&gt;




&lt;h2&gt;
  
  
  Series Philosophy
&lt;/h2&gt;

&lt;p&gt;Every article follows the same investigation model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Question
   ↓
Hypothesis
   ↓
Small Rust Program
   ↓
Run It
   ↓
Inspect Linux
   ↓
Measure
   ↓
Read Source Code
   ↓
Explain Internals
   ↓
Stress the Design
   ↓
Production Connection
   ↓
Conclusion
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And one hard constraint: &lt;strong&gt;nothing is invented.&lt;/strong&gt; No benchmark numbers, memory figures, syscall traces, or scheduler behaviour is written from memory or plausibility. Every number and every trace line in this article was produced by running the programs shown, on the machine described in the appendix, and pasted in. Where a result surprised me or contradicted what I expected, I say so rather than smoothing it over — a couple of the measurements below did exactly that.&lt;/p&gt;

&lt;p&gt;Your numbers will differ. The &lt;em&gt;shapes&lt;/em&gt; should not.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Rust for This Series
&lt;/h2&gt;

&lt;p&gt;Rust is unusually good for this kind of work, and it is worth being specific about why rather than cheerleading.&lt;/p&gt;

&lt;p&gt;What helps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ownership and borrowing&lt;/strong&gt; make the memory model explicit at the type level, so "who owns this buffer across threads" is a compile-time question rather than a debugging session.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Send&lt;/code&gt; and &lt;code&gt;Sync&lt;/code&gt;&lt;/strong&gt; encode thread-safety as traits. When you study synchronization primitives, having the safety rules written in the type system is a genuine teaching aid.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No hidden runtime.&lt;/strong&gt; &lt;code&gt;std::thread::spawn&lt;/code&gt; is a thin wrapper over the platform's native threads. There is no green-thread layer or VM between you and &lt;code&gt;clone3&lt;/code&gt;, which is exactly what you want when the goal is to see the syscall.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Explicit atomics with explicit orderings&lt;/strong&gt; (&lt;code&gt;Relaxed&lt;/code&gt;, &lt;code&gt;Acquire&lt;/code&gt;, &lt;code&gt;Release&lt;/code&gt;, &lt;code&gt;SeqCst&lt;/code&gt;) force you to state memory ordering rather than inherit it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero-cost abstractions&lt;/strong&gt; mean the assembly usually corresponds to the source in a way you can follow.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;First-class FFI&lt;/strong&gt; lets you drop to &lt;code&gt;libc&lt;/code&gt; and call the raw syscall when you want to compare.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What genuinely hurts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Compiler complexity.&lt;/strong&gt; The borrow checker is a real cost when you are prototyping a data structure whose whole point is aliasing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Async type complexity.&lt;/strong&gt; Deeply nested &lt;code&gt;impl Future&lt;/code&gt; types, &lt;code&gt;Pin&lt;/code&gt;, and lifetime errors in async code are hard, and error messages in that area are still rough.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Native dependencies and build friction.&lt;/strong&gt; Anything touching C libraries brings the usual pain, plus &lt;code&gt;bindgen&lt;/code&gt;/&lt;code&gt;cc&lt;/code&gt; on top.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;unsafe&lt;/code&gt; and FFI boundaries.&lt;/strong&gt; The moment you go low-level you lose the guarantees that made Rust attractive, and you are back to C-level discipline without C's decades of tooling defaults.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ecosystem maturity for systems tooling.&lt;/strong&gt; C and C++ have a longer tail of profilers, sanitizers, and debugger integrations that "just work". Rust's are good and improving, but not equal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Debugging low-level async.&lt;/strong&gt; A stack trace through a poll chain is much less informative than a stack trace through blocking calls. This is a real regression in observability, and article 10 will have to deal with it head-on.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I am using Rust here because it is the best available lens on these mechanisms, not because it removes them.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Question
&lt;/h2&gt;

&lt;p&gt;What actually happens after this line?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;thread&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;spawn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Specifically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which syscall is issued, and with which arguments?&lt;/li&gt;
&lt;li&gt;What memory is reserved before the syscall, and by whom?&lt;/li&gt;
&lt;li&gt;What does Linux create, and how does it name and track it?&lt;/li&gt;
&lt;li&gt;What does it cost — in time, in virtual memory, in resident memory?&lt;/li&gt;
&lt;li&gt;What changes when there are more runnable threads than CPU cores?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Hypothesis
&lt;/h2&gt;

&lt;p&gt;My starting hypothesis, before running anything:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;std::thread::spawn&lt;/code&gt; creates a native OS thread. Rust's standard library delegates to &lt;code&gt;pthread_create&lt;/code&gt;, which allocates a stack, then asks the kernel to create a new task sharing the caller's address space. Linux tracks that task independently and schedules it independently. The thread is not free: it costs a stack-sized virtual memory reservation and a kernel-visible schedulable entity.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is a hypothesis, not an answer. Let's check every clause of it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Experiment
&lt;/h2&gt;

&lt;p&gt;The smallest program that spawns exactly one thread:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// hello_thread.rs&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;thread&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;thread&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;spawn&lt;/span&gt;&lt;span class="p"&gt;(||&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"hello from a spawned thread"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="nf"&gt;.join&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"main thread done"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Built with &lt;code&gt;rustc -O -g hello_thread.rs -o hello_thread&lt;/code&gt;, then traced:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;strace &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; ./hello_thread
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The full syscall summary for the whole process life is 89 syscalls. The interesting ones:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  0.00    0.000000           0        16           mmap
  0.00    0.000000           0         8           mprotect
  0.00    0.000000           0         5           munmap
  0.00    0.000000           0         2           gettid
  0.00    0.000000           0         1           futex
  0.00    0.000000           0         2           set_robust_list
  0.00    0.000000           0         2           rseq
  0.00    0.000000           0         1           clone3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One &lt;code&gt;clone3&lt;/code&gt;. That is the whole thread creation, from the kernel's point of view. Everything else around it is preparation and cleanup.&lt;/p&gt;

&lt;p&gt;Note what is &lt;em&gt;not&lt;/em&gt; there: there is no &lt;code&gt;thread_create&lt;/code&gt; syscall on Linux. There is no separate "thread" object. Threads and processes are the same kernel primitive — a &lt;code&gt;task_struct&lt;/code&gt; — created by the same syscall family, differing only in which resources they share. That is the first thing the trace teaches, and it reframes everything that follows.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the Trace Actually Shows
&lt;/h2&gt;

&lt;p&gt;Here is the relevant window from the full trace (&lt;code&gt;strace -f&lt;/code&gt;), with the process ID &lt;code&gt;3616&lt;/code&gt; as the main thread and &lt;code&gt;3617&lt;/code&gt; as the spawned thread. This is verbatim output, trimmed only to the region around the spawn:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;3616  mmap(NULL, 2101248, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_STACK, -1, 0) = 0x7ff8a9bff000
3616  mprotect(0x7ff8a9c00000, 2097152, PROT_READ|PROT_WRITE) = 0
3616  rt_sigprocmask(SIG_BLOCK, ~[], [], 8) = 0
3616  clone3({flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM
              |CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID,
              child_tid=0x7ff8a9dff990, parent_tid=0x7ff8a9dff990, exit_signal=0,
              stack=0x7ff8a9bff000, stack_size=0x1fff40, tls=0x7ff8a9dff6c0}
             =&amp;gt; {parent_tid=[3617]}, 88) = 3617
3616  rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
3616  futex(0x7ff8a9dff990, FUTEX_WAIT_BITSET|FUTEX_CLOCK_REALTIME, 3617, NULL,
            FUTEX_BITSET_MATCH_ANY &amp;lt;unfinished ...&amp;gt;
3617  rseq(0x7ff8a9dfffe0, 0x20, 0, 0x53053053) = 0
3617  set_robust_list(0x7ff8a9dff9a0, 24) = 0
3617  rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
3617  mmap(NULL, 134217728, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7ff8a1a00000
3617  munmap(0x7ff8a1a00000, 39845888)  = 0
3617  munmap(0x7ff8a8000000, 27262976)  = 0
3617  mprotect(0x7ff8a4000000, 135168, PROT_READ|PROT_WRITE) = 0
3617  gettid()                          = 3617
3617  write(1, "hello from a spawned thread\n", 28) = 28
3617  madvise(0x7ff8a9bff000, 2076672, MADV_DONTNEED) = 0
3617  exit(0)                           = ?
3616  &amp;lt;... futex resumed&amp;gt;)              = 0
3616  write(1, "main thread done\n", 17) = 17
3616  exit_group(0)                     = ?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is a lot in there. Let's go line by line.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. The stack is allocated in userspace, before the kernel is involved
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mmap(NULL, 2101248, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_STACK, -1, 0)
mprotect(0x7ff8a9c00000, 2097152, PROT_READ|PROT_WRITE)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;2101248&lt;/code&gt; bytes is &lt;code&gt;2 MiB + 4096&lt;/code&gt;. The whole region is mapped &lt;code&gt;PROT_NONE&lt;/code&gt; — no access at all — and then all of it &lt;em&gt;except the first page&lt;/em&gt; is flipped to read/write.&lt;/p&gt;

&lt;p&gt;That leftover 4 KiB at the bottom is the &lt;strong&gt;guard page&lt;/strong&gt;. It is a page with no permissions sitting immediately below the stack, so that a stack that grows too far hits an unmapped page and faults instead of silently scribbling over whatever mapping happens to be next in the address space.&lt;/p&gt;

&lt;p&gt;This is worth pausing on: &lt;strong&gt;the kernel did not allocate the thread stack.&lt;/strong&gt; glibc did, in userspace, with an ordinary anonymous &lt;code&gt;mmap&lt;/code&gt;, before &lt;code&gt;clone3&lt;/code&gt; was ever called. The kernel is handed a pointer to memory that already exists. A "thread stack" is not a kernel concept — it is just anonymous memory that a thread happens to use as a stack.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;MAP_NORESERVE&lt;/code&gt; is not set here, but the mapping is &lt;code&gt;PROT_NONE&lt;/code&gt; until &lt;code&gt;mprotect&lt;/code&gt; and untouched afterwards, so no physical pages are committed yet. We will measure exactly that in a moment.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Why 2 MiB?
&lt;/h3&gt;

&lt;p&gt;Not because Linux says so. The main thread's stack limit on this machine is 8 MiB:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;prlimit64(0, RLIMIT_STACK, NULL, {rlim_cur=8192*1024, rlim_max=RLIM64_INFINITY}) = 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The 2 MiB is Rust's choice. In the standard library's Unix thread implementation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[cfg(not(any(&lt;/span&gt;
    &lt;span class="nd"&gt;target_os&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"l4re"&lt;/span&gt;&lt;span class="nd"&gt;,&lt;/span&gt;
    &lt;span class="nd"&gt;target_os&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"vxworks"&lt;/span&gt;&lt;span class="nd"&gt;,&lt;/span&gt;
    &lt;span class="nd"&gt;target_os&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"espidf"&lt;/span&gt;&lt;span class="nd"&gt;,&lt;/span&gt;
    &lt;span class="nd"&gt;target_os&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"nuttx"&lt;/span&gt;
&lt;span class="nd"&gt;)))]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;DEFAULT_MIN_STACK_SIZE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can confirm the plumbing empirically rather than trusting the constant. &lt;code&gt;RUST_MIN_STACK&lt;/code&gt; should change the size of that &lt;code&gt;mmap&lt;/code&gt;, and it does:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;RUST_MIN_STACK&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1048576 strace &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;trace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;mmap,clone3 ./hello_thread
&lt;span class="go"&gt;mmap(NULL, 1052672, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_STACK, ...) 
clone3({... stack_size=0xfff40 ...})

&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;RUST_MIN_STACK&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;8388608 strace &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;trace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;mmap,clone3 ./hello_thread
&lt;span class="go"&gt;mmap(NULL, 8392704, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_STACK, ...)
clone3({... stack_size=0x7fff40 ...})
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;1052672 = 1 MiB + 4096&lt;/code&gt;. &lt;code&gt;8392704 = 8 MiB + 4096&lt;/code&gt;. Same guard page, different stack. The request travels from a Rust env var, through &lt;code&gt;Builder&lt;/code&gt;, into &lt;code&gt;pthread_attr_setstacksize&lt;/code&gt;, and out as the size of an &lt;code&gt;mmap&lt;/code&gt;. That is the whole chain, visible in one command.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. &lt;code&gt;clone3&lt;/code&gt; is the actual thread creation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;clone3({flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM
        |CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID, ...}) = 3617
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each flag is a decision about what the new task shares with the old one:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Flag&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;th&gt;Consequence&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CLONE_VM&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;share the address space (&lt;code&gt;mm_struct&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;this is what makes it a &lt;em&gt;thread&lt;/em&gt; and not a process&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CLONE_FS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;share filesystem info (cwd, umask, root)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;chdir&lt;/code&gt; in one thread affects all&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CLONE_FILES&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;share the file descriptor table&lt;/td&gt;
&lt;td&gt;fd 7 means the same socket in every thread&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CLONE_SIGHAND&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;share signal handlers&lt;/td&gt;
&lt;td&gt;one handler table per process&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CLONE_THREAD&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;join the same thread group&lt;/td&gt;
&lt;td&gt;same PID/TGID; &lt;code&gt;getpid()&lt;/code&gt; matches&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CLONE_SYSVSEM&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;share System V semaphore undo state&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CLONE_SETTLS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;install the given TLS pointer&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;thread_local!&lt;/code&gt; needs this&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CLONE_PARENT_SETTID&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;write the new TID into the parent's memory&lt;/td&gt;
&lt;td&gt;the parent learns the child's TID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CLONE_CHILD_CLEARTID&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;clear that word on exit &lt;strong&gt;and futex-wake it&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;this is how &lt;code&gt;join()&lt;/code&gt; works&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;exit_signal=0&lt;/code&gt; means the parent process is not sent &lt;code&gt;SIGCHLD&lt;/code&gt; when this task dies, because it is a thread, not a child process.&lt;/p&gt;

&lt;p&gt;The first four or five flags are the entire difference between &lt;code&gt;fork&lt;/code&gt; and "spawn a thread". A process is the same call with fewer sharing flags. Once you have seen this, "threads share memory, processes don't" stops being a rule you memorised and becomes an argument you passed.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. &lt;code&gt;join()&lt;/code&gt; is a futex, and the kernel does the wake
&lt;/h3&gt;

&lt;p&gt;Look at the pairing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;3616  futex(0x7ff8a9dff990, FUTEX_WAIT_BITSET|FUTEX_CLOCK_REALTIME, 3617, ...)
...
3617  exit(0)
3616  &amp;lt;... futex resumed&amp;gt;) = 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The address &lt;code&gt;0x7ff8a9dff990&lt;/code&gt; is exactly the &lt;code&gt;child_tid&lt;/code&gt; pointer passed to &lt;code&gt;clone3&lt;/code&gt;. &lt;code&gt;CLONE_CHILD_CLEARTID&lt;/code&gt; told the kernel: when this task exits, zero that word and perform a futex wake on it. The parent then simply waits on that futex with the expected value &lt;code&gt;3617&lt;/code&gt; (the child's TID).&lt;/p&gt;

&lt;p&gt;So &lt;code&gt;handle.join()&lt;/code&gt; compiles down to: &lt;em&gt;sleep on a futex until the kernel clears the child's TID&lt;/em&gt;. There is no polling. There is no "thread finished" callback. It is one memory word and one kernel wait queue.&lt;/p&gt;

&lt;p&gt;That single &lt;code&gt;futex&lt;/code&gt; line is the seed of article 4. &lt;code&gt;Mutex&lt;/code&gt;, &lt;code&gt;Condvar&lt;/code&gt;, &lt;code&gt;join&lt;/code&gt;, channel blocking, and Tokio's own parking all end up at the same syscall.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. The child does more work than you'd expect before running your closure
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;3617  rseq(0x7ff8a9dfffe0, 0x20, 0, 0x53053053) = 0
3617  set_robust_list(0x7ff8a9dff9a0, 24) = 0
3617  mmap(NULL, 134217728, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7ff8a1a00000
3617  munmap(0x7ff8a1a00000, 39845888)  = 0
3617  munmap(0x7ff8a8000000, 27262976)  = 0
3617  mprotect(0x7ff8a4000000, 135168, PROT_READ|PROT_WRITE) = 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;rseq&lt;/code&gt; registers restartable sequences (used by glibc for fast per-CPU operations). &lt;code&gt;set_robust_list&lt;/code&gt; registers the list the kernel walks if this thread dies while holding a robust mutex.&lt;/p&gt;

&lt;p&gt;Then something bigger: a &lt;strong&gt;128 MiB &lt;code&gt;PROT_NONE&lt;/code&gt; reservation&lt;/strong&gt;, two &lt;code&gt;munmap&lt;/code&gt;s to trim it down to a 64 MiB aligned region, and an &lt;code&gt;mprotect&lt;/code&gt; of 132 KiB.&lt;/p&gt;

&lt;p&gt;That is glibc's malloc creating a &lt;strong&gt;new per-thread arena&lt;/strong&gt;. The first time a new thread allocates, glibc may give it its own arena to avoid contention on the main arena's lock. The 128 MiB &lt;code&gt;mmap&lt;/code&gt; is a reservation of address space, not memory — it is &lt;code&gt;PROT_NONE&lt;/code&gt;, so not a single physical page is committed.&lt;/p&gt;

&lt;p&gt;I did not expect this to show up in a program whose thread only calls &lt;code&gt;println!&lt;/code&gt;. It matters, because it shows up in virtual memory accounting in a way that looks alarming and isn't — as the next section demonstrates.&lt;/p&gt;

&lt;p&gt;Also note:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;3617  exit(0)
3616  exit_group(0)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The thread exits with &lt;code&gt;exit&lt;/code&gt;, which terminates one task. The main thread ends with &lt;code&gt;exit_group&lt;/code&gt;, which terminates the whole thread group. Same distinction, visible in the trace.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Linux Sees
&lt;/h2&gt;

&lt;p&gt;A trace shows the transition. &lt;code&gt;/proc&lt;/code&gt; shows the steady state. This program spawns four named threads and sleeps:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// four_threads.rs&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;thread&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;time&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;handles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Vec&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;thread&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Builder&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="nf"&gt;.name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"worker-{i}"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="nf"&gt;.spawn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;move&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nn"&gt;thread&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_secs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
                &lt;span class="n"&gt;i&lt;/span&gt;
            &lt;span class="p"&gt;})&lt;/span&gt;
            &lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;handles&lt;/span&gt;&lt;span class="nf"&gt;.push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"pid = {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;process&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;handles&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="nf"&gt;.join&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While it runs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; /proc/&lt;span class="nv"&gt;$PID&lt;/span&gt;/task
&lt;span class="go"&gt;5481  5483  5484  5485  5486

&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for &lt;/span&gt;t &lt;span class="k"&gt;in&lt;/span&gt; /proc/&lt;span class="nv"&gt;$PID&lt;/span&gt;/task/&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s2"&gt;"%s comm=%-12s State=%s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;basename&lt;/span&gt; &lt;span class="nv"&gt;$t&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="nv"&gt;$t&lt;/span&gt;/comm&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'/^State:/{print $2,$3}'&lt;/span&gt; &lt;span class="nv"&gt;$t&lt;/span&gt;/status&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;done&lt;/span&gt;
&lt;span class="go"&gt;5481  comm=four_threads State=S (sleeping)
5483  comm=worker-0     State=S (sleeping)
5484  comm=worker-1     State=S (sleeping)
5485  comm=worker-2     State=S (sleeping)
5486  comm=worker-3     State=S (sleeping)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Five kernel tasks. Each has its own TID, its own state, its own scheduler accounting. &lt;code&gt;thread::Builder::name()&lt;/code&gt; is not just a Rust-side label — it reaches &lt;code&gt;/proc/&amp;lt;tid&amp;gt;/comm&lt;/code&gt;, which means it shows up in &lt;code&gt;ps&lt;/code&gt;, &lt;code&gt;top&lt;/code&gt;, &lt;code&gt;perf&lt;/code&gt;, and every kernel-level tool. That is a free observability win that a lot of Rust code leaves on the table.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ps&lt;/code&gt; agrees:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ps &lt;span class="nt"&gt;-o&lt;/span&gt; pid,tid,psr,pcpu,stat,comm &lt;span class="nt"&gt;-L&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="nv"&gt;$PID&lt;/span&gt;
&lt;span class="go"&gt;  PID   TID PSR %CPU STAT COMMAND
27548 27548   0  0.0 Sl   four_threads
27548 27549   1  0.0 Sl   worker-0
27548 27550   0  0.0 Sl   worker-1
27548 27551   1  0.0 Sl   worker-2
27548 27552   0  0.0 Sl   worker-3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One PID, five TIDs, and &lt;code&gt;PSR&lt;/code&gt; shows the kernel has already spread them across both CPUs.&lt;/p&gt;

&lt;p&gt;From a worker's own status file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'^(Name|State|Tgid|Pid|Threads|Cpus_allowed_list)'&lt;/span&gt; /proc/&lt;span class="nv"&gt;$PID&lt;/span&gt;/task/27552/status
&lt;span class="go"&gt;Name:   worker-3
State:  S (sleeping)
Tgid:   27548
Pid:    27552
Threads:    5
Cpus_allowed_list:  0-1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Pid: 27552&lt;/code&gt;, &lt;code&gt;Tgid: 27548&lt;/code&gt;. Inside the kernel, "PID" means the task ID and "TGID" means what userspace calls the process ID. &lt;code&gt;getpid()&lt;/code&gt; returns the TGID; &lt;code&gt;gettid()&lt;/code&gt; returns the PID. This is why the trace showed &lt;code&gt;gettid()&lt;/code&gt; — the Rust runtime wants the &lt;em&gt;task&lt;/em&gt; identity, not the process identity.&lt;/p&gt;

&lt;h3&gt;
  
  
  Confirming &lt;code&gt;CLONE_VM&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The claim "threads share an address space" is testable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;diff &amp;lt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /proc/&lt;span class="nv"&gt;$PID&lt;/span&gt;/task/&lt;span class="nv"&gt;$PID&lt;/span&gt;/maps&lt;span class="o"&gt;)&lt;/span&gt; &amp;lt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /proc/&lt;span class="nv"&gt;$PID&lt;/span&gt;/task/&lt;span class="nv"&gt;$TID&lt;/span&gt;/maps&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="go"&gt;    &amp;amp;&amp;amp; echo "identical"
identical
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Byte-identical memory maps for two different tasks. One &lt;code&gt;mm_struct&lt;/code&gt;, five tasks pointing at it. That is &lt;code&gt;CLONE_VM&lt;/code&gt;, observed rather than asserted.&lt;/p&gt;

&lt;h3&gt;
  
  
  The stacks, and their guard pages
&lt;/h3&gt;

&lt;p&gt;Dumping the anonymous mappings with their neighbours:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;7f3b0abfc000-7f3b0abfd000 ---p 00000000 00:00 0      4 kB ---p   (guard)
7f3b0abfd000-7f3b0adfd000 rw-p 00000000 00:00 0   2048 kB rw-p   (thread stack)

7f3b0adfd000-7f3b0adfe000 ---p 00000000 00:00 0      4 kB ---p   (guard)
7f3b0adfe000-7f3b0affe000 rw-p 00000000 00:00 0   2048 kB rw-p   (thread stack)

7f3b0affe000-7f3b0afff000 ---p 00000000 00:00 0      4 kB ---p   (guard)
7f3b0afff000-7f3b0b1ff000 rw-p 00000000 00:00 0   2048 kB rw-p   (thread stack)

7f3b0b1ff000-7f3b0b200000 ---p 00000000 00:00 0      4 kB ---p   (guard)
7f3b0b200000-7f3b0b400000 rw-p 00000000 00:00 0   2048 kB rw-p   (thread stack)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Four threads, four 2 MiB stacks, four 4 KiB &lt;code&gt;---p&lt;/code&gt; guard pages, laid out back to back. Note the accounting consequence: &lt;strong&gt;each thread costs two VMA entries&lt;/strong&gt;, not one. That will matter when we look at limits.&lt;/p&gt;

&lt;h3&gt;
  
  
  The guard page, doing its job
&lt;/h3&gt;

&lt;p&gt;A thread with a deliberately small stack and unbounded recursion:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// overflow.rs&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;thread&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;recurse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;pad&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;             &lt;span class="c1"&gt;// 8 KiB of stack per frame&lt;/span&gt;
    &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;hint&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;black_box&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;pad&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;recurse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;pad&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;thread&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Builder&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;.name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"deep"&lt;/span&gt;&lt;span class="nf"&gt;.into&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="nf"&gt;.stack_size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;       &lt;span class="c1"&gt;// deliberately small: 64 KiB&lt;/span&gt;
        &lt;span class="nf"&gt;.spawn&lt;/span&gt;&lt;span class="p"&gt;(||&lt;/span&gt; &lt;span class="nf"&gt;recurse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1_000_000&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"join result: {:?}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="nf"&gt;.join&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.is_err&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;./overflow
&lt;span class="go"&gt;
thread 'deep' (27182) has overflowed its stack
fatal runtime error: stack overflow, aborting
Aborted
&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$?&lt;/span&gt;
&lt;span class="go"&gt;134
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the signals, from strace:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[pid 27928] --- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_ACCERR, si_addr=0x7ff67a257cc0} ---
[pid 27928] --- SIGABRT {si_signo=SIGABRT, si_code=SI_TKILL, si_pid=27927, si_uid=0} ---
[pid 27928] +++ killed by SIGABRT +++
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;SEGV_ACCERR&lt;/code&gt; — an access permission error, not &lt;code&gt;SEGV_MAPERR&lt;/code&gt;. The address is mapped; the thread simply had no permission to touch it. That is the &lt;code&gt;PROT_NONE&lt;/code&gt; guard page.&lt;/p&gt;

&lt;p&gt;This also explains the &lt;code&gt;sigaltstack&lt;/code&gt; calls that appear near the top of the trace for &lt;em&gt;every&lt;/em&gt; thread:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mmap(NULL, 16048, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_STACK, -1, 0)
mprotect(0x7ff8aa0b9000, 4096, PROT_NONE)
sigaltstack({ss_sp=0x7ff8aa0ba000, ss_flags=0, ss_size=11952}, NULL)
rt_sigaction(SIGSEGV, {sa_handler=0x560dd7ce53a0, ..., sa_flags=...|SA_ONSTACK|SA_SIGINFO}, ...)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rust installs a &lt;code&gt;SIGSEGV&lt;/code&gt; handler with &lt;code&gt;SA_ONSTACK&lt;/code&gt; and gives every thread a small alternate signal stack — with its own guard page. It has to: when you overflow a stack you cannot run a signal handler on that stack. Roughly 16 KiB per thread of extra mapping buys you a readable "has overflowed its stack" message instead of an unexplained segfault.&lt;/p&gt;

&lt;p&gt;An engineering consequence that costs nothing to apply: &lt;strong&gt;the message names the thread.&lt;/strong&gt; &lt;code&gt;thread 'deep' (27182) has overflowed its stack&lt;/code&gt; is dramatically more useful in a production log than &lt;code&gt;thread '&amp;lt;unnamed&amp;gt;'&lt;/code&gt;. Name your threads.&lt;/p&gt;




&lt;h2&gt;
  
  
  What a Thread Actually Costs
&lt;/h2&gt;

&lt;p&gt;Now the part that changes how you design systems.&lt;/p&gt;

&lt;h3&gt;
  
  
  Virtual memory
&lt;/h3&gt;

&lt;p&gt;This program spawns batches of parked threads and reads its own &lt;code&gt;/proc/self/status&lt;/code&gt; at each step:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// cost.rs (excerpt)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0usize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4000&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ... spawn up to `target` live threads, wait until all are parked ...&lt;/span&gt;
    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{:&amp;gt;8} {:&amp;gt;12} {:&amp;gt;10} {:&amp;gt;8}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
             &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;stat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"VmSize:"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;stat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"VmRSS:"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;stat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Threads:"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Default run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;live-thread footprint (kB from /proc/self/status)
 threads       VmSize      VmRSS  Threads
       0        70856       2408        1
     100      1193108       3488      101
     500      2020572       7312      501
    1000      3054836      12108     1001
    2000      5123528      21688     2001
    4000      9260740      40868     4001
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The jump from 0 to 100 threads is over a &lt;strong&gt;gigabyte&lt;/strong&gt; of virtual memory, which is far more than 100 x 2 MiB. That is the malloc arena behaviour from the trace: glibc creates up to &lt;code&gt;8 * ncores&lt;/code&gt; arenas, each reserving 64 MiB of address space. Capping it isolates the stack cost:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;MALLOC_ARENA_MAX&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 ./cost 200
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; threads       VmSize      VmRSS  Threads
       0         5320       2404        1
     100       210200       3420      101
     500      1038060       7252      501
    1000      2072852      12044     1001
    2000      4142436      21624     2001
    4000      8281508      40784     4001
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now it is clean and linear: roughly &lt;strong&gt;2,069 kB of virtual memory per thread&lt;/strong&gt; (2 MiB stack + guard page + TLS + thread descriptor). 4,000 threads costs about 7.9 GiB of virtual address space.&lt;/p&gt;

&lt;p&gt;The arena arithmetic checks out too. In the default run, 100 threads added 1,122,252 kB of &lt;code&gt;VmSize&lt;/code&gt;; 100 x 2,069 kB of stacks accounts for 206,900 kB, leaving 915,352 kB, which is 14 x 65,536 kB. Fourteen 64 MiB arenas, against a ceiling of &lt;code&gt;8 * 2 cores = 16&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Two lessons, and the second is the one that actually costs people time:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Threads are expensive in virtual memory, cheap in resident memory.&lt;/strong&gt; 4,000 threads cost 7.9 GiB of &lt;code&gt;VmSize&lt;/code&gt; but only about 40 MiB of &lt;code&gt;VmRSS&lt;/code&gt; — roughly &lt;strong&gt;10 kB resident per thread&lt;/strong&gt;. The stacks are mapped, not touched. Physical pages arrive on first write.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;VmSize&lt;/code&gt; is not memory usage.&lt;/strong&gt; If you alert on virtual size, a multi-threaded Rust service on glibc will page you for nothing. Alert on RSS, or on &lt;code&gt;Committed_AS&lt;/code&gt; if you care about overcommit headroom.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Does stack size actually matter?
&lt;/h3&gt;

&lt;p&gt;If RSS is what matters and stacks are lazily backed, does &lt;code&gt;stack_size&lt;/code&gt; matter at all? Measured, 1,000 threads each, arenas capped:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;stack_size=    16384  n=1000  VmSize delta=    49584 kB (   49 kB/thread)  VmRSS delta= 13712 kB ( 13 kB/thread)
stack_size=    65536  n=1000  VmSize delta=    85584 kB (   85 kB/thread)  VmRSS delta=  9712 kB (  9 kB/thread)
stack_size=   262144  n=1000  VmSize delta=   277584 kB (  277 kB/thread)  VmRSS delta=  9712 kB (  9 kB/thread)
stack_size=  2097152  n=1000  VmSize delta=  2069584 kB ( 2069 kB/thread)  VmRSS delta=  9712 kB (  9 kB/thread)
stack_size=  8388608  n=1000  VmSize delta=  8213584 kB ( 8213 kB/thread)  VmRSS delta=  9712 kB (  9 kB/thread)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;VmSize&lt;/code&gt; tracks the requested stack almost exactly, with a constant overhead of about &lt;strong&gt;21 kB per thread&lt;/strong&gt; on top (guard page, thread control block, static TLS) — visible in every row from 64 KiB upward: 85 - 64 = 21, 277 - 256 = 21, 2069 - 2048 = 21, 8213 - 8192 = 21. The 16 KiB row breaks the pattern because the request is below the platform minimum and gets raised; that is the &lt;code&gt;cmp::max(stack, min_stack_size(...))&lt;/code&gt; we will see in the source. &lt;code&gt;VmRSS&lt;/code&gt; is flat at roughly 9.7 kB per thread regardless of whether you asked for 64 KiB or 8 MiB of stack.&lt;/p&gt;

&lt;p&gt;So: shrinking stacks buys you address space, not RAM. On 64-bit that is usually not the constraint you are fighting — which means &lt;code&gt;stack_size&lt;/code&gt; tuning is mostly worth doing when you are hitting VMA or overcommit limits, not as a general memory optimisation. That is a different conclusion from the folklore, and it only shows up if you measure both numbers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Time
&lt;/h3&gt;

&lt;p&gt;Cost of a thread that does nothing at all — &lt;code&gt;spawn&lt;/code&gt; immediately followed by &lt;code&gt;join&lt;/code&gt;, 1,000 iterations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;spawn+join, 1000 iterations (nanoseconds)
  min    = 11206
  p50    = 47496
  p90    = 119084
  p99    = 230279
  max    = 505619
  mean   = 70174
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A round trip of about &lt;strong&gt;47 us at p50&lt;/strong&gt;, with a p99 of &lt;strong&gt;230 us&lt;/strong&gt; and a worst case over &lt;strong&gt;half a millisecond&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Separating creation from teardown — 10,000 threads created with 64 KiB stacks, all created before any join:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;creating 10000 threads (64 KiB stacks)
  spawn() call    p50 =    36569 ns
  spawn() call    p99 =   300113 ns
  spawn() call    max =  2687274 ns
  wall to create all  = 546.666318ms  (18293 threads/sec)
  wall to join all    = 43.088635ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Roughly &lt;strong&gt;18,000 threads/sec&lt;/strong&gt; of creation throughput on two cores, with a p50 of 37 us for the &lt;code&gt;spawn()&lt;/code&gt; call itself and a tail reaching &lt;strong&gt;2.7 ms&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Put that next to a latency budget. If your API server's p99 target is 10 ms and you spawn a thread per request, creation alone consumes 0.4% of the budget at p50, 3% at p99, and 27% in the worst case observed — before your handler has read a single byte. At 20,000 requests/sec, thread creation alone saturates both cores.&lt;/p&gt;

&lt;p&gt;That is the number that justifies article 5.&lt;/p&gt;

&lt;h3&gt;
  
  
  But the obvious fix is less obvious than it looks
&lt;/h3&gt;

&lt;p&gt;I expected the "reuse a thread instead" comparison to be dramatic. It wasn't:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;N = 20000
  spawn+join per item :     46675 ns   (total 933.507137ms)
  channel round-trip  :     37140 ns   (total 742.79602ms)
  ratio               :       1.3x
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Handing work to an already-running thread over an &lt;code&gt;mpsc&lt;/code&gt; channel and waiting for the reply is only &lt;strong&gt;1.3x faster&lt;/strong&gt; than creating a whole new OS thread for it.&lt;/p&gt;

&lt;p&gt;That result is real, and it is more instructive than the one I was expecting. Both paths are dominated by the same thing: &lt;strong&gt;blocking, and being woken by the scheduler.&lt;/strong&gt; A strict ping-pong handoff costs two context switches per item, and on a 2-core machine those wakeups cost roughly what a &lt;code&gt;clone3&lt;/code&gt; costs. Thread creation is not the expensive part in this shape of workload — &lt;em&gt;blocking&lt;/em&gt; is.&lt;/p&gt;

&lt;p&gt;Which reframes the lesson. The value of a thread pool is not "spawning is slow". It is that a pool lets many items be in flight across a fixed set of threads without a sleep/wake cycle per item. And it points straight at the async argument: the reason Tokio tasks are cheap is not primarily that they skip &lt;code&gt;clone3&lt;/code&gt; — it is that switching between tasks does not go through the kernel scheduler at all. Article 6 gets to test that claim against these exact numbers.&lt;/p&gt;

&lt;p&gt;I am keeping this result in because it is the kind of thing that gets quietly dropped when a benchmark doesn't say what the author wanted it to.&lt;/p&gt;




&lt;h2&gt;
  
  
  Stressing the Design: More Threads Than Cores
&lt;/h2&gt;

&lt;p&gt;This machine has 2 cores. What happens when 64 threads all want CPU?&lt;/p&gt;

&lt;p&gt;Each thread does an &lt;em&gt;identical&lt;/em&gt; fixed amount of CPU-bound work, and reports its own wall time and its own involuntary context switches, read from &lt;code&gt;/proc/thread-self/status&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// oversub2.rs (excerpt)&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;handles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.map&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nn"&gt;thread&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;spawn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;move&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;t0&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Instant&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;n0&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;self_stat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"nonvoluntary_ctxt_switches:"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;hint&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;black_box&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;burn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WORK&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t0&lt;/span&gt;&lt;span class="nf"&gt;.elapsed&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.as_millis&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="nf"&gt;self_stat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"nonvoluntary_ctxt_switches:"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;n0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}))&lt;/span&gt;&lt;span class="nf"&gt;.collect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; threads     min_ms     p50_ms     max_ms     total_ms    nonvol_ctxt
       1        372        372        372          372              3
       2        373        380        380          380             46
       4        654        755        759          760            369
       8       1494       1506       1511         1517            819
      16       2955       2981       3014         3015           1739
      32       5711       5928       6035         6047           3557
      64      11763      12023      12130        12139           6942
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read this carefully, because the interesting result is what &lt;em&gt;didn't&lt;/em&gt; happen.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Throughput is preserved.&lt;/strong&gt; 64 threads each doing 372 ms of work is 23.8 seconds of CPU work, completed in 12.1 s of wall time on 2 cores — a speedup of about 1.96x, which is essentially both cores fully busy. The scheduler did not fall over. There is no throughput collapse from oversubscription here.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Latency is destroyed.&lt;/strong&gt; The &lt;em&gt;same unit of work&lt;/em&gt; takes 372 ms when it has a core to itself and 12,023 ms when it is one of 64 threads sharing two cores. That is 32x. If that unit of work is a request, your p50 just moved by a factor of 32 while your dashboards show healthy CPU utilization and healthy throughput.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Involuntary context switches scale linearly.&lt;/strong&gt; 6,942 involuntary switches across 64 threads is about 108 preemptions per thread. Each thread consumed roughly 372 ms of actual CPU time, so it was preempted after about &lt;strong&gt;3.4 ms of CPU&lt;/strong&gt; each time. That is the scheduler doing exactly what it was designed to do — and 6,942 switches is 6,942 register saves, TLB and cache disturbances, and runqueue operations that the 1-thread run did not pay for.&lt;/p&gt;

&lt;p&gt;The engineering consequence: &lt;strong&gt;oversubscription is a latency problem, not a throughput problem.&lt;/strong&gt; Adding threads past core count does not get more work done; it spreads the same work over more concurrent, slower units. For a batch job that is harmless. For a request/response service it converts a fast p50 into a uniformly slow one — and it does so without tripping any of the signals people usually watch. Neither CPU utilization nor throughput will tell you. Only per-request latency will.&lt;/p&gt;

&lt;p&gt;This is also why "just add more threads" fails for a service that is slow because it is CPU-bound, and works for a service that is slow because it is blocking on I/O. Those two look identical on a throughput graph and are opposite problems.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where the ceiling actually is
&lt;/h3&gt;

&lt;p&gt;The limits on this box:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;threads-max:    64113        # /proc/sys/kernel/threads-max
pid_max:        32768        # /proc/sys/kernel/pid_max
max_map_count:  65530        # /proc/sys/vm/max_map_count
RLIMIT_STACK:   8192 kB
max user processes (-u): 32056
MemTotal:       8216168 kB
CommitLimit:    4108084 kB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The one people forget is &lt;code&gt;max_map_count&lt;/code&gt;. We measured that each thread costs &lt;strong&gt;two VMAs&lt;/strong&gt; — stack plus guard page. At 65,530 mappings that caps you near ~32,000 threads from mapping pressure alone, which happens to land in the same range as &lt;code&gt;pid_max&lt;/code&gt;. You will typically hit a limit like this, or &lt;code&gt;CommitLimit&lt;/code&gt;, well before you run out of RAM.&lt;/p&gt;

&lt;p&gt;So "how many threads can I create" has at least four independent answers — &lt;code&gt;threads-max&lt;/code&gt;, &lt;code&gt;pid_max&lt;/code&gt;, &lt;code&gt;RLIMIT_NPROC&lt;/code&gt;, &lt;code&gt;max_map_count&lt;/code&gt; — plus overcommit policy. None of them is the number you would guess from RSS.&lt;/p&gt;




&lt;h2&gt;
  
  
  Reading the Source: How the Call Gets There
&lt;/h2&gt;

&lt;p&gt;The trace tells us where we ended up. The source tells us how.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;std::thread::spawn&lt;/code&gt; is a thin wrapper over &lt;code&gt;Builder&lt;/code&gt;, which eventually calls the platform implementation. On Unix that is &lt;code&gt;Thread::new&lt;/code&gt;, in the standard library's &lt;code&gt;sys/thread/unix.rs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;unsafe&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Box&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ThreadInit&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;io&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;MaybeUninit&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nn"&gt;libc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;pthread_attr_t&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;MaybeUninit&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;uninit&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nd"&gt;assert_eq!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;libc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;pthread_attr_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attr&lt;/span&gt;&lt;span class="nf"&gt;.as_mut_ptr&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;attr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;DropGuard&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="n"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nd"&gt;assert_eq!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;libc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;pthread_attr_destroy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attr&lt;/span&gt;&lt;span class="nf"&gt;.as_mut_ptr&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;stack_size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;cmp&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;min_stack_size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attr&lt;/span&gt;&lt;span class="nf"&gt;.as_ptr&lt;/span&gt;&lt;span class="p"&gt;()));&lt;/span&gt;
    &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="nn"&gt;libc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;pthread_attr_setstacksize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attr&lt;/span&gt;&lt;span class="nf"&gt;.as_mut_ptr&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;stack_size&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
        &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nd"&gt;assert_eq!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;libc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;EINVAL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="c1"&gt;// Round up to nearest page on alignment failure&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;page_size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;pal&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;conf&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;page_size&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;stack_size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stack_size&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;page_size&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;
                            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page_size&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;isize&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="c1"&gt;// ...&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;// ... libc::pthread_create(...)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the whole story in one function. There is no Rust-specific thread machinery in the kernel path. Rust:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;picks a stack size — &lt;code&gt;max(requested, platform minimum)&lt;/code&gt;, defaulting to &lt;code&gt;DEFAULT_MIN_STACK_SIZE = 2 * 1024 * 1024&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;sets it on a &lt;code&gt;pthread_attr_t&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;calls &lt;code&gt;pthread_create&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;and lets glibc do the &lt;code&gt;mmap&lt;/code&gt; + &lt;code&gt;mprotect&lt;/code&gt; + &lt;code&gt;clone3&lt;/code&gt; we watched in the trace.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The closure is boxed and handed through as the thread's argument; the &lt;code&gt;JoinHandle&lt;/code&gt; wraps the resulting thread handle plus a slot for the return value. &lt;code&gt;join()&lt;/code&gt; ends in the &lt;code&gt;futex&lt;/code&gt; wait we already saw.&lt;/p&gt;

&lt;p&gt;The chain, complete:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;std::thread::spawn
  -&amp;gt; std::thread::Builder::spawn_unchecked_
    -&amp;gt; sys::thread::Thread::new
      -&amp;gt; pthread_attr_init / pthread_attr_setstacksize
        -&amp;gt; pthread_create                        (glibc)
          -&amp;gt; mmap(PROT_NONE) + mprotect          (stack + guard page)
            -&amp;gt; clone3(CLONE_VM|CLONE_THREAD|...) (kernel)
              -&amp;gt; new task_struct, scheduled independently
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every layer in that chain is one we can now name, observe, and measure. Nothing in it is magic. And nothing in it is free.&lt;/p&gt;




&lt;h2&gt;
  
  
  Production Connection
&lt;/h2&gt;

&lt;p&gt;Bringing this back to systems people actually operate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;API servers.&lt;/strong&gt; Thread-per-request costs about 37 us at p50 and up to 2.7 ms at the tail before your handler runs. Once concurrent requests exceed core count, per-request latency degrades proportionally while throughput and CPU graphs stay flat. This is the specific, measurable reason the industry moved to pools and then to async — not fashion.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Databases and caches.&lt;/strong&gt; Connection-per-thread designs hit &lt;code&gt;max_map_count&lt;/code&gt; and &lt;code&gt;CommitLimit&lt;/code&gt; before they hit RAM. A 2 MiB default stack times 10,000 connections is 20 GiB of address space for maybe 100 MiB of actual use. Knowing that &lt;code&gt;VmSize&lt;/code&gt; is address space and &lt;code&gt;VmRSS&lt;/code&gt; is memory is the difference between capacity planning and guessing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Message brokers and Kafka-style consumers.&lt;/strong&gt; The backpressure chain from the introduction — queue growth, memory growth, scheduler pressure, latency spikes — is exactly the oversubscription curve above. When a consumer falls behind and something spawns more workers to catch up, you get the 64-thread row: same throughput, 32x the latency.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Low-latency and trading systems.&lt;/strong&gt; The tail matters more than the mean, and the tail here is involuntary preemption. 108 preemptions per thread over 12 s is invisible in an average and fatal in a p99.9. This is why such systems pin threads to cores, keep runnable threads at or below core count, and never create threads on the hot path.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Blockchain nodes, proxies, WebSocket servers, vector databases, AI infrastructure.&lt;/strong&gt; All of them are "many concurrent connections, mostly waiting". That workload is the worst possible fit for thread-per-connection: you pay full thread cost for entities that are idle almost all the time. That mismatch is precisely the gap async runtimes exist to fill — and article 6 will measure whether they actually do.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Everywhere.&lt;/strong&gt; Name your threads. It costs one &lt;code&gt;Builder::name()&lt;/code&gt; call, and it puts a meaningful string into &lt;code&gt;/proc/&amp;lt;tid&amp;gt;/comm&lt;/code&gt;, &lt;code&gt;ps&lt;/code&gt;, &lt;code&gt;top&lt;/code&gt;, &lt;code&gt;perf&lt;/code&gt;, and your crash messages.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The hypothesis was: &lt;em&gt;Rust creates a native OS thread that Linux independently tracks and schedules.&lt;/em&gt; That held up. The useful part was everything the investigation added around it.&lt;/p&gt;

&lt;p&gt;What we established, all of it observed rather than assumed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;There is &lt;strong&gt;no thread syscall&lt;/strong&gt;. &lt;code&gt;clone3&lt;/code&gt; creates a task; the sharing flags — &lt;code&gt;CLONE_VM&lt;/code&gt;, &lt;code&gt;CLONE_THREAD&lt;/code&gt;, &lt;code&gt;CLONE_FILES&lt;/code&gt;, &lt;code&gt;CLONE_SIGHAND&lt;/code&gt; — are the entire difference between a thread and a process.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;stack is allocated in userspace by glibc&lt;/strong&gt; before the kernel is involved: a &lt;code&gt;PROT_NONE&lt;/code&gt; &lt;code&gt;mmap&lt;/code&gt; of 2 MiB + 4 KiB, then &lt;code&gt;mprotect&lt;/code&gt; to open everything except the guard page.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;2 MiB is Rust's choice&lt;/strong&gt;, from &lt;code&gt;DEFAULT_MIN_STACK_SIZE&lt;/code&gt;, overridable via &lt;code&gt;RUST_MIN_STACK&lt;/code&gt; or &lt;code&gt;Builder::stack_size&lt;/code&gt; — confirmed by watching the &lt;code&gt;mmap&lt;/code&gt; size change.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;join()&lt;/code&gt; is a futex wait&lt;/strong&gt;, armed by &lt;code&gt;CLONE_CHILD_CLEARTID&lt;/code&gt;. The kernel clears the child's TID on exit and wakes the waiter.&lt;/li&gt;
&lt;li&gt;A thread costs about &lt;strong&gt;2,069 kB of virtual memory&lt;/strong&gt; and about &lt;strong&gt;10 kB of resident memory&lt;/strong&gt;. Those two numbers are two orders of magnitude apart, and confusing them is a common operational error.&lt;/li&gt;
&lt;li&gt;A thread costs about &lt;strong&gt;37 us to create&lt;/strong&gt; at p50, with a &lt;strong&gt;2.7 ms&lt;/strong&gt; tail, at roughly &lt;strong&gt;18,000 threads/sec&lt;/strong&gt; on two cores.&lt;/li&gt;
&lt;li&gt;Beyond core count, &lt;strong&gt;throughput holds and latency degrades linearly&lt;/strong&gt; — 32x for 64 threads on 2 cores — with involuntary context switches scaling to match.&lt;/li&gt;
&lt;li&gt;The practical ceiling on thread count comes from &lt;strong&gt;&lt;code&gt;max_map_count&lt;/code&gt;, &lt;code&gt;pid_max&lt;/code&gt;, &lt;code&gt;RLIMIT_NPROC&lt;/code&gt; and overcommit&lt;/strong&gt;, not from RAM. Each thread burns two VMAs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And one thing I had wrong going in: I assumed the expensive part of thread-per-work was creation. The reuse benchmark says the expensive part is &lt;strong&gt;blocking and waking&lt;/strong&gt;. Creation is merely expensive as well. That distinction is going to shape most of the rest of this series.&lt;/p&gt;

&lt;h3&gt;
  
  
  Next
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Article 2: What Does &lt;code&gt;Arc&lt;/code&gt; Actually Do? Atomic Reference Counting Under the Hood.&lt;/strong&gt; We now have multiple tasks sharing one &lt;code&gt;mm_struct&lt;/code&gt; — byte-identical &lt;code&gt;/proc/&amp;lt;tid&amp;gt;/maps&lt;/code&gt;, confirmed above. The immediate question is how ownership of anything inside that shared address space is tracked safely. That means looking at what &lt;code&gt;Arc&lt;/code&gt; actually contains in memory, what an atomic increment compiles to on x86-64, why the &lt;code&gt;Drop&lt;/code&gt; path needs a stronger ordering than the clone path, and what the counter does under contention.&lt;/p&gt;

&lt;p&gt;Then article 3 puts a lock around it and measures what contention costs, and article 4 follows the blocking path back to the same &lt;code&gt;futex&lt;/code&gt; syscall we already saw here.&lt;/p&gt;




&lt;h2&gt;
  
  
  Appendix: Environment and Reproducibility
&lt;/h2&gt;

&lt;p&gt;Every number in this article came from this machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kernel:  6.18.5-fc-v20 (x86_64)
distro:  Ubuntu 24.04.4 LTS
rustc:   1.95.0 (59807616e 2026-04-14)
glibc:   2.39-0ubuntu8.7
strace:  6.8
cpu:     Intel(R) Xeon(R) Processor @ 2.10GHz, 2 cores
memory:  8216168 kB total
RLIMIT_STACK: 8192 kB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Programs used, all single-file, all buildable with &lt;code&gt;rustc -O&lt;/code&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;File&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;hello_thread.rs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;minimal one-thread program, for the syscall trace&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;four_threads.rs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;four named parked threads, for &lt;code&gt;/proc&lt;/code&gt; inspection&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;overflow.rs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;deliberate stack overflow, for the guard page&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cost.rs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;spawn+join latency percentiles and live-thread footprint&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;stacksize.rs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;VmSize&lt;/code&gt;/&lt;code&gt;VmRSS&lt;/code&gt; per thread across stack sizes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;createcost.rs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;creation-only latency and creation throughput&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;reuse.rs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;spawn-per-item vs. channel handoff&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;oversub2.rs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;per-thread latency and preemptions vs. thread count&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;rustc &lt;span class="nt"&gt;-O&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; hello_thread.rs &lt;span class="nt"&gt;-o&lt;/span&gt; hello_thread
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;strace &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; ./hello_thread
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;strace &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; hello_full.strace ./hello_thread
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;strace &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;trace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;none ./overflow          &lt;span class="c"&gt;# signals only&lt;/span&gt;
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;MALLOC_ARENA_MAX&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 ./cost 200
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;MALLOC_ARENA_MAX&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 ./oversub2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Caveats I want to be explicit about, because they change the numbers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Two cores.&lt;/strong&gt; The oversubscription curve is steeper here than on a 32-core host, and absolute spawn latency is higher. The shape transfers; the values do not.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Virtualised environment.&lt;/strong&gt; Syscall and context-switch costs under a hypervisor differ from bare metal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;glibc-specific.&lt;/strong&gt; The malloc arena behaviour — those 128 MiB &lt;code&gt;PROT_NONE&lt;/code&gt; reservations — is a glibc implementation detail. musl behaves differently, and a Rust binary using a different global allocator will not show it at all.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Instant::now()&lt;/code&gt; around &lt;code&gt;spawn&lt;/code&gt;&lt;/strong&gt; includes timing-call overhead. At tens of microseconds that is negligible, but it is not zero.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;/proc/self/status&lt;/code&gt; counters are per-thread, not per-process.&lt;/strong&gt; My first oversubscription run read them from the main thread and reported almost no context switches, which is why the final version reads &lt;code&gt;/proc/thread-self/status&lt;/code&gt; from inside each worker. Worth knowing before you trust a similar measurement of your own.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you re-run these on a machine with more cores, I would be interested in where the latency curve bends.&lt;/p&gt;

&lt;h2&gt;
  
  
  Thanks for Reading
&lt;/h2&gt;

&lt;p&gt;If you got this far — thank you. That was a long way down for one line of Rust.&lt;/p&gt;

&lt;p&gt;This series is me working through layers I have used for years without ever inspecting, and writing it up as I go rather than after I have it all figured out. That means the investigations are honest about what surprised me, and it means some of them will be wrong. If you spot something wrong here, I would much rather hear it now than leave it standing while nine more articles get built on top of it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Next up — Article 2: What Does &lt;code&gt;Arc&lt;/code&gt; Actually Do? Atomic Reference Counting Under the Hood.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/Parikalp-Bhardwaj" rel="noopener noreferrer"&gt;GITHUB_URL&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;LinkedIn: &lt;a href="https://www.linkedin.com/in/parikalp-bhardwaj/" rel="noopener noreferrer"&gt;LINKEDIN_URL&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>rust</category>
      <category>linux</category>
      <category>programming</category>
      <category>performance</category>
    </item>
    <item>
      <title>Building a RAG System in Rust with Qdrant, Rig, and gRPC 🦀</title>
      <dc:creator>Parikalp Bhardwaj</dc:creator>
      <pubDate>Sat, 30 May 2026 21:01:01 +0000</pubDate>
      <link>https://dev.to/parikalp_bhardwaj_9e9d812/understanding-rag-internals-by-building-one-in-rust-30c8</link>
      <guid>https://dev.to/parikalp_bhardwaj_9e9d812/understanding-rag-internals-by-building-one-in-rust-30c8</guid>
      <description>&lt;p&gt;&lt;em&gt;With Qdrant, Rig, Tonic — and a healthy obsession with what's actually happening underneath.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I Built This
&lt;/h2&gt;

&lt;p&gt;A few weeks ago I came across &lt;a href="https://rust-dd.com/post/our-first-production-ready-rag-dev-journey-in-pure-rust" rel="noopener noreferrer"&gt;&lt;em&gt;Our First Production-Ready RAG Dev Journey in Pure Rust&lt;/em&gt;&lt;/a&gt; by the rust-dd team — and something clicked.&lt;/p&gt;

&lt;p&gt;Reading it, I realized two things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Rust is the right language for building AI systems&lt;/strong&gt; — fast, safe, and built for the kind of infrastructure work AI actually needs.&lt;/li&gt;
&lt;li&gt;I'd been wanting to build something like this myself for months and kept finding excuses not to start.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So I started. This post is what came out of it. 🦀&lt;/p&gt;




&lt;p&gt;Most RAG tutorials hand you a framework, four function calls, and a working demo. You upload some documents, vectors get generated somewhere, an LLM answers your questions, and you walk away with a chatbot but no real understanding of what just happened.&lt;/p&gt;

&lt;p&gt;This post takes the opposite approach. We'll build a small but complete RAG system in Rust using Qdrant for vector search, Rig as the AI framework, and Tonic for gRPC API, and a terminal chat client — and explain &lt;em&gt;why&lt;/em&gt; every piece exists. By the end you'll understand:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🧠 Why embeddings work and how semantic retrieval actually functions&lt;/li&gt;
&lt;li&gt;🗄️ Why vector databases exist and what problem ANN indexing solves&lt;/li&gt;
&lt;li&gt;⚡ Why async runtimes matter for retrieval pipelines&lt;/li&gt;
&lt;li&gt;🦀 Why Rust is becoming genuinely interesting for AI infrastructure&lt;/li&gt;
&lt;li&gt;🔌 How gRPC fits into modern AI service architectures&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The full source is at &lt;a href="https://github.com/Parikalp-Bhardwaj/qrag-rust" rel="noopener noreferrer"&gt;&lt;code&gt;github.com/Parikalp-Bhardwaj/qrag-rust&lt;/code&gt;&lt;/a&gt;. Clone it and read along.&lt;/p&gt;




&lt;h2&gt;
  
  
  🤔 Why RAG exists
&lt;/h2&gt;

&lt;p&gt;LLMs are powerful, but they don't actually &lt;em&gt;know&lt;/em&gt; anything about your data. They generate from patterns learned during training, which means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🚫 &lt;strong&gt;Hallucinations&lt;/strong&gt; when asked about specifics they never saw&lt;/li&gt;
&lt;li&gt;📅 &lt;strong&gt;Outdated information&lt;/strong&gt; — anything after the cutoff is invisible&lt;/li&gt;
&lt;li&gt;🔒 &lt;strong&gt;No access to private knowledge&lt;/strong&gt; — your docs, your codebase, your wiki&lt;/li&gt;
&lt;li&gt;📏 &lt;strong&gt;Limited context windows&lt;/strong&gt; — you can't paste everything in&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A model trained in 2023 can't tell you what your internal API does or what changed in last week's release. That's not a model problem — it's a &lt;em&gt;retrieval&lt;/em&gt; problem.&lt;/p&gt;

&lt;p&gt;RAG fixes it by inserting a retrieval step before generation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;query
  → retrieve relevant context from your data
    → inject context into the prompt
      → generate a grounded response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This shifts the engineering focus. The LLM becomes one component among several, and the quality of the system depends on chunking strategy, embedding quality, retrieval accuracy, indexing, and latency. That's the territory most tutorials skip. We're going to live there.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧩 The building blocks
&lt;/h2&gt;

&lt;p&gt;Four pieces do most of the work in this project.&lt;/p&gt;

&lt;h3&gt;
  
  
  🔎 Qdrant — the vector database
&lt;/h3&gt;

&lt;p&gt;In a normal database you search by exact values:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;documents&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Rust'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That works for keywords. It doesn't work for meaning. Consider:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Query:&lt;/strong&gt; "How does Rust prevent race conditions?"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Relevant doc:&lt;/strong&gt; "Rust provides memory safety and fearless concurrency through ownership and borrowing."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Zero word overlap. A SQL &lt;code&gt;LIKE&lt;/code&gt; won't find this. But a human reading both knows they're talking about the same thing.&lt;/p&gt;

&lt;p&gt;Qdrant stores documents as &lt;strong&gt;vectors&lt;/strong&gt; — numerical fingerprints of meaning. Two semantically similar texts produce two vectors that sit close together in high-dimensional space. Search becomes "find the vectors nearest to my query vector." A Qdrant point looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Point {
  id: "doc-1",
  vector: [0.12, -0.44, 0.91, ...],   // 1536 dimensions
  payload: {
    "file_path": "rust-notes.md",
    "chunk_index": 3,
    "text": "Rust ownership prevents memory bugs..."
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why a dedicated database?&lt;/strong&gt; Naively, retrieval means comparing the query vector against every stored vector — &lt;code&gt;O(n)&lt;/code&gt; per query. Fine for 100 documents, a disaster for 10 million. Qdrant uses &lt;strong&gt;Approximate Nearest Neighbor (ANN)&lt;/strong&gt; indexing (HNSW under the hood) to find near matches without scanning everything. You trade a sliver of accuracy for a dramatic speedup. That's the entire reason vector databases exist as a category.&lt;/p&gt;

&lt;h3&gt;
  
  
  🦀 Rig — the AI application layer
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/0xPlaygrounds/rig" rel="noopener noreferrer"&gt;Rig&lt;/a&gt; is a Rust framework for LLM apps. It handles the boring, provider-specific parts: embedding APIs, completion APIs, vector store glue, agent construction. Without Rig we'd be hand-writing HTTP clients for OpenRouter.&lt;/p&gt;

&lt;p&gt;⚠️ &lt;strong&gt;Honest caveat:&lt;/strong&gt; Rig reduces boilerplate, but it doesn't think for you. Chunking strategy, retrieval ranking, prompt construction — those are still your problem and they're what actually determine answer quality.&lt;/p&gt;

&lt;h3&gt;
  
  
  🚀 gRPC + Tonic — the service layer
&lt;/h3&gt;

&lt;p&gt;gRPC is a high-performance RPC framework. Instead of &lt;code&gt;POST /chat&lt;/code&gt; with JSON, you define services in Protocol Buffers and get strongly-typed clients and servers in any supported language. &lt;a href="https://github.com/hyperium/tonic" rel="noopener noreferrer"&gt;Tonic&lt;/a&gt; is the Rust implementation.&lt;/p&gt;

&lt;p&gt;Why gRPC over REST? Two reasons:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Typed contracts.&lt;/strong&gt; The &lt;code&gt;.proto&lt;/code&gt; file is the single source of truth. Client and server can't drift apart.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backend-to-backend fit.&lt;/strong&gt; Real AI infrastructure looks like &lt;code&gt;frontend → API gateway → RAG service → vector DB → LLM provider&lt;/code&gt;. gRPC is built for the internal hops.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  ⚡ Tokio — the async runtime
&lt;/h3&gt;

&lt;p&gt;Every network call here is async — Qdrant queries, OpenRouter embeddings, LLM completions. Tokio lets thousands of them run concurrently on a small thread pool without us managing threads by hand. It's the quiet substrate underneath everything else.&lt;/p&gt;




&lt;h2&gt;
  
  
  🏗️ Architecture at a glance
&lt;/h2&gt;

&lt;p&gt;The system has two phases that share one &lt;code&gt;RagEngine&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Indexing&lt;/strong&gt; (runs once, or whenever docs change):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;./docs → load → chunk → embed (OpenRouter) → store (Qdrant)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Query&lt;/strong&gt; (runs every time someone asks):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;question → embed → search Qdrant → top-k chunks → prompt → LLM → answer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same embedding model on both sides — that's what makes the geometry work. You can't embed documents with one model and queries with another and expect distances to mean anything.&lt;/p&gt;




&lt;h2&gt;
  
  
  📂 Project layout
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;qrag-rust/
├── Cargo.toml               # crate metadata + dependencies
├── build.rs                 # compiles .proto → Rust at build time
├── docker-compose.yaml      # Qdrant container
├── .env.example             # template for secrets
├── proto/
│   └── rag.proto            # gRPC service definition
├── docs/                    # your knowledge base lives here
│   ├── grpc.md
│   ├── rust.md
│   └── tokio.md
|   └── Rust-for-Network-Programming-and-Automation.pdf
└── src/
    ├── main.rs              # boots the gRPC server
    ├── config.rs            # env-var configuration
    ├── document_loader.rs   # read .md, .txt, .pdf
    ├── chunker.rs           # split into ~120-word chunks
    ├── qdrant_store.rs      # embeddings + vector storage
    ├── llm.rs               # prompt + completion
    ├── rag.rs               # orchestration
    ├── grpc_service.rs      # tonic handlers
    └── bin/
        └── chat.rs          # terminal chat client
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each file has one job. That's deliberate — RAG systems get complicated quickly, and clean seams are the only defense.&lt;/p&gt;




&lt;h2&gt;
  
  
  🛠️ Prerequisites
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Rust
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;--proto&lt;/span&gt; &lt;span class="s1"&gt;'=https'&lt;/span&gt; &lt;span class="nt"&gt;--tlsv1&lt;/span&gt;.2 &lt;span class="nt"&gt;-sSf&lt;/span&gt; https://sh.rustup.rs | sh
&lt;span class="nb"&gt;source&lt;/span&gt; &lt;span class="nv"&gt;$HOME&lt;/span&gt;/.cargo/env
rustc &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  System packages
&lt;/h3&gt;

&lt;p&gt;A surprising number of Rust crates compile native C/C++ underneath. On Ubuntu:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    build-essential pkg-config libssl-dev &lt;span class="se"&gt;\&lt;/span&gt;
    clang cmake protobuf-compiler poppler-utils
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Package&lt;/th&gt;
&lt;th&gt;Why you need it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;build-essential&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;GCC/G++ for native compilation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pkg-config&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Locates system libraries&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;libssl-dev&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;OpenSSL headers — any HTTPS crate needs them&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;clang&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;LLVM toolchain (bindgen, ML runtime crates)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cmake&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Used by several native ML libraries&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;protobuf-compiler&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The &lt;code&gt;protoc&lt;/code&gt; binary that turns &lt;code&gt;.proto&lt;/code&gt; into Rust&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;poppler-utils&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Provides &lt;code&gt;pdftotext&lt;/code&gt; — we shell out to it for PDFs&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Verify protoc:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;protoc &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Docker + Qdrant
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; docker.io docker-compose-plugin
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="nt"&gt;--now&lt;/span&gt; docker
docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;   &lt;span class="c"&gt;# uses the docker-compose.yaml in the repo&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Port&lt;/th&gt;
&lt;th&gt;What it serves&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;6333&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;REST API + dashboard at &lt;code&gt;http://localhost:6333/dashboard&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;6334&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;gRPC API — what our Rust client talks to&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  OpenRouter key
&lt;/h3&gt;

&lt;p&gt;We use &lt;a href="https://openrouter.ai" rel="noopener noreferrer"&gt;OpenRouter&lt;/a&gt; so the same key serves both the embedding model and the completion model. Create a key, then:&lt;/p&gt;




&lt;h2&gt;
  
  
  🔐 Setting up &lt;code&gt;.env&lt;/code&gt; and Qdrant
&lt;/h2&gt;

&lt;p&gt;Before you can run anything, two things need to be in place: a &lt;code&gt;.env&lt;/code&gt; file with your API key, and a running Qdrant container.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Create the &lt;code&gt;.env&lt;/code&gt; file
&lt;/h3&gt;

&lt;p&gt;In the project root, create a file named &lt;code&gt;.env&lt;/code&gt; (note the leading dot — it's a hidden file):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Required — your OpenRouter API key&lt;/span&gt;
&lt;span class="nv"&gt;OPENROUTER_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;sk-or-v1-paste-your-key-here

&lt;span class="c"&gt;# Optional — defaults shown&lt;/span&gt;
&lt;span class="nv"&gt;SERVER_ADDR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;127.0.0.1
&lt;span class="nv"&gt;PORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;50051
&lt;span class="nv"&gt;QDRANT_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://127.0.0.1:6334
&lt;span class="nv"&gt;QDRANT_COLLECTION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;question
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Get a key at &lt;a href="https://openrouter.ai/keys" rel="noopener noreferrer"&gt;openrouter.ai/keys&lt;/a&gt;. Free tier is enough to test.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What each variable does:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Variable&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;What it controls&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;OPENROUTER_API_KEY&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;(required)&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;Authenticates both embedding and LLM calls&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SERVER_ADDR&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;127.0.0.1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Host the gRPC server binds to (use &lt;code&gt;0.0.0.0&lt;/code&gt; for Docker)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PORT&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;50051&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;gRPC port your clients connect to&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;QDRANT_URL&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;http://127.0.0.1:6334&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Where Qdrant's gRPC endpoint lives&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;QDRANT_COLLECTION&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;question&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Name of the collection that stores your vectors&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;⚠️ &lt;strong&gt;Never commit &lt;code&gt;.env&lt;/code&gt; to git.&lt;/strong&gt; Add it to &lt;code&gt;.gitignore&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;".env"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; .gitignore
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you've already committed it once, rotate the key — git history doesn't forget. Ship a &lt;code&gt;.env.example&lt;/code&gt; with empty values instead so collaborators know what to set.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Start Qdrant with Docker
&lt;/h3&gt;

&lt;p&gt;The repo includes a &lt;code&gt;docker-compose.yaml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;qdrant&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;qdrant/qdrant:latest&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;coderag-qdrant&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;6333:6333"&lt;/span&gt;   &lt;span class="c1"&gt;# REST API + dashboard&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;6334:6334"&lt;/span&gt;   &lt;span class="c1"&gt;# gRPC API (this is what the Rust app uses)&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;qdrant_data:/qdrant/storage&lt;/span&gt;

&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;qdrant_data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Start it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;-d&lt;/code&gt; flag runs it in the background. Verify it's up:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker ps
&lt;span class="c"&gt;# you should see coderag-qdrant in the list&lt;/span&gt;

curl http://localhost:6333/healthz
&lt;span class="c"&gt;# should return: healthz check passed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open the dashboard in a browser:&lt;/p&gt;




&lt;h2&gt;
  
  
  📖 Walking through every file
&lt;/h2&gt;

&lt;p&gt;This is the part most tutorials skip. We're going file by file, top to bottom, explaining what each one does and why it's structured the way it is.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;Cargo.toml&lt;/code&gt; — the manifest
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[package]&lt;/span&gt;
&lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"qrag-rust"&lt;/span&gt;
&lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.1.0"&lt;/span&gt;
&lt;span class="py"&gt;edition&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"2024"&lt;/span&gt;

&lt;span class="nn"&gt;[[bin]]&lt;/span&gt;
&lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"qrag-rust"&lt;/span&gt;
&lt;span class="py"&gt;path&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"src/main.rs"&lt;/span&gt;

&lt;span class="nn"&gt;[[bin]]&lt;/span&gt;
&lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"chat"&lt;/span&gt;
&lt;span class="py"&gt;path&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"src/bin/chat.rs"&lt;/span&gt;

&lt;span class="nn"&gt;[dependencies]&lt;/span&gt;
&lt;span class="py"&gt;anyhow&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1"&lt;/span&gt;
&lt;span class="py"&gt;rig&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.37.0"&lt;/span&gt;
&lt;span class="py"&gt;tokio&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;features&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"full"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="py"&gt;tonic&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.12"&lt;/span&gt;
&lt;span class="py"&gt;prost&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.13"&lt;/span&gt;
&lt;span class="py"&gt;rig-qdrant&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.2"&lt;/span&gt;
&lt;span class="py"&gt;qdrant-client&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1"&lt;/span&gt;
&lt;span class="py"&gt;dotenvy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.15"&lt;/span&gt;
&lt;span class="py"&gt;uuid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;features&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"v4"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="py"&gt;serde&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;features&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"derive"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="py"&gt;serde_json&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1"&lt;/span&gt;
&lt;span class="py"&gt;tracing&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.1"&lt;/span&gt;
&lt;span class="py"&gt;tracing-subscriber&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.3"&lt;/span&gt;

&lt;span class="nn"&gt;[build-dependencies]&lt;/span&gt;
&lt;span class="py"&gt;tonic-build&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.12"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The two &lt;code&gt;[[bin]]&lt;/code&gt; entries are the important part. By default Cargo finds &lt;code&gt;src/main.rs&lt;/code&gt; automatically, but the moment you add a binary under &lt;code&gt;src/bin/&lt;/code&gt;, Cargo stops auto-discovering and you have to declare both explicitly. One is the server, one is the chat client. Both end up in &lt;code&gt;target/release/&lt;/code&gt; after &lt;code&gt;cargo build --release&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;tonic-build&lt;/code&gt; is a &lt;strong&gt;build-dependency&lt;/strong&gt;, not a runtime dependency. It only runs at compile time to turn &lt;code&gt;.proto&lt;/code&gt; files into Rust code.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;build.rs&lt;/code&gt; — code generation at compile time
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nb"&gt;Box&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;dyn&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;error&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nn"&gt;tonic_build&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;compile_protos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"proto/rag.proto"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(())&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three lines, big effect. Every time you run &lt;code&gt;cargo build&lt;/code&gt;, this script runs first. It reads &lt;code&gt;proto/rag.proto&lt;/code&gt;, generates Rust structs and traits for every message and service, and writes them to Cargo's &lt;code&gt;OUT_DIR&lt;/code&gt;. Later, &lt;code&gt;tonic::include_proto!("rag")&lt;/code&gt; in &lt;code&gt;main.rs&lt;/code&gt; pulls that generated code into our crate.&lt;/p&gt;

&lt;p&gt;This is why you never check generated proto code into git — it's regenerated on every build.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;proto/rag.proto&lt;/code&gt; — the service contract
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight protobuf"&gt;&lt;code&gt;&lt;span class="na"&gt;syntax&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"proto3"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;rag&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;service&lt;/span&gt; &lt;span class="n"&gt;RagService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;rpc&lt;/span&gt; &lt;span class="n"&gt;AskQuestion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AskQuestionRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;returns&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AskQuestionResponse&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;rpc&lt;/span&gt; &lt;span class="n"&gt;Reindex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ReindexRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;returns&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ReindexResponse&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;message&lt;/span&gt; &lt;span class="nc"&gt;AskQuestionRequest&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="na"&gt;question&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;message&lt;/span&gt; &lt;span class="nc"&gt;AskQuestionResponse&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="na"&gt;answer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;repeated&lt;/span&gt; &lt;span class="n"&gt;Source&lt;/span&gt; &lt;span class="na"&gt;sources&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;message&lt;/span&gt; &lt;span class="nc"&gt;Source&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="na"&gt;file_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;uint32&lt;/span&gt; &lt;span class="na"&gt;chunks_indexed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="na"&gt;preview&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="na"&gt;score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;message&lt;/span&gt; &lt;span class="nc"&gt;ReindexRequest&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="kd"&gt;message&lt;/span&gt; &lt;span class="nc"&gt;ReindexResponse&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;uint64&lt;/span&gt; &lt;span class="na"&gt;chunks_indexed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="kd"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This file is the single source of truth for the API. Two RPCs: &lt;code&gt;Reindex&lt;/code&gt; rebuilds the vector index from &lt;code&gt;./docs&lt;/code&gt;, and &lt;code&gt;AskQuestion&lt;/code&gt; is the actual query endpoint. The response carries the answer &lt;strong&gt;plus the source chunks that produced it&lt;/strong&gt; — that's the difference between a RAG system and a black box. Always return your sources so users can verify the grounding.&lt;/p&gt;

&lt;p&gt;The numbers (&lt;code&gt;= 1&lt;/code&gt;, &lt;code&gt;= 2&lt;/code&gt;) are field tags. They're how protobuf identifies fields on the wire. Once assigned, never change them — that's an instant breaking change for every existing client.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;src/config.rs&lt;/code&gt; — environment-driven config
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;#[derive(Debug,&lt;/span&gt; &lt;span class="nd"&gt;Clone)]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Config&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;addr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;qdrant_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;qdrant_collection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="n"&gt;Config&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;from_env&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;Self&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;Self&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;addr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SERVER_ADDR"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="nf"&gt;.unwrap_or_else&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="s"&gt;"127.0.0.1"&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
            &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"PORT"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="nf"&gt;.unwrap_or_else&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="s"&gt;"50051"&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
                &lt;span class="py"&gt;.parse&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;u16&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="nf"&gt;.expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"PORT must be a valid number"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;qdrant_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"QDRANT_URL"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="nf"&gt;.unwrap_or_else&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="s"&gt;"http://127.0.0.1:6334"&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
            &lt;span class="n"&gt;qdrant_collection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"QDRANT_COLLECTION"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="nf"&gt;.unwrap_or_else&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="s"&gt;"question"&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;server_addr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{}:{}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.addr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.port&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Plain config struct, populated from environment variables with sensible defaults. Each setting is documented by its name and its default. &lt;code&gt;SERVER_ADDR&lt;/code&gt; and &lt;code&gt;PORT&lt;/code&gt; configure where the gRPC server binds. &lt;code&gt;QDRANT_URL&lt;/code&gt; points to the Qdrant gRPC port (note: 6334, not the REST 6333). &lt;code&gt;QDRANT_COLLECTION&lt;/code&gt; names the collection.&lt;/p&gt;

&lt;p&gt;Defaults work out of the box for local dev. Production overrides come from &lt;code&gt;.env&lt;/code&gt; or the deployment environment.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;src/document_loader.rs&lt;/code&gt; — reading files from disk
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;anyhow&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;path&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PathBuf&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;process&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;#[derive(Debug,&lt;/span&gt; &lt;span class="nd"&gt;Clone)]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Document&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="n"&gt;loader_documents_from_dir&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;P&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;AsRef&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;P&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Document&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;documents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Vec&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;entries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;read_dir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.with_context&lt;/span&gt;&lt;span class="p"&gt;(||&lt;/span&gt; &lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to read directory: {:?}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dir&lt;/span&gt;&lt;span class="nf"&gt;.as_ref&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;entries&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;PathBuf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="nf"&gt;.path&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="nf"&gt;.is_file&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;extension&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="nf"&gt;.extension&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;extension&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;extension&lt;/span&gt;&lt;span class="nf"&gt;.to_string_lossy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.to_lowercase&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;extension&lt;/span&gt;&lt;span class="nf"&gt;.as_str&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s"&gt;"md"&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;read_to_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="nf"&gt;.with_context&lt;/span&gt;&lt;span class="p"&gt;(||&lt;/span&gt; &lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to read text file: {:?}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"pdf"&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;extract_pdf_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="nf"&gt;.with_context&lt;/span&gt;&lt;span class="p"&gt;(||&lt;/span&gt; &lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to extract text from PDF: {:?}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;

        &lt;span class="n"&gt;documents&lt;/span&gt;&lt;span class="nf"&gt;.push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Document&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="nf"&gt;.to_string_lossy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;documents&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;extract_pdf_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"pdftotext"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.arg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.arg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"-"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.output&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;.with_context&lt;/span&gt;&lt;span class="p"&gt;(||&lt;/span&gt; &lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to run pdftotext for {:?}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="py"&gt;.status&lt;/span&gt;&lt;span class="nf"&gt;.success&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nn"&gt;anyhow&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nd"&gt;bail!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"pdftotext failed for {:?}: {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;String&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_utf8_lossy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="py"&gt;.stderr&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;String&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_utf8_lossy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="py"&gt;.stdout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the on-ramp. The loader walks &lt;code&gt;./docs&lt;/code&gt;, skips directories and unknown extensions, reads each supported file, and returns a &lt;code&gt;Vec&amp;lt;Document&amp;gt;&lt;/code&gt; of &lt;code&gt;(path, content)&lt;/code&gt; pairs.&lt;/p&gt;

&lt;p&gt;Two design choices worth flagging. &lt;strong&gt;&lt;code&gt;&amp;lt;P: AsRef&amp;lt;Path&amp;gt;&amp;gt;&lt;/code&gt;&lt;/strong&gt; is a generic over anything that can become a &lt;code&gt;Path&lt;/code&gt; — &lt;code&gt;&amp;amp;str&lt;/code&gt;, &lt;code&gt;String&lt;/code&gt;, &lt;code&gt;PathBuf&lt;/code&gt;, &lt;code&gt;&amp;amp;Path&lt;/code&gt;. Callers can pass whatever they have without converting. &lt;strong&gt;PDF extraction shells out to &lt;code&gt;pdftotext&lt;/code&gt;&lt;/strong&gt; (from &lt;code&gt;poppler-utils&lt;/code&gt;) rather than pulling a heavyweight PDF crate. Not glamorous, but it's been battle-tested for years and avoids a 500-line dependency.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;src/chunker.rs&lt;/code&gt; — splitting documents into pieces
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="k"&gt;crate&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;document_loader&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Document&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;#[derive(Debug,&lt;/span&gt; &lt;span class="nd"&gt;Clone)]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;DocumentChunk&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;chunk_index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;chunk_retrieve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;documents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Document&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_word&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;DocumentChunk&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;chunks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Vec&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;docs&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;documents&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;words&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;docs&lt;/span&gt;&lt;span class="py"&gt;.content&lt;/span&gt;&lt;span class="nf"&gt;.split_whitespace&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.collect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunk_idx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;word_chunk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;words&lt;/span&gt;&lt;span class="nf"&gt;.chunks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_word&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.enumerate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;word_chunk&lt;/span&gt;&lt;span class="nf"&gt;.join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;" "&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="nf"&gt;.trim&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.is_empty&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="nf"&gt;.push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DocumentChunk&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Uuid&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new_v4&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                &lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;docs&lt;/span&gt;&lt;span class="py"&gt;.file_path&lt;/span&gt;&lt;span class="nf"&gt;.clone&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                &lt;span class="n"&gt;chunk_index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;chunk_idx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;chunks&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The single most underrated decision in RAG. Too large and retrieval becomes vague (the chunk has the answer plus a lot of noise); too small and meaning fragments across boundaries. We use a simple &lt;strong&gt;word-count chunker&lt;/strong&gt; at 120 words per chunk — a sweet spot for technical docs.&lt;/p&gt;

&lt;p&gt;Each chunk gets a fresh UUID (Qdrant uses it as the point ID), the source file path, and a &lt;code&gt;chunk_index&lt;/code&gt; so we can show users where the answer came from. &lt;code&gt;split_whitespace()&lt;/code&gt; is a small but important choice — it handles tabs, newlines, and runs of spaces correctly, where &lt;code&gt;split(' ')&lt;/code&gt; would not.&lt;/p&gt;

&lt;p&gt;Production systems often do smarter things: sentence-boundary chunking, semantic chunking (split where meaning shifts), or sliding-window chunks with overlap so context isn't lost at boundaries. All worth experimenting with once your baseline works.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;src/qdrant_store.rs&lt;/code&gt; — the heart of retrieval
&lt;/h3&gt;

&lt;p&gt;This is the longest file in the project. It does three things: create or reset the Qdrant collection, embed chunks and upsert them, and search by question. Let's break it into pieces.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The struct and embedding model:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;EMBEDDING_MODEL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"openai/text-embedding-3-small"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;#[derive(Clone)]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;QdrantStore&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Qdrant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;openrouter_client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;OpenRouterAiClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;collection_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;#[derive(Embed,&lt;/span&gt; &lt;span class="nd"&gt;Clone)]&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;ChunkEmbedding&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;chunk_index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nd"&gt;#[embed]&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;QdrantStore&lt;/code&gt; owns two clients: one to Qdrant, one to OpenRouter for embeddings. &lt;code&gt;ChunkEmbedding&lt;/code&gt; is a Rig pattern — the &lt;code&gt;#[derive(Embed)]&lt;/code&gt; macro plus the &lt;code&gt;#[embed]&lt;/code&gt; field attribute tells Rig "embed the &lt;code&gt;text&lt;/code&gt; field; everything else is metadata that rides along."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Constructing the store:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;qdrant_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;collection_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;Self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;anyhow&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nn"&gt;dotenvy&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;dotenv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.ok&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Qdrant&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;qdrant_url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;.context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to create Qdrant client"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;openrouter_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;OpenRouterAiClient&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_env&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;Self&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;openrouter_client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;collection_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;collection_name&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The constructor wires up both clients in one place. &lt;strong&gt;&lt;code&gt;Qdrant::from_url(...)&lt;/code&gt;&lt;/strong&gt; builds the gRPC client pointed at &lt;code&gt;http://127.0.0.1:6334&lt;/code&gt; (or wherever &lt;code&gt;QDRANT_URL&lt;/code&gt; says). &lt;strong&gt;&lt;code&gt;OpenRouterAiClient::from_env()&lt;/code&gt;&lt;/strong&gt; reads &lt;code&gt;OPENROUTER_API_KEY&lt;/code&gt; straight from the environment — that's why &lt;code&gt;dotenvy::dotenv().ok()&lt;/code&gt; runs first, so values from your &lt;code&gt;.env&lt;/code&gt; file are loaded before Rig looks for them.&lt;/p&gt;

&lt;p&gt;Both clients are cheap to clone (they wrap connection pools internally), which is why we can hand &lt;code&gt;QdrantStore&lt;/code&gt; around freely via &lt;code&gt;#[derive(Clone)]&lt;/code&gt; later.&lt;/p&gt;

&lt;p&gt;Why pass &lt;code&gt;qdrant_url&lt;/code&gt; and &lt;code&gt;collection_name&lt;/code&gt; as &lt;code&gt;&amp;amp;str&lt;/code&gt; instead of taking a &lt;code&gt;Config&lt;/code&gt; struct? Loose coupling. &lt;code&gt;QdrantStore&lt;/code&gt; doesn't need to know about the rest of the app's configuration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Collection creation:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;ensure_collection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;exists&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.client&lt;/span&gt;&lt;span class="nf"&gt;.collection_exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.collection_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;exists&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(());&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.client&lt;/span&gt;&lt;span class="nf"&gt;.create_collection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nn"&gt;CreateCollectionBuilder&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.collection_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;.vectors_config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;VectorParamsBuilder&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1536&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;Distance&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Cosine&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(())&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;1536&lt;/code&gt; matches &lt;code&gt;openai/text-embedding-3-small&lt;/code&gt;. &lt;strong&gt;Get this wrong and every upsert fails with a dimension mismatch&lt;/strong&gt; — a mistake everyone makes once. Cosine distance is the standard choice for normalized text embeddings.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;reset_collection&lt;/code&gt; (omitted for space — see repo) deletes and recreates. It's what &lt;code&gt;Reindex&lt;/code&gt; calls to start fresh.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Embedding and upserting chunks:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;upsert_chunks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;DocumentChunk&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="nf"&gt;.is_empty&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;embedding_model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.openrouter_client&lt;/span&gt;&lt;span class="nf"&gt;.embedding_model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;EMBEDDING_MODEL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;EmbeddingsBuilder&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;embedding_model&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;chunks&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="nf"&gt;.document&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ChunkEmbedding&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="py"&gt;.id&lt;/span&gt;&lt;span class="nf"&gt;.clone&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="py"&gt;.file_path&lt;/span&gt;&lt;span class="nf"&gt;.clone&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="n"&gt;chunk_index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="py"&gt;.chunk_index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="py"&gt;.text&lt;/span&gt;&lt;span class="nf"&gt;.clone&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;embedded_doc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="nf"&gt;.build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;points&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PointStruct&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;embedded_doc&lt;/span&gt;&lt;span class="nf"&gt;.into_iter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.map&lt;/span&gt;&lt;span class="p"&gt;(|(&lt;/span&gt;&lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;embeddings&lt;/span&gt;&lt;span class="p"&gt;)|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;f32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;embeddings&lt;/span&gt;&lt;span class="nf"&gt;.first&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="py"&gt;.vec&lt;/span&gt;
            &lt;span class="nf"&gt;.iter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.map&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;f32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.collect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="nn"&gt;PointStruct&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="py"&gt;.id&lt;/span&gt;&lt;span class="nf"&gt;.clone&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nn"&gt;Payload&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;try_from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;json!&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
                &lt;span class="s"&gt;"file_path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="py"&gt;.file_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s"&gt;"chunk_index"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="py"&gt;.chunk_index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="py"&gt;.text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;}))&lt;/span&gt;&lt;span class="nf"&gt;.expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"valid Qdrant payload"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="nf"&gt;.collect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.client&lt;/span&gt;&lt;span class="nf"&gt;.upsert_points&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nn"&gt;UpsertPointsBuilder&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.collection_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;points&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="nf"&gt;.len&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The flow: build up a batch of &lt;code&gt;ChunkEmbedding&lt;/code&gt;s, hand them to Rig's &lt;code&gt;EmbeddingsBuilder&lt;/code&gt;, get back &lt;code&gt;(doc, embeddings)&lt;/code&gt; pairs. We cast &lt;code&gt;f64 → f32&lt;/code&gt; because Qdrant expects 32-bit floats. The payload carries the original text and metadata — that's what &lt;code&gt;search&lt;/code&gt; will later return to us so we can hand it to the LLM.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Searching:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;top_k&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;RetrievedChunk&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;embedding_model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.openrouter_client&lt;/span&gt;&lt;span class="nf"&gt;.embedding_model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;EMBEDDING_MODEL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;QueryPointsBuilder&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.collection_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.with_payload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;top_k&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;vector_store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;QdrantVectorStore&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.client&lt;/span&gt;&lt;span class="nf"&gt;.clone&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;embedding_model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;VectorSearchRequest&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;.query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.samples&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;top_k&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vector_store&lt;/span&gt;&lt;span class="py"&gt;.top_n&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nn"&gt;serde_json&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Vec&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;file_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="nf"&gt;.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"file_path"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;.and_then&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="nf"&gt;.as_str&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="nf"&gt;.unwrap_or_default&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;chunk_index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="nf"&gt;.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"chunk_index"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;.and_then&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="nf"&gt;.as_u64&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="nf"&gt;.unwrap_or_default&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="nf"&gt;.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;.and_then&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="nf"&gt;.as_str&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="nf"&gt;.unwrap_or_default&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="nf"&gt;.push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RetrievedChunk&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chunk_index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;f32&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rig handles embedding the question internally — that's what &lt;code&gt;vector_store.top_n(...)&lt;/code&gt; does under the hood. Results come back as &lt;code&gt;(score, id, payload)&lt;/code&gt; tuples. We destructure them and pull &lt;code&gt;file_path&lt;/code&gt;, &lt;code&gt;chunk_index&lt;/code&gt;, and &lt;code&gt;text&lt;/code&gt; out of the JSON payload with safe defaults so a malformed point can't crash the whole query.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;src/llm.rs&lt;/code&gt; — building the prompt and calling the model
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[derive(Clone)]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;LlmService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;openrouter&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="n"&gt;LlmService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;Self&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;openrouter&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_env&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="nf"&gt;.context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to create Rig OpenRouter client from OPENROUTER_API_KEY"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;Self&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;answer_question&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;RetrievedChunk&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="nf"&gt;.is_empty&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"could not find relevant content for: {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;build_context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;r#"
            You are a helpful Rust AI assistant.
            Answer the question using only the provided document context.

            Rules:
            - Be clear and concise.
            - If the context is not enough, say so.
            - Mention the source file when useful.
            - Do not invent facts outside the context.

            Context:
            {}

            Question:
            {}

            Answer:
            "#&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.client&lt;/span&gt;
            &lt;span class="nf"&gt;.agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"openai/gpt-4o-mini"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;.preamble&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"You answer questions using retrieved chunks as grounded context."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;.build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="nf"&gt;.prompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;
            &lt;span class="nf"&gt;.context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to generate answer with Rig agent"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;build_context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;RetrievedChunk&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;String&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;chunks&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="nf"&gt;.push_str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Source: {}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Chunk: {}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Score: {:.4}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Text: {}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="py"&gt;.file_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="py"&gt;.chunk_index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="py"&gt;.score&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="py"&gt;.text&lt;/span&gt;
        &lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;context&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is where retrieval finally meets generation. &lt;code&gt;build_context&lt;/code&gt; formats each retrieved chunk into a labeled block so the model can tell them apart and cite them. The main prompt drops that block in, asks the question, and adds explicit grounding rules.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;"Do not invent facts outside the context"&lt;/strong&gt; line matters more than people think. Without it, models cheerfully fill gaps with training-data plausibilities — which is exactly the hallucination problem RAG is supposed to fix. The "If the context is not enough, say so" rule is equally important: it tells the model that "I don't know" is an acceptable answer.&lt;/p&gt;

&lt;p&gt;If retrieval returns nothing (empty chunks), we short-circuit and never call the LLM — saves a token bill and produces a clearer "no relevant content found" message.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;src/rag.rs&lt;/code&gt; — orchestration
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[derive(Clone)]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;RagEngine&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;file_dir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;qdrant_store&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;QdrantStore&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;LlmService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;#[derive(Debug)]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;RagAnswer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;sources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;RetrievedChunk&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="n"&gt;RagEngine&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_dir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="nb"&gt;Into&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;qdrant_store&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;QdrantStore&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;LlmService&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;Self&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;Self&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;file_dir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;file_dir&lt;/span&gt;&lt;span class="nf"&gt;.into&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;qdrant_store&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;llm&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.qdrant_store&lt;/span&gt;&lt;span class="nf"&gt;.ensure_collection&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;reindex_docs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;load&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;loader_documents_from_dir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.file_dir&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;chunks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;chunk_retrieve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.qdrant_store&lt;/span&gt;&lt;span class="nf"&gt;.reset_collection&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;indexed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.qdrant_store&lt;/span&gt;&lt;span class="nf"&gt;.upsert_chunks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;indexed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;ask_question&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;RagAnswer&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.qdrant_store&lt;/span&gt;&lt;span class="nf"&gt;.search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;answer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.llm&lt;/span&gt;&lt;span class="nf"&gt;.answer_question&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RagAnswer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;RagEngine&lt;/code&gt; is the conductor. It doesn't know how PDFs are parsed, how chunks become vectors, or how the LLM is called — it just sequences the pieces. That's the entire point: when you want to swap Qdrant for Weaviate or OpenRouter for a local model, you change one file and everything else is fine.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;top_k = 3&lt;/code&gt; is conservative. Bigger &lt;code&gt;k&lt;/code&gt; means more context and higher cost; smaller means crisper but riskier. Tune it for your corpus.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;#[derive(Clone)]&lt;/code&gt; is what makes the engine cheap to hand to the gRPC handler. The expensive things inside (&lt;code&gt;Qdrant&lt;/code&gt; client, OpenRouter client) are themselves cheaply cloneable — they're just &lt;code&gt;Arc&lt;/code&gt;-wrapped connection pools.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;src/grpc_service.rs&lt;/code&gt; — the network surface
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[derive(Clone)]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;RagGrpcService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;RagEngine&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;#[tonic::async_trait]&lt;/span&gt;
&lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="n"&gt;RagService&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;RagGrpcService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;reindex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ReindexRequest&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ReindexResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;engine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.engine&lt;/span&gt;&lt;span class="nf"&gt;.clone&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;tokio&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;spawn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;move&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="nf"&gt;.reindex_docs&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;
                &lt;span class="nf"&gt;.map_err&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nn"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;internal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;chunks_indexed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;
            &lt;span class="nf"&gt;.map_err&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nn"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;internal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;&lt;span class="o"&gt;??&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ReindexResponse&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;chunks_indexed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;chunks_indexed&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Indexed {} chunk into qdrant"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chunks_indexed&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;}))&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;ask_question&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AskQuestionRequest&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AskQuestionResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;question&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="nf"&gt;.into_inner&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="py"&gt;.question&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="nf"&gt;.trim&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.is_empty&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;invalid_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"question cannot be empty"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;rag_answer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.engine&lt;/span&gt;&lt;span class="nf"&gt;.ask_question&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;
            &lt;span class="nf"&gt;.map_err&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nn"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;internal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;sources&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rag_answer&lt;/span&gt;&lt;span class="py"&gt;.sources&lt;/span&gt;&lt;span class="nf"&gt;.into_iter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.map&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;Source&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="py"&gt;.file_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;chunks_indexed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="py"&gt;.chunk_index&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;u32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;preview&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="py"&gt;.text&lt;/span&gt;&lt;span class="nf"&gt;.chars&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.take&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;180&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.collect&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="py"&gt;.score&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="nf"&gt;.collect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AskQuestionResponse&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rag_answer&lt;/span&gt;&lt;span class="py"&gt;.answer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;sources&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}))&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tonic handlers are thin — they unpack the request, validate, call the engine, pack the response. Two details worth pointing out.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;reindex&lt;/code&gt; uses &lt;code&gt;tokio::spawn&lt;/code&gt; because indexing can take a while (hundreds of OpenRouter embedding calls). Spawning lets it run on a dedicated task; the handler awaits the join handle and propagates errors. For a production system you'd probably make this fire-and-forget with a separate status endpoint, but for our scale, await-and-wait is fine.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;&lt;code&gt;preview: c.text.chars().take(180).collect()&lt;/code&gt;&lt;/strong&gt; truncates the chunk to 180 characters before sending it over the wire. Full chunks could be hundreds of words; the preview is enough to let the user verify the source without bloating the response.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;src/main.rs&lt;/code&gt; — boot
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[tokio::main]&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nn"&gt;anyhow&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nn"&gt;dotenvy&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;dotenv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.ok&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nn"&gt;tracing_subscriber&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;.with_target&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.compact&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.init&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_env&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;addr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="nf"&gt;.server_addr&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;qdrant_store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;QdrantStore&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="py"&gt;.qdrant_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="py"&gt;.qdrant_collection&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;llm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;LlmService&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;engine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;RagEngine&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"./docs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;qdrant_store&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="nf"&gt;.initialize&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;grpc_service&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;RagGrpcService&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nd"&gt;info!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"CodeRAG-rs gRPC server running on {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;addr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nn"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;.add_service&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;RagServiceServer&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;grpc_service&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="nf"&gt;.serve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;addr&lt;/span&gt;&lt;span class="nf"&gt;.parse&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(())&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Linear and easy to follow: load &lt;code&gt;.env&lt;/code&gt;, set up logging, build config, construct the three services in dependency order (&lt;code&gt;QdrantStore&lt;/code&gt; → &lt;code&gt;LlmService&lt;/code&gt; → &lt;code&gt;RagEngine&lt;/code&gt;), call &lt;code&gt;initialize()&lt;/code&gt; to make sure the Qdrant collection exists, then start the tonic server. The &lt;code&gt;#[tokio::main]&lt;/code&gt; attribute turns this into an async runtime entry point.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;src/bin/chat.rs&lt;/code&gt; — the terminal client
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[tokio::main]&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;addr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"RAG_SERVER"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.unwrap_or_else&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="s"&gt;"http://127.0.0.1:50051"&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;RagServiceClient&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;addr&lt;/span&gt;&lt;span class="nf"&gt;.clone&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;
        &lt;span class="nf"&gt;.with_context&lt;/span&gt;&lt;span class="p"&gt;(||&lt;/span&gt; &lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"could not reach RAG server at {addr}"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Type a question and press Enter. Commands: /reindex, /quit"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;stdin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;io&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;stdin&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;stdout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;io&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stdin&lt;/span&gt;&lt;span class="nf"&gt;.lock&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;String&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;loop&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nd"&gt;print!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"you &amp;gt; "&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="nf"&gt;.flush&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.ok&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="nf"&gt;.clear&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="nf"&gt;.read_line&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;   &lt;span class="c1"&gt;// Ctrl-D&lt;/span&gt;

        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="nf"&gt;.trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="nf"&gt;.is_empty&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"/quit"&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"/exit"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"/reindex"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="nf"&gt;.reindex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ReindexRequest&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="nf"&gt;.into_inner&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"indexed {} chunks — {}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="py"&gt;.chunks_indexed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="py"&gt;.message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="nf"&gt;.ask_question&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AskQuestionRequest&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="nf"&gt;.into_inner&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;bot &amp;gt; {}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="py"&gt;.answer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="py"&gt;.sources&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;preview&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="py"&gt;.preview&lt;/span&gt;&lt;span class="nf"&gt;.chars&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.take&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.collect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"  - {} (score {:.3})&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;    {}…"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="py"&gt;.file_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="py"&gt;.score&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;preview&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(())&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a separate binary that &lt;strong&gt;connects to the running server over gRPC&lt;/strong&gt;. The same &lt;code&gt;tonic::include_proto!("rag")&lt;/code&gt; macro that gave the server its types gives the client a strongly-typed &lt;code&gt;RagServiceClient&lt;/code&gt;. The loop reads from stdin, dispatches commands (&lt;code&gt;/reindex&lt;/code&gt;, &lt;code&gt;/quit&lt;/code&gt;), and otherwise sends the input as a question.&lt;/p&gt;

&lt;p&gt;Two binaries from one codebase. Production-friendly: you can deploy the server in Docker and ship the chat client to developers' laptops, and they share types automatically.&lt;/p&gt;




&lt;h2&gt;
  
  
  ▶️ Running it end-to-end
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Build everything
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/Parikalp-Bhardwaj/qrag-rust
&lt;span class="nb"&gt;cd &lt;/span&gt;qrag-rust
cargo build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first build is slow (tonic, qdrant-client, and rig pull a lot of crates). Subsequent builds are quick.&lt;/p&gt;

&lt;h3&gt;
  
  
  Start Qdrant and the server
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
cargo run &lt;span class="nt"&gt;--bin&lt;/span&gt; qrag-rust
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INFO CodeRAG-rs gRPC server running on 127.0.0.1:50051
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Index your docs (via grpcurl)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;grpcurl &lt;span class="nt"&gt;-plaintext&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-import-path&lt;/span&gt; proto &lt;span class="nt"&gt;-proto&lt;/span&gt; rag.proto &lt;span class="se"&gt;\&lt;/span&gt;
  127.0.0.1:50051 rag.RagService/Reindex
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Expected response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"chunksIndexed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"386"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Indexed 386 chunk into qdrant"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Ask a question
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;grpcurl &lt;span class="nt"&gt;-plaintext&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"question":"What is tokio?"}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-import-path&lt;/span&gt; proto &lt;span class="nt"&gt;-proto&lt;/span&gt; rag.proto &lt;span class="se"&gt;\&lt;/span&gt;
  127.0.0.1:50051 rag.RagService/AskQuestion
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Expected response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"answer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Tokio is an asynchronous runtime for Rust that enables programs to run many async tasks concurrently. It is useful for background jobs, concurrent I/O, network servers, and worker tasks. The `tokio::spawn` function is utilized to start a new asynchronous task that runs independently within the Tokio runtime. (Source: ./docs/tokio.md)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sources"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"filePath"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./docs/tokio.md"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"preview"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"# Tokio Runtime Tokio is an asynchronous runtime for Rust. It allows programs to run many async tasks concurrently. tokio::spawn is used to start a new asynchronous task. The spawn"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"score"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.5813062&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"filePath"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./docs/Rust-for-Network-Programming-and-Automation.pdf"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"chunksIndexed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;230&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"preview"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"resources like sockets and orchestrating the event loop that drives the application. It provides an API for registering interest in I/O events (e.g., data arriving on a socket or a"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"score"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.51516765&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"filePath"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./docs/Rust-for-Network-Programming-and-Automation.pdf"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"chunksIndexed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;233&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"preview"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"With the help of Tokio's abstractions and asynchronous I/O, you can create scalable network apps that can manage any number of connections at once. Hyper: High-Level HTTP Library H"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"score"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.48589033&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll get an answer plus the source chunks that produced it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Or use the chat CLI
&lt;/h3&gt;

&lt;p&gt;In a second terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cargo run &lt;span class="nt"&gt;--bin&lt;/span&gt; chat
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;you &amp;gt; what is TCP

bot &amp;gt; TCP, or Transmission Control Protocol, is a core protocol within the TCP/IP suite designed to facilitate reliable data transmission over networks. It ensures that data packets are transmitted accurately and in the correct order by using a process known as the three-way handshake for establishing connections. TCP also incorporates mechanisms for error detection, correction, and retransmission, ensuring data integrity and reliability even in complex network environments. This makes TCP essential for applications that require robust communication.

(Source: ./docs/Rust-for-Network-Programming-and-Automation.pdf)

  sources:
    - ./docs/Rust-for-Network-Programming-and-Automation.pdf  (score 0.546)
      which is then encapsulated in an IP packet, and finally in a link layer frame. T…
    - ./docs/Rust-for-Network-Programming-and-Automation.pdf  (score 0.540)
      requirements. Its robustness ensures reliable data transmission even in complex …
    - ./docs/Rust-for-Network-Programming-and-Automation.pdf  (score 0.526)
      as OSPF (Open Shortest Path First) and BGP (Border Gateway Protocol), help route…
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Commands: &lt;code&gt;/reindex&lt;/code&gt; to rebuild, &lt;code&gt;/quit&lt;/code&gt; to exit.&lt;/p&gt;




&lt;h2&gt;
  
  
  ⚠️ Gotchas worth knowing
&lt;/h2&gt;

&lt;p&gt;A handful of papercuts you'll save time on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Vector dimension mismatch.&lt;/strong&gt; If you initialize the collection at the wrong dimension, every upsert fails. Drop the collection (&lt;code&gt;docker compose down -v&lt;/code&gt;) and let it recreate. &lt;code&gt;text-embedding-3-small&lt;/code&gt; is 1536-dim.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't commit &lt;code&gt;.env&lt;/code&gt;.&lt;/strong&gt; If you already did, rotate the key — git history is forever.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OpenRouter for embeddings.&lt;/strong&gt; OpenRouter is primarily a completion router but does pass through OpenAI's embedding models. If embeddings start erroring, point the embedding client directly at OpenAI and keep OpenRouter for completion.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🔭 Where to go from here
&lt;/h2&gt;

&lt;p&gt;The system you just built is the simplest thing that works. Things to try next, in rough order of payoff:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Better chunking.&lt;/strong&gt; Sentence-aware splits, sliding-window overlap, or semantic chunking. Probably the single biggest quality lever.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reranking.&lt;/strong&gt; After Qdrant returns top-20, use a cross-encoder to rerank down to top-3. Higher precision at the cost of latency.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hybrid retrieval.&lt;/strong&gt; Combine vector search with BM25 keyword search. Catches exact-match queries that pure vector search misses.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Streaming responses.&lt;/strong&gt; gRPC supports server streaming — tokens can land in the client as the LLM produces them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Per-tenant collections.&lt;/strong&gt; One Qdrant collection per user or per workspace.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Observability.&lt;/strong&gt; Trace every step — embedding latency, search latency, LLM latency. RAG systems get slow in surprising places.&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;If you want a structured course on RAG fundamentals in Rust — chunking strategies, retrieval evaluation, embedding selection — check out &lt;a href="https://codesignal.com/learn/paths/foundations-of-retrieval-augmented-generation-rag-systems-with-rust" rel="noopener noreferrer"&gt;Foundations of RAG Systems with Rust&lt;/a&gt; on CodeSignal. Pair it with this post: course for the &lt;em&gt;why&lt;/em&gt;, this build for the &lt;em&gt;how&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  💭 Closing thoughts
&lt;/h2&gt;

&lt;p&gt;Modern AI engineering isn't really about models anymore. The models are a commodity you call over HTTP. The interesting work is everywhere else — splitting documents intelligently, indexing vectors at scale, routing requests with low latency, streaming results without blocking, keeping memory and concurrency under control.&lt;/p&gt;

&lt;p&gt;That's systems engineering. And it's exactly the territory Rust is built for. Strong types, async without garbage collection, predictable performance, and a tooling ecosystem (tonic, tokio, rig, qdrant-client) that's quietly catching up to anything Python has for AI infrastructure.&lt;/p&gt;

&lt;p&gt;You don't need Rust to build RAG. You need Rust when RAG turns into a real system someone depends on.&lt;/p&gt;

&lt;p&gt;Build the thing. Read the source. Break it. That's where the understanding comes from. 🦀&lt;/p&gt;




&lt;h2&gt;
  
  
  🙌 Thanks for reading
&lt;/h2&gt;

&lt;p&gt;If you made it this far — seriously, thank you. 🙏&lt;/p&gt;

&lt;p&gt;I'd love to hear what you're building, what didn't work for you, or what you'd do differently. Feel free to drop a comment, open an issue on the repo, or reach out on LinkedIn — always happy to talk Rust, AI, RAG, or anything in between.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;🔗 Links&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;👤 Connect with me on &lt;a href="https://www.linkedin.com/in/parikalp-bhardwaj/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📦 Project repo: &lt;a href="https://github.com/Parikalp-Bhardwaj/qrag-rust" rel="noopener noreferrer"&gt;github.com/Parikalp-Bhardwaj/qrag-rust&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📘 &lt;a href="https://qdrant.tech/documentation/" rel="noopener noreferrer"&gt;Qdrant docs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📘 &lt;a href="https://docs.rig.rs" rel="noopener noreferrer"&gt;Rig docs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📘 &lt;a href="https://github.com/hyperium/tonic" rel="noopener noreferrer"&gt;Tonic guide&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📘 &lt;a href="https://openrouter.ai" rel="noopener noreferrer"&gt;OpenRouter&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>rust</category>
      <category>rag</category>
      <category>ai</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
