Benefits of Rust¶
Level: 101 · for newcomers
One line: The twenty selling points on Google's list are three different kinds of claim — a build that fails, a run-time behaviour that is written down, and a convenience — and telling them apart is the difference between trusting the list and taking it on faith.
Comprehensive Rust ↗ opens with a list of what Rust buys you: compile-time memory safety, no undefined run-time behaviour, modern language features. The list is accurate and this page does not argue with it. What it adds is the evidence behind each bullet, because "no use-after-free" and "built-in dependency manager" are true in two completely different senses — one is a theorem the compiler enforces, the other is that somebody wrote a good tool — and a reader who cannot tell them apart ends up believing all twenty equally or none of them.
Every row links twice. The claim goes to C and C++ — the bug it is a reply to, compiled and run, so you can see what it costs when nobody prevents it. See it in goes to the page here where you can watch Rust's side happen.
The two groups below that link that way are the ones where there is a bug to show. The third group is features, and the claims there link nowhere but the lesson.
Compile-time memory safety¶
This whole group is prevented by the build failing. There is nothing to run and no output to record: the evidence is an error message.
| The claim | What it actually says | See it in |
|---|---|---|
| No uninitialized variables | let b: Ballot; is legal — reading b before some path assigns it is E0381, and the compiler checks every path |
A type is not a constructor |
| No double-frees | One value, one owner, so exactly one binding will free it; a move transfers that responsibility rather than duplicating it | Ownership and moves |
| No use-after-free | A reference may not outlive what it points at, and 'a is how you name which "what" when the compiler cannot work it out alone |
Borrowing · Lifetime annotations |
No NULL pointers |
Absence is a different type — Option<Box<T>> — which costs the same eight bytes and cannot be read without saying what happens when it is empty |
Nullable pointers · Some and None |
| No forgotten locked mutexes | There is no unlock() to forget: the guard's destructor releases the lock. Binding it to _, which drops it immediately, is a deny-by-default lint |
Scope is about names · Mutex poisoning |
| No data races between threads | Send and Sync decide what may cross a thread boundary — Rc may not and Arc may — so the mistake is a build error rather than a wrong answer once a fortnight |
Sharing across threads |
| No iterator invalidation | You cannot hold a reference into a collection and mutate the collection in the same breath. E0502 — the same borrow rule as every other row, aimed at a loop |
while let · String slices |
No undefined run-time behaviour¶
These two are not prevented. They happen, and what happens is written down — which is the actual contrast with C, where the standard declines to say and the answer can differ per run. See undefined behaviour.
| The claim | What it actually says | See it in |
|---|---|---|
| Array access is bounds checked | v[9] on a three-element vector aborts the program; it does not hand you whatever was next in memory. v.get(9) is the same question asked without the abort, and returns None |
Partial functions · What a panic costs |
| Integer overflow is defined (panic or wrap-around) | Both — selected by the build profile, which is the trap. Every integer also carries wrapping_, checked_, saturating_ and overflowing_, so you can put the decision in the method name |
Meet the byte |
Modern language features¶
Nothing here is a safety guarantee, and this is the half of the list that decides whether you enjoy the language.
| The claim | What it actually says | See it in |
|---|---|---|
| Enums and pattern matching | A value is exactly one of a closed set, and a match that forgets a variant is a build error rather than a branch nobody wrote |
What an enum is · Variants that carry data |
| Generics | <T> is a type the caller fills in: written once, checked once, then stamped out per type your program actually uses |
What a generic is |
| No overhead FFI | extern "C" is an ordinary C call. No marshalling layer, no JNI, no ctypes — and no runtime on your side to start up first |
Calling C |
| Zero-cost abstractions | Ask for optimization and a loop summing an array comes out as the answer, with no loop and no array left in the machine code | What the optimizer does · Iterators are lazy · Static vs dynamic dispatch |
| Great compiler errors | The message usually contains the fix, and a warning is a question about your intent rather than a complaint about your style | What a warning is asking |
| Built-in dependency manager | cargo add rayon and it is in the build — though what it writes into the manifest is a version range, not the version you got |
Cargo dependencies |
| Built-in support for testing | #[test] and cargo test ship with the toolchain, so there is no framework to choose and no argument to have about it |
nextest · Commit on green |
| Excellent Language Server Protocol support | Every editor worth using is a window onto the same program, rust-analyzer, so the intelligence is the same and only the window differs |
Editors |
The fine print¶
Six of the twenty are narrower than the one-line version, and each is narrower in a way that eventually costs somebody a day.
- All of it means safe Rust.
unsafereopens every door in the first table — that is what the keyword is for, andMaybeUninit, raw pointers andunionare the named exceptions to its rows. The guarantee is not that the doors are locked; it is that they are labelled, sogrep unsafefinds every place the compiler stopped checking. - "No
NULL" does not mean absence went away. It means absence became a value with a type, which you must open before you can use what is inside..unwrap()is still a way to end the program on an empty one — the slide is about the pointer, not about the problem. - "No data races" is much narrower than "no concurrency bugs." A data race is two threads touching one location, at least one of them writing, with no synchronisation; that is what
SendandSyncrule out. Deadlocks, lost updates, and ordering bugs in your own logic remain entirely available — and it takes only one lock, correctly used: release it between checking a balance and spending it and the account goes overdrawn, with no data race anywhere in the program. Take two locks at once on one thread and it deadlocks, with nothing objecting at compile time. - "Integer overflow is defined" defines it as two different things. A debug build panics and a release build wraps. So an overflow bug can pass every test you run and still wrap in production, which is the one consequence to internalise from that row.
- "Zero-cost abstractions" is a claim about most of them. Generics and iterator chains do compile away.
dyn Traitcosts a pointer indirection and blocks inlining — deliberately, because being able to decide at run time is the feature you are paying for. - "No overhead FFI" is about the call, not the data. Crossing the boundary is free; getting your data into a shape C recognises often is not.
&strhas to become a NUL-terminatedCString, which allocates and copies, because Rust strings carry a length instead of a terminator.
If you are coming from another language¶
Google's speaker notes suggest asking the room which languages they already write, and pitching accordingly. Here are the four pitches, with what actually transfers and what changes.
- C or C++ — this is the audience the list was written for, and the whole section written for you runs the nine bugs the first two tables are about. Most of it already has a name in your head: RAII is how the mutex row works,
unique_ptris roughlyBox,shared_ptrisArc, andconst-correctness is the ancestor of&versus&mut. Three things change. Moves are the default rather than an opt-instd::move, and a moved-from value is unusable rather than "valid but unspecified" — so there is no state to reason about. The borrow rule is checked rather than documented, which is the one that costs you a fortnight and then pays back forever. And the question "is this undefined behaviour?" stops being askable in safe code, which quietly removes a whole genre of code review. - Python — you already never free anything, so ownership is the genuinely new idea rather than a stricter version of an old one. What transfers immediately:
OptionisNonewith the check made compulsory, andResultis an exception that travels as a return value, sotry/exceptbecomes something the type system counts. What changes is the width of a number — a Pythonintis unbounded, a Rust one is not — and thatlen()counts bytes here and characters there. And the row that will surprise you most is iterator invalidation:for x in lst: lst.remove(x)is a bug Python lets you write, and it silently skips elements —[1, 2, 3, 4]comes back as[2, 4]— while the same shape in Rust isE0502before the program runs. Threads too — the GIL made data races rare rather than impossible, and the free-threaded builds now arriving remove even that accident. - ABAP — three rows map onto things you already do by hand.
TYPE REF TOcan be initial and dereferencing it dumps withCX_SY_REF_IS_INITIALat run time;Option<Box<T>>is that same check moved to compile time, soIS BOUNDbecomes the only way to reach the value at all. Arithmetic overflow raisesCX_SY_ARITHMETIC_OVERFLOWwhen it happens, where Rust wants the range argued before the run and puts your answer in the method name. And deleting from an internal table inside aLOOP ATover that same table is iterator invalidation exactly — ABAP permits it and leaves the consequences to you, where Rust refuses to build it. What does not transfer is the runtime: there is no work process, no roll area and no short dump to open afterwards, so the failures you are used to reading in ST22 have to be handled where they occur or the program ends. - Java, Go or JavaScript — you get the memory safety you already have, and keep the high-level feel, plus predictable performance with no collector pause and access to the hardware if you ever need it. The bill is the collector's convenience: a cyclic structure your language builds without being asked needs
Rc<RefCell<T>>andWeakhere, and the compiler will argue with you about it. For Go specifically, the channels and lightweight tasks are all present, but the race detector is a run-time tool that finds races on the paths your tests happened to exercise, whereasSendandSyncare checked on all of them.
See also¶
- Start here — the plan, and the three free resources it is built around
- Measured claims — the other list: six claims that arrive with a number attached, and what each number counted
- Comprehensive Rust in the shelf — what the course is good at, and the one cost of its slide format
- C and C++ — the nine bugs the first two tables prevent, each one compiled and run
- Undefined behaviour — the term the second table is really about
Po polsku¶
Ta strona nie streszcza listy zalet, tylko ją sortuje: dwadzieścia punktów rozpada się na trzy zupełnie różne rodzaje twierdzeń — takie, gdzie dowodem jest nieudana kompilacja, takie, gdzie dowodem jest spisane zachowanie w czasie działania, i takie, gdzie chodzi po prostu o wygodę. W polskich omówieniach Rust przyjeżdża zwykle jako jedno słowo — „bezpieczny” — i to jest pierwszy kłopot, bo polskie bezpieczny skleja dwa angielskie: safe i secure. Rust jest bezpieczny pamięciowo (memory-safe), co jest twierdzeniem o zarządzaniu pamięcią, a nie o bezpieczeństwie systemu. Nie chroni przed wstrzyknięciem SQL, źle dobraną kryptografią ani sekretem w repozytorium. Jeśli w polskiej dyskusji ma paść jedno zdanie, niech to będzie „bezpieczny pamięciowo”, z przymiotnikiem.
Druga tabela na tej stronie mówi o czymś, czego polska nazwa myli jeszcze bardziej. Undefined behaviour tłumaczy się jako „zachowanie niezdefiniowane” albo „nieokreślone”, co brzmi jak „nieprzewidywalne”, „losowe” — a znaczy coś ostrzejszego: standard odmawia powiedzenia, co się stanie, więc optymalizator ma prawo założyć, że taka sytuacja nigdy nie zachodzi, i wyciąć kod, który zakłada inaczej. Dlatego te dwa punkty — sprawdzanie zakresu tablicy i przepełnienie liczby całkowitej — nie są w tabeli jako „Rust jest bezpieczniejszy”, tylko jako „Rust to zapisał”. Zapisane zachowanie bywa nieprzyjemne (v[9] kończy program paniką), ale jest jedno i to samo przy każdym uruchomieniu.
Z „drobnego druku” trzy pozycje warto znać po polsku dokładnie, bo w każdej polskie nazewnictwo dokłada się do pomyłki:
- „Brak wyścigów danych” to nie „brak błędów współbieżności”. Po polsku słowo „wyścig” obsługuje i data race, i race condition, a to nie to samo. Wyścig danych — dwa wątki, jedno miejsce w pamięci, co najmniej jeden zapis, brak synchronizacji — jest tym, co wykluczają
SendiSync. Zakleszczenie (deadlock), zgubiona aktualizacja i zwykły błąd w kolejności twoich własnych operacji kompilują się bez jednego ostrzeżenia — i nie trzeba do tego dwóch blokad: wystarczy jedna, użyta poprawnie, ale zwolniona między sprawdzeniem salda a jego wydaniem, żeby konto zeszło na minus. - „Przepełnienie jest zdefiniowane” definiuje je jako dwie różne rzeczy. Kompilacja
debugpanikuje,releasezawija wartość — więc błąd potrafi przejść wszystkie testy i zawinąć się dopiero na produkcji. To jedyny wniosek z tego wiersza, który naprawdę trzeba zapamiętać. - Wszystko powyżej dotyczy Rusta bezpiecznego.
unsafeotwiera z powrotem każde drzwi z pierwszej tabeli — i o to w tym słowie kluczowym chodzi. Gwarancja nie polega na tym, że drzwi są zamknięte, tylko na tym, że są opisane:grep unsafeznajduje wszystkie miejsca, w których kompilator przestał sprawdzać.
Praktyczny wniosek na polskie dyskusje o Ruscie: kłóć się kodem błędu, nie przymiotnikiem. „Rust jest bezpieczny” to slogan, który druga strona odbije równie ogólnym zdaniem. „Ten program się nie skompiluje, E0502, i oto komunikat” to argument, który da się sprawdzić w trzydzieści sekund, jest identyczny w każdym języku i nie wymaga niczyjego zaufania. Kody błędów, nazwy cech (Send, Sync, Copy) i słowa kluczowe zostają po angielsku właśnie dlatego, że są adresem, pod który można pójść.
Szukaj po polsku: bezpieczeństwo pamięci w Ruscie · wyścig danych a zakleszczenie · zachowanie niezdefiniowane · przepełnienie liczby całkowitej Rust · rust memory safety guarantees · rust overflow debug vs release