Reading Rust

Part 6 · Speaking Rust

Explaining Rust to your team

The last lesson. Scripts for the seven conversations a tech lead actually has about Rust — including the one where the honest answer is no.

45 minauthentic taskarticulation
What this lesson is trying to break (2)
  • A lead advocating for Rust should emphasise its strengths.
  • You cannot review Rust code unless you can write it.

The last lesson. Everything so far has been about understanding Rust; this is about the position you will actually be in — a lead who understands Rust deeply but does not write it daily, in a room where a decision is being made.

1. “Should we use Rust for this?”

The most important thing here is that your credibility comes from being willing to say no. A lead who advocates for Rust in every context is discounted; a lead who says “not here, and here’s why” is listened to when they say yes.

A usable frame:

“Rust is a good fit when at least one of three things is true: performance is a product requirement rather than a preference; we need predictability — no GC pauses, bounded memory; or correctness matters more than iteration speed, because the type system genuinely catches things. If none of those is true, the cost is real and I’d rather not pay it.”

And the costs, stated plainly:

  • Ramp-up is real. Google published data on this: experienced engineers reaching self-rated productivity in roughly two months, with about a third needing longer. That is a real number to plan with, and better than folklore in either direction.
  • Compile times affect iteration. Not fatal, but you feel it every day.
  • Hiring pool is smaller — though people who want to write Rust are usually easy to attract.
  • Library maturity varies wildly by domain. Web and CLI: excellent. Some enterprise integrations: thin.

Where the answer is usually no: an ordinary CRUD service, I/O-bound, with a team that knows Go or TypeScript well and a deadline. You would spend the performance win on ramp-up and never notice the difference in production.

Where the answer is usually yes: a hot path already causing problems, a component where GC pauses hurt (streaming, games, trading, embedded), something compiled to WASM, a CLI you ship as a binary, or replacing a C/C++ component where the alternative is memory-unsafe.

The strongest form of yes is small: one service, one component, one binary. Not a rewrite.

2. “Why is the build so slow?”

You can now answer this properly, which most people cannot.

“Three main causes. Monomorphisation — generic code produces a separate copy per concrete type, so a heavily generic dependency multiplies out. LLVM then optimises every one of those copies. And crate granularity — a crate is the unit of recompilation, so one enormous crate rebuilds entirely on any change.”

Practical levers, roughly in order of payoff:

  • Split into more crates so changes recompile less.
  • cargo check while iterating — type-checks without code generation, and is dramatically faster.
  • Fewer generic instantiations on hot paths — sometimes one dyn in a non-critical place removes a lot of build time.
  • Faster linker (lld or mold), which is often the single biggest easy win.
  • cargo-bloat and cargo llvm-lines to find which generics are actually expensive.
  • Cache in CIsccache, or just a properly configured target cache.

3. “What does Arc<Mutex<T>> cost?”

Arc is an atomic refcount — cloning is an atomic increment, and dropping is a decrement plus a branch. Cheap, but not free, and it can contend if many threads clone the same one constantly. Mutex is uncontended-fast and contended-slow, like any lock. The thing that usually matters is not the primitive, it’s how long we hold the lock — and in Rust that’s the scope of the guard, which is easy to get wrong by accident.”

Then the follow-up that shows you have actually thought about it:

“The real questions are: is there I/O inside the critical section? Could this be an atomic instead? Would a channel express the ownership better? And if it’s async — is this a std mutex held across an .await?”

4. “Is unsafe acceptable in our codebase?”

“Yes, with conditions. unsafe doesn’t turn off the borrow checker — it unlocks five specific abilities and moves responsibility for some invariants onto us. Vec and Mutex are unsafe inside; that’s normal and fine.”

The conditions to state:

  1. Confined to a small, clearly marked module — not scattered.
  2. Every block has a // SAFETY: comment giving the actual argument.
  3. It runs under Miri in CI.
  4. There is a measured reason. “It’s faster” without a benchmark is not one.
  5. Two people review it, and one of them understands the invariant.

For dependencies, the useful question is not “does it contain unsafe” but “does it run Miri in CI, and is the unsafe confined?”

5. “Why can’t we just clone everything?”

This comes up when someone is frustrated, so the answer should start by agreeing.

“Often you can, and it’s fine. A String clone at a request boundary is invisible. The problem isn’t cloning — it’s cloning as the only tool, because then nobody ever learns where ownership should actually live, and eventually there’s a clone in a loop that matters.”

“My rule: clone freely while you’re getting it working, then look at the clones once. Most stay. The ones in hot paths get fixed, and fixing them usually improves the design anyway. And note that Arc::clone isn’t a deep copy at all — half the clones people worry about are refcount bumps.”

6. “Should we rewrite X in Rust?”

Almost always no, and it is worth being direct.

“Rewrites lose accumulated bug fixes, and the second system is usually worse before it’s better. If the current thing works, the case has to be about something Rust specifically fixes — memory safety in a C component, GC pauses we can measure, a cost we can put a number on.”

“What I’d rather do is put Rust at a boundary. A new service, a hot component called over FFI or a socket, a CLI tool. We learn on something with a real deadline but a small blast radius, and we find out whether the team likes it before we bet anything.”

7. Reviewing Rust when you are not fluent

The most practically useful thing in this lesson. You do not need to follow every line to review usefully — read the signatures.

Varies: what you can check without reading a single function body

Look atAskWhy it matters
ParametersOwned or borrowed? Is String taken where &str would do?Forces callers to allocate unnecessarily.
Return typesResult with what error type? Box<dyn Error> in a library?Callers cannot match on an opaque error.
BoundsIs each one used? Is this generic for a reason?Over-generic APIs cost build time and readability.
unwrap / expectIn library code? Does expect state the invariant?Turns a recoverable case into a crash.
unsafeIs there a // SAFETY: comment?Its absence is the review comment.
LocksWhere does the guard’s scope end? Any I/O inside?The scope is the critical section.
.awaitAny non-Send guard live across it? Sequential awaits that should be join!?Silent latency, or a compile error you can predict.
let _ =Deliberately discarding a Result?Where ignored errors hide.

Every row is visible in the diff without following the logic. This is a genuinely effective review pass and it is available to you now.

The exit test

You are done with this course when you can do these, out loud, without notes:

  1. Explain to a JavaScript developer why let t = s; breaks, in under a minute, without saying “ownership” until you have described what happens.
  2. Explain what a lifetime is without using the word “time”.
  3. Explain why Arc<Mutex<T>> exists, starting from the fact that ownership is a tree.
  4. Explain why calling an async fn prints nothing.
  5. State exactly what Rust prevents and exactly what it does not.
  6. Read an unfamiliar generic signature and say who chooses each type parameter.
  7. Give the four-sentence summary of a crate you had not seen an hour earlier.
The last onewrite it — thinking it does not count

Pick the conversation from this lesson you are most likely to have in the next three months. Write your actual script — not notes, the words you would say.

Then find a colleague and have it. That is the articulation step, and it is the one part of this course the site cannot do for you: the research on cognitive apprenticeship is clear that explaining aloud to a real person does something that rehearsing internally does not.

Where to go now

  • Keep the Review queue running. Most of what you have built here decays without retrieval, and fifteen minutes a week holds it.
  • Read one crate a month using the procedure from 5.3. Reading is the skill this course was built for, and it is the one that decays fastest without practice.
  • Write something small. A CLI tool, a hundred lines. You have deliberately not been taught to write Rust, and at some point the reading model needs to be tested against a compiler that disagrees with you.
  • Skim This Week in Rust. Twenty minutes weekly is enough to stay current on a language you do not write daily.

And check Calibration one last time. Compare your confidence against your accuracy now versus at the pretest in 0.2. If the gap has narrowed, that is the real result of this course — not the facts, but knowing how far to trust yourself when you are about to say something in a design review.

In the wildgo read the real thing now, while it is fresh
  • Google's Rust productivity studyReal data on ramp-up time from a large organisation. Useful because it is specific, published, and contradicts the folklore in both directions — worth citing rather than paraphrasing.
  • Rust in production case studiesInterviews with teams running Rust in production, including the parts that went badly. Far more useful for a real adoption discussion than any advocacy post.
Scheduled for recall3 items added to your review queue

These are not shown to you now on purpose. They will surface in Review tomorrow, mixed in among items from other parts of the course — not grouped by topic. Sorting out which idea applies is the part that transfers.