Language Internals
Part 1 of 11 · Rust Language ProficiencyRust — Beginner to Advanced (from TypeScript & Python)
Hub: beginner–advanced Rust for TS/Python engineers — ownership, traits, Result, async; Rosetta ownership/Result/concurrency.
- 1Gist
- 2Maps
- 3Q&A
- 4Sandbox
Voice readout needs Web Speech Synthesis in this browser.
Question ladder
L1
What is the single shift from TypeScript or Python?
Answer
Stop assuming aliasing and mutation are free. Design the API around who owns the data.
L2
When does a value move, and when does it copy?
Answer
String and Vec move. i32 and bool are Copy. After a move the source name is gone unless you clone on purpose.
L3
What does a shared borrow forbid?
Answer
An overlapping exclusive borrow. Many readers, or one writer. The checker rejects the overlap.
L4
How do Result and Option replace throw and null?
Answer
Failure and absence are in the type. Callers should handle it, propagate with ?, or discard it on purpose (ignoring a Result is a must_use warning).
L5
impl Trait or dyn Trait?
Answer
impl Trait and generics monomorphize a known type. dyn Trait is a vtable for a heterogeneous collection.
L6
Does async fn start a thread?
Answer
No. The future is lazy. A runtime polls it. Multi-thread spawn also requires Send.
L7
When is unsafe the right next step?
Answer
After a release profile shows a real hotspot that safe code cannot express. Keep the block tiny and comment the invariant.
Failure modes
Fighting the borrow checker
Cloning every argument or wrapping everything in Rc instead of shortening the borrow.
Unwrap in a library
A panic crosses an API that should have returned Result.
Blocking the async runtime
A sync file or CPU loop runs on a Tokio worker.
Clone soup
Every call clones so the checker stays quiet, and the hot path allocates.
Lifetime annotations on day one
Structs that store references before moves and borrows are solid.
Misconceptions
Lifetimes are a garbage collector.
They are compile-time names for how long a borrow is valid. They are erased, like TypeScript types, and they do not free memory at runtime.
Rust has no shared mutation.
RefCell, Mutex, and atomics allow it. The type records who may mutate.
async is multi-threaded by default.
A future does nothing until a runtime polls it. The runtime chooses the thread pool.
unsafe means the program is insecure.
unsafe means the compiler does not check the invariants. A sound wrapper can still be a safe API.
Interviewer traps
Answering ownership with a GC diagram.
Name the owner, the borrow, and when Drop runs.
Saying clone is the way you share.
Pass a shared borrow for a read. Clone only when two owners of equal data are required.
Treating a Tokio task like a JavaScript Promise that starts on call.
The future is lazy. Spawn or await it, and keep the task Send if the runtime is multi-thread.
Design scenario
Same prompt for every reader.
Requirements
Async HTTP on Tokio, a shared cache behind Arc and RwLock or an actor, typed errors in the domain, and context only at the binary edge.
Traffic / scale
Hundreds of in-flight requests, each awaiting a few sockets and one cache lookup.
Latency
Socket waits overlap on the runtime. The cache lock must not be held across an await.
Consistency
A failed downstream call returns Result and does not publish a partial cache write.
Availability
A worker must not block on sync disk or a CPU loop.
Failure assumptions
- The cache is a plain HashMap shared by alias, as in Python.
- Errors are thrown and caught only at the framework boundary.
- A std Mutex is held across an await.
Constraints
- Stay on the language. Do not redesign a queue cluster.
- Name the owner of the cache and the error type split.
Prompt
Port a small HTTP service that fans out downstream calls, keeps a shared cache, and records metrics.
What this cluster trains
Prefer
Ownership without a collector
One owner, explicit borrows, and failure in the type. You can explain a move and a Send bound without a framework diagram.
- A move invalidates the source for non-Copy types.
- Shared and exclusive borrows do not overlap.
- Async is a lazy future plus a runtime.
Alternative
GC aliasing as the default
TypeScript and Python keep the object alive while any name points at it. That is the habit this cluster replaces.
- Assignment aliases the same object.
- Exceptions are invisible in the signature.
- A Promise or coroutine starts on the host loop.
Read the cluster in this order
Each page is one mechanism. The hub is only the map.
- 1
Toolchain
rustup, Cargo, edition 2024, and the lockfile. - 2
Ownership
Moves, borrows, and lifetimes. - 3
Types
Structs, enums, and match. - 4
Traits
Generics, impl Trait, and dyn Trait. - 5
Errors
Result, Option, and propagation. - 6
Iterators
Vec, HashMap, lazy iterators, and closures. - 7
Pointers
Box, Rc and Arc, and interior mutability. - 8
Concurrency
Threads, Send and Sync, then async on a runtime. - 9
Unsafe
A small boundary for FFI, after safe code is profiled. - 10
Testing
cargo test, clippy, and production API patterns.
Overview
This cluster is Rust for people who already ship TypeScript and Python. The core model is ownership at compile time, not a garbage collector.
Verified on 2026-10-01: stable rustc 1.98.1 (the 1.98.1 point release of 2026-09-03), edition 2024. Sources are the stable release notes and the stable channel manifest. 1.99.0 was still beta at that write. Update 2026-10-05: 1.99.0 shipped as stable on 2026-10-01; the samples need no changes for it.
The JS/TS language hub and the Python language hub are the parallel intuition for a host loop and a GIL. They are not a substitute for moves, Result, or Send.
Comparative map
| Concern | TypeScript | Python | Rust |
|---|---|---|---|
| Memory | GC | GC | Ownership and drop |
| Missing value | null or undefined | None | Option |
| Errors | throw | exceptions | Result |
| Concurrency | event loop and workers | GIL, asyncio, processes | std thread (true parallelism; closure must be Send + 'static, or use thread::scope) plus async |
| Shared mutation | default | default | exclusive borrow, or interior mutability |
| Packages | npm or pnpm | pip, uv, or poetry | Cargo and crates.io |
Who owns the value
Decisions
- 1
Step 1 let s = String::from - s owns the heap buffer
- nextStep 2 How is s used next
- ?
Step 2 How is s used next
- let t = sStep 3a Move - t owns it, s is unusable
- pass &sStep 3b Shared borrow - many readers
- pass &mut sStep 3c Exclusive borrow - one writer
- 3
Step 3a Move - t owns it, s is unusable
- nextStep 5 Owner leaves scope - Drop frees memory once
- use s after the moveFailure path - compile error E0382
- 4
Step 3b Shared borrow - many readers
- nextStep 4 Borrow ends at its last use
- 5
Step 3c Exclusive borrow - one writer
- nextStep 4 Borrow ends at its last use
- 6
Step 4 Borrow ends at its last use
- nextStep 5 Owner leaves scope - Drop frees memory once
- 7
Step 5 Owner leaves scope - Drop frees memory once
- 8
Failure path - compile error E0382
Lesson map
Rust — Beginner to Advanced (from TypeScript & Python)
Hub: beginner–advanced Rust for TS/Python engineers — ownership, traits, Result, async; Rosetta ownership/Result/concurrency.
Architecture. Step 1 let s = String::from - s owns the heap buffer Ready. Step 2 How is s used next Ready. Step 3a Move - t owns it, s is unusable Ready. Step 3b Shared borrow - many readers Ready. Step 3c Exclusive borrow - one writer Ready. Step 4 Borrow ends at its last use Ready. Step 5 Owner leaves scope - Drop frees memory once Ready. Failure path - compile error E0382 Ready
Select a node to see why it exists, or an edge to see the protocol, direction, effect, and consequence.
Mermaid export
flowchart TB A["Step 1 let s = String::from - s owns the heap buffer Ready"] B["Step 2 How is s used next Ready"] M["Step 3a Move - t owns it, s is unusable Ready"] R["Step 3b Shared borrow - many readers Ready"] W["Step 3c Exclusive borrow - one writer Ready"] E["Step 4 Borrow ends at its last use Ready"] D["Step 5 Owner leaves scope - Drop frees memory once Ready"] F["Failure path - compile error E0382 Ready"] A -->|continues| B B -->|let t = s| M B -->|pass &s| R B -->|pass &mut s| W R -->|continues| E W -->|continues| E M -->|continues| D E -->|continues| D M -->|use s after the move| F
Press Run. Snippets must be self-contained — no network, files, or native modules.
Rosetta — move versus alias
Rust moves a non-Copy String. The source is gone.
fn takes(s: String) {
println!("owned {s}");
}
fn main() {
let s = String::from("hello");
takes(s);
// println!("{s}"); // used after move
}TypeScript aliases the same object. The garbage collector decides later.
function takes(s: { text: string }) {
console.log("owned", s.text);
}
const s = { text: "hello" };
takes(s);
console.log(s.text);Python binds another name to the same object.
def takes(s: str) -> None:
print("owned", s)
s = "hello"
takes(s)
print(s)To keep using the data in Rust, pass a shared borrow or clone on purpose.
Rosetta — Result versus throw
fn parse_port(s: &str) -> Result<u16, std::num::ParseIntError> {
s.parse()
}
fn main() {
match parse_port("8080") {
Ok(p) => println!("port {p}"),
Err(e) => eprintln!("bad: {e}"),
}
}function parsePort(s: string): number {
const n = Number.parseInt(s, 10);
if (Number.isNaN(n)) throw new Error(`bad: ${s}`);
return n;
}def parse_port(s: str) -> int:
return int(s)Rust puts failure in the type. TypeScript and Python unwind with an exception the signature does not have to name.
Rosetta — a small concurrent sketch
use std::thread;
fn main() {
let handle = thread::spawn(|| 21 * 2);
println!("{}", handle.join().unwrap());
}async function main() {
const v = await Promise.resolve(21 * 2);
console.log(v);
}
main();import asyncio
async def main() -> None:
v = await asyncio.sleep(0, result=21 * 2)
print(v)
asyncio.run(main())A Rust thread is an OS thread (true parallelism; the closure must be Send + 'static, or use thread::scope). A JavaScript Promise stays on the host loop. Python asyncio is cooperative on one thread unless you add processes. Rust async still needs an explicit runtime.
What the rest of the cluster adds
- Toolchain covers rustup, Cargo, and edition 2024.
- Ownership covers moves, borrows, and lifetimes.
- Types covers structs, enums, and match.
- Traits covers generics and dyn Trait.
- Errors covers Result, Option, and propagation.
- Collections, pointers, concurrency, unsafe, and tests close the ring.
Interview Q&A
What is the most important shift from TypeScript or Python?
Answer
Stop assuming aliasing and mutation are free. Design APIs around who owns the data.
When does Rust lose to Go, TypeScript, or Python?
Answer
Junior-heavy CRUD often fits a simpler service language. Product UI stays in TypeScript. Notebooks stay in Python. Rust fits predictable latency and embeddable libraries. Hybrids are normal.
Should you memorize every lifetime on day one?
Answer
No. Master moves and the two borrow kinds first. Write a lifetime when a struct stores a reference.
Which rustc and edition do these samples use?
Answer
Stable rustc 1.98.1 and edition 2024. An edition is the crate's dialect. Dependencies keep their own editions.
Why is a Promise not the same as thread spawn?
Answer
A Promise schedules on the host event loop. Rust plus std thread is true parallelism; the closure must be Send + 'static, or use thread::scope.
Where do the other language hubs fit?
Answer
Read the JS/TS hub for the event-loop habit and the Python hub for the GIL. Come back here for ownership and Result.
What is Drop?
Answer
The owner leaving scope runs the destructor. There is no later janitor pass for that value.
What is the first sentence of the answer?
Answer
Name the owner, the borrow or the Result, and the runtime if the question is async. Then stop.
Pitfalls
- Cloning every value so the checker stays quiet.
- Storing references in structs before you can move and borrow.
- unwrap in library code.
- Blocking a Tokio worker with sync I/O.
- Treating unsafe as a style escape.
Take a TypeScript service that shares a cache object and throws on a bad port. Name the Rust owner of the cache, the error type, and the sibling page you would open first.