Skip to content

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 typeOption<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. unsafe reopens every door in the first table — that is what the keyword is for, and MaybeUninit, raw pointers and union are the named exceptions to its rows. The guarantee is not that the doors are locked; it is that they are labelled, so grep unsafe finds 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 Send and Sync rule 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 Trait costs 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. &str has to become a NUL-terminated CString, 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_ptr is roughly Box, shared_ptr is Arc, and const-correctness is the ancestor of & versus &mut. Three things change. Moves are the default rather than an opt-in std::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: Option is None with the check made compulsory, and Result is an exception that travels as a return value, so try/except becomes something the type system counts. What changes is the width of a number — a Python int is unbounded, a Rust one is not — and that len() 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 is E0502 before 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 TO can be initial and dereferencing it dumps with CX_SY_REF_IS_INITIAL at run time; Option<Box<T>> is that same check moved to compile time, so IS BOUND becomes the only way to reach the value at all. Arithmetic overflow raises CX_SY_ARITHMETIC_OVERFLOW when 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 a LOOP AT over 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>> and Weak here, 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, whereas Send and Sync are 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ą Send i Sync. 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 debug panikuje, release zawija 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. unsafe otwiera 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 unsafe znajduje 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