Rust Language Cheat Sheet 27. June 2025
Contains clickable links to The Book,BK Rust by Example,EX Std Docs,STD Nomicon,NOM and Reference.REF
Clickable symbols
BKThe Book.
EXRust by Example.
STDStandard Library (API).
NOMNomicon.
REFReference.
RFCOfficial RFC documents.
🔗The internet.
↑On this page, above.
↓On this page, below.
Other symbols
🗑️Largely deprecated.
'18Has minimum edition requirement.
🚧Requires Rust nightly (or is incomplete).
🛑Intentionally wrong example or pitfall.
🝖Slightly esoteric, rarely used or advanced.
🔥Something with outstanding utility.
↪The parent item expands to …
💬Opinionated.
?Is missing good link or explanation.
X-Ray visualizations are enabled. These show aggregated feedback per section. Right now this is experimental. Known caveats:
- some sections that receive lots of feedback aren't shown (e.g., "Hello Rust")
- it does not account for age of the section (some sections have been around for many years, others for just a few months)
- it does neither account for feedback bursts (people mashing the same button 3 times), nor "most recent feedback" (people up- then down-voting).
The feedback format is (positive, negative, textual), equivalent to use of the feedback buttons.
Language Constructs
- Data Structures
- References & Pointers
- Functions & Behavior
- Control Flow
- Organizing Code
- Type Aliases and Casts
- Macros & Attributes
- Pattern Matching
- Generics & Constraints
- Higher-Ranked Items🝖
- Strings & Chars
- Documentation
- Miscellaneous
Behind the Scenes
Memory Layout
Misc
Standard Library
- One-Liners
- Thread Safety
- Atomics & Cache🝖
- Iterators
- Number Conversions
- String Conversions
- String Output
Tooling
Working with Types
Coding Guides
- Idiomatic Rust
- Performance Tips
- Async-Await 101
- Closures in APIs
- Unsafe, Unsound, Undefined
- Adversarial Code🝖
- API Stability
Hello, Rust!url
If you are new to Rust, or if you want to try the things below:
Hello WorldPoints you might run into
- Steep learning curve;1 compiler enforcing (esp. memory) rules that would be "best practices" elsewhere.
- Missing Rust-native libs in some domains, target platforms (esp. embedded), IDE features.1
- Longer compile times than "similar" code in other languages.1
- Careless (use of unsafe in) libraries can secretly break safety guarantees.
- No formal language specification, 🔗 can prevent legal use in some domains (aviation, medical, …). 🔗
- Rust Foundation may offensively use their IP to affect 'Rust' projects (e.g, forbid names, impose policies). 🔗🔗2
1 Compare Rust Survey.
2 Avoiding their marks (e.g, in your name, URL, logo, dress) is probably sufficient.
Modular Beginner Resources
- Tour of Rust - Live code and explanations, side by side.
- Rust in Easy English - 60+ concepts, simple English, example-driven.
- Rust for the Polyglot Programmer - A guide for the experienced programmer.
In addition consider The Book,BK Rust by Example,EX the Standard Library,STD and Learn Rust.🔗
Opinion 💬 — If you have never seen or used any Rust it might be good to visit one of the links above before continuing; the next chapter might feel a bit terse otherwise.
Data Structuresurl
Data types and memory locations defined via keywords.
| struct S {} | Define a struct BK EX STD REF with named fields. |
| struct S { x: T } | Define struct with named field x of type T. |
| struct S (T); | Define "tupled" struct with numbered field .0 of type T. |
| struct S; | Define zero sized NOM unit struct. Occupies no space, optimized away. |
| enum E {} | Define an enum, BK EX REF c. algebraic data types, tagged unions. |
| enum E { A, B(), C {} } | Define variants of enum; can be unit- A, tuple- B () and struct-like C{}. |
| enum E { A = 1 } | Enum with explicit discriminant values, REF e.g., for FFI. |
| enum E {} | Enum w/o variants is uninhabited, REF can't be created, c. 'never' ↓ 🝖 |
| union U {} | Unsafe C-like union REF for FFI compatibility. 🝖 |
| static X: T = T(); | Global variable BK EX REF with 'static lifetime, single 🛑1 memory location. |
| const X: T = T(); | Defines constant, BK EX REF copied into a temporary when used. |
| let x: T; | Allocate T bytes on stack2 bound as x. Assignable once, not mutable. |
| let mut x: T; | Like let, but allow for mutability BK EX and mutable borrow.3 |
| x = y; | Moves y to x, inval. y if T is not Copy, STD and copying y otherwise. |
1 In libraries you might secretly end up with multiple instances of X, depending on how your crate is imported. 🔗
2 Bound variables BK EX REF live on stack for synchronous code. In async {} they become part of async's state machine, may reside on heap.
3 Technically mutable and immutable are misnomer. Immutable binding or shared reference may still contain Cell STD, giving interior mutability.
Creating and accessing data structures; and some more sigilic types.
| S { x: y } | Create struct S {} or use'ed enum E::S {} with field x set to y. |
| S { x } | Same, but use local variable x for field x. |
| S { ..s } | Fill remaining fields from s, esp. useful with Default::default(). STD |
| S { 0: x } | Like S (x) below, but set field .0 with struct syntax. |
| S (x) | Create struct S (T) or use'ed enum E::S () with field .0 set to x. |
| S | If S is unit struct S; or use'ed enum E::S create value of S. |
| E::C { x: y } | Create enum variant C. Other methods above also work. |
| () | Empty tuple, both literal and type, aka unit. STD |
| (x) | Parenthesized expression. |
| (x,) | Single-element tuple expression. EX STD REF |
| (S,) | Single-element tuple type. |
| [S] | Array type of unspec. length, i.e., slice. EX STD REF Can't live on stack. * |
| [S; n] | Array type EX STD REF of fixed length n holding elements of type S. |
| [x; n] | Array instance REF (expression) with n copies of x. |
| [x, y] | Array instance with given elements x and y. |
| x[0] | Collection indexing, here w. usize. Impl. via Index, IndexMut. |
| x[..] | Same, via range (here full range), also x[a..b], x[a..=b], … c. below. |
| a..b | Right-exclusive range STD REF creation, e.g., 1..3 means 1, 2. |
| ..b | Right-exclusive range to STD without starting point. |
| ..=b | Inclusive range to STD without starting point. |
| a..=b | Inclusive range, STD 1..=3 means 1, 2, 3. |
| a.. | Range from STD without ending point. |
| .. | Full range, STD usually means the whole collection. |
| s.x | Named field access, REF might try to Deref if x not part of type S. |
| s.0 | Numbered field access, used for tuple types S (T). |
* For now,RFC pending completion of tracking issue.
References & Pointersurl
Granting access to un-owned memory. Also see section on Generics & Constraints.
| &S | Shared reference BK STD NOM REF (type; space for holding any &s). |
| &[S] | Special slice reference that contains (addr, count). |
| &str | Special string slice reference that contains (addr, byte_len). |
| &mut S | Exclusive reference to allow mutability (also &mut [S], &mut dyn S, …). |
| &dyn T | Special trait object BK REF ref. as (addr, vtable); T must be object safe. REF |
| &s | Shared borrow BK EX STD (e.g., addr., len, vtable, … of this s, like 0x1234). |
| &mut s | Exclusive borrow that allows mutability. EX |
| *const S | Immutable raw pointer type BK STD REF w/o memory safety. |
| *mut S | Mutable raw pointer type w/o memory safety. |
| &raw const s | Create raw pointer w/o going through ref.; c. ptr:addr_of!() STD 🝖 |
| &raw mut s | Same, but mutable. 🚧 Needed for unaligned, packed fields. 🝖 |
| ref s | Bind by reference, EX makes binding reference type. 🗑️ |
| let ref r = s; | Equivalent to let r = &s. |
| let S { ref mut x } = s; | Mut. ref binding (let x = &mut s.x), shorthand destructuring ↓ version. |
| *r | Dereference BK STD NOM a reference r to access what it points to. |
| *r = s; | If r is a mutable reference, move or copy s to target memory. |
| s = *r; | Make s a copy of whatever r references, if that is Copy. |
| s = *r; | Won't work 🛑 if *r is not Copy, as that would move and leave empty. |
| s = *my_box; | Special case🔗 for BoxSTD that can move out b'ed content not Copy. |
| 'a | A lifetime parameter, BK EX NOM REF duration of a flow in static analysis. |
| &'a S | Only accepts address of some s; address existing 'a or longer. |
| &'a mut S | Same, but allow address content to be changed. |
| struct S<'a> {} | Signals this S will contain address with lt. 'a. Creator of S decides 'a. |
| trait T<'a> {} | Signals any S, which impl T for S, might contain address. |
| fn f<'a>(t: &'a T) | Signals this function handles some address. Caller decides 'a. |
| 'static | Special lifetime lasting the entire program execution. |
Functions & Behaviorurl
Define units of code and their abstractions.
| trait T {} | Define a trait; BK EX REF common behavior types can adhere to. |
| trait T : R {} | T is subtrait of supertrait BK EX REF R. Any S must impl R before it can impl T. |
| impl S {} | Implementation REF of functionality for a type S, e.g., methods. |
| impl T for S {} | Implement trait T for type S; specifies how exactly S acts like T. |
| impl !T for S {} | Disable an automatically derived auto trait. NOM REF 🚧 🝖 |
| fn f() {} | Definition of a function; BK EX REF or associated function if inside impl. |
| fn f() -> S {} | Same, returning a value of type S. |
| fn f(&self) {} | Define a method, BK EX REF e.g., within an impl S {}. |
| struct S (T); | More arcanely, also↑ defines fn S(x: T) -> S constructor fn. RFC 🝖 |
| const fn f() {} | Constant fn usable at compile time, e.g., const X: u32 = f(Y). REF '18 |
| const { x } | Used within a function, ensures { x } evaluated during compilation. REF |
| async fn f() {} | Async REF '18 function transform, ↓ makes f return an impl Future. STD |
| async fn f() -> S {} | Same, but make f return an impl Future<Output=S>. |
| async { x } | Used within a function, make { x } an impl Future<Output=X>. REF |
| async move { x } | Moves captured variables into future, c. move closure. REF ↓ |
| fn() -> S | Function references, 1 BK STD REF memory holding address of a callable. |
| Fn() -> S | Callable trait BK STD (also FnMut, FnOnce), impl. by closures, fn's … |
| AsyncFn() -> S | Callable async trait STD (also AsyncFnMut, AsyncFnOnce), impl. by async c. |
| || {} | A closure BK EX REF that borrows its captures, ↓ REF (e.g., a local variable). |
| |x| {} | Closure accepting one argument named x, body is block expression. |
| |x| x + x | Same, without block expression; may only consist of single expression. |
| move |x| x + y | Move closure REF taking ownership; i.e., y transferred into closure. |
| async |x| x + x | Async closure. REF Converts its result into an impl Future<Output=X>. |
| async move |x| x + y | Async move closure. Combination of the above. |
| return || true | Closures sometimes look like logical ORs (here: return a closure). |
| unsafe | If you enjoy debugging segfaults; unsafe code. ↓ BK EX NOM REF |
| unsafe fn f() {} | Means "calling can cause UB, ↓ YOU must check requirements". |
| unsafe trait T {} | Means "careless impl. of T can cause UB; implementor must check". |
| unsafe { f(); } | Guarantees to compiler "I have checked requirements, trust me". |
| unsafe impl T for S {} | Guarantees S is well-behaved w.r.t T; people may use T on S safely. |
| unsafe extern "abi" {} | Starting with Rust 2024 extern "abi" {} blocks ↓ must be unsafe. |
| pub safe fn f(); | Inside an unsafe extern "abi" {}, mark f is actually safe to call. RFC |
1 Most documentation calls them function pointers, but function references might be more appropriate🔗 as they can't be null and must point to valid target.
Control Flowurl
Control execution within a function.
| while x {} | Loop, REF run while expression x is true. |
| loop {} | Loop indefinitely REF until break. Can yield value with break x. |
| for x in collection {} | Syntactic sugar to loop over iterators. BK STD REF |
| ↪ collection.into_iter() | Effectively converts any IntoIterator STD type into proper iterator first. |
| ↪ iterator.next() | On proper Iterator STD then x = next() until exhausted (first None). |
| if x {} else {} | Conditional branch REF if expression is true. |
| 'label: {} | Block label, RFC can be used with break to exit out of this block. 1.65+ |
| 'label: loop {} | Similar loop label, EX REF useful for flow control in nested loops. |
| break | Break expression REF to exit a labelled block or loop. |
| break 'label x | Break out of block or loop named 'label and make x its value. |
| break 'label | Same, but don't produce any value. |
| break x | Make x value of the innermost loop (only in actual loop). |
| continue | Continue expression REF to the next loop iteration of this loop. |
| continue 'label | Same but instead of this loop, enclosing loop marked with 'label. |
| x? | If x is Err or None, return and propagate. BK EX STD REF |
| x.await | Syntactic sugar to get future, poll, yield. REF '18 Only inside async. |
| ↪ x.into_future() | Effectively converts any IntoFuture STD type into proper future first. |
| ↪ future.poll() | On proper Future STD then poll() and yield flow if Poll::Pending. STD |
| return x | Early return REF from fn. More idiomatic is to end with expression. |
| { return } | Inside normal {}-blocks return exits surrounding function. |
| || { return } | Within closures return exits that c. only, i.e., closure is s. fn. |
| async { return } | Inside async a return only REF 🛑 exits that {}, i.e., async {} is s. fn. |
| f() | Invoke callable f (e.g., a function, closure, function pointer, Fn, …). |
| x.f() | Call member fn, requires f takes self, &self, … as first argument. |
| X::f(x) | Same as x.f(). Unless impl Copy for X {}, f can only be called once. |
| X::f(&x) | Same as x.f(). |
| X::f(&mut x) | Same as x.f(). |
| S::f(&x) | Same as x.f() if X derefs to S, i.e., x.f() finds methods of S. |
| T::f(&x) | Same as x.f() if X impl T, i.e., x.f() finds methods of T if in scope. |
| X::f() | Call associated function, e.g., X::new(). |
| <X as T>::f() | Call trait method T::f() implemented for X. |
Organizing Codeurl
Segment projects into smaller units and minimize dependencies.
| mod m {} | Define a module, BK EX REF get definition from inside {}. ↓ |
| mod m; | Define a module, get definition from m.rs or m/mod.rs. ↓ |
| a::b | Namespace path EX REF to element b within a (mod, enum, …). |
| ::b | Search b in crate root '15 REF or ext. prelude; '18 REF global path. REF 🗑️ |
| crate::b | Search b in crate root. '18 |
| self::b | Search b in current module. |
| super::b | Search b in parent module. |
| use a::b; | Use EX REF b directly in this scope without requiring a anymore. |
| use a::{b, c}; | Same, but bring b and c into scope. |
| use a::b as x; | Bring b into scope but name x, like use std::error::Error as E. |
| use a::b as _; | Bring b anon. into scope, useful for traits with conflicting names. |
| use a::*; | Bring everything from a in, only recomm. if a is some prelude. STD 🔗 |
| pub use a::b; | Bring a::b into scope and reexport from here. |
| pub T | "Public if parent path is public" visibility BK REF for T. |
| pub(crate) T | Visible at most1 in current crate. |
| pub(super) T | Visible at most1 in parent. |
| pub(self) T | Visible at most1 in current module (default, same as no pub). |
| pub(in a::b) T | Visible at most1 in ancestor a::b. |
| extern crate a; | Declare dependency on external crate; BK REF 🗑️ just use a::b in '18. |
| extern "C" {} | Declare external dependencies and ABI (e.g., "C") from FFI. BK EX NOM REF |
| extern "C" fn f() {} | Define function to be exported with ABI (e.g., "C") to FFI. |
1 Items in child modules always have access to any item, regardless if pub or not.
Type Aliases and Castsurl
Short-hand names of types, and methods to convert one type to another.
| type T = S; | Create a type alias, BK REF i.e., another name for S. |
| Self | Type alias for implementing type, REF e.g., fn new() -> Self. |
| self | Method subject BK REF in fn f(self) {}, e.g., akin to fn f(self: Self) {}. |
| &self | Same, but refers to self as borrowed, would equal f(self: &Self) |
| &mut self | Same, but mutably borrowed, would equal f(self: &mut Self) |
| self: Box<Self> | Arbitrary self type, add methods to smart ptrs (my_box.f_of_self()). |
| <S as T> | Disambiguate BK REF type S as trait T, e.g., <S as T>::f(). |
| a::b as c | In use of symbol, import S as R, e.g., use a::S as R. |
| x as u32 | Primitive cast, EX REF may truncate and be a bit surprising. 1 NOM |
1 See Type Conversions below for all the ways to convert between types.
Macros & Attributesurl
Code generation constructs expanded before the actual compilation happens.
| m!() | Macro BK STD REF invocation, also m!{}, m![] (depending on macro). |
| #[attr] | Outer attribute, EX REF annotating the following item. |
| #![attr] | Inner attribute, annotating the upper, surrounding item. |
1 Applies to 'macros by example'. REF
2 See Tooling Directives below for all fragment specifiers.
Pattern Matchingurl
Constructs found in match or let expressions, or function parameters.
| match m {} | Initiate pattern matching, BK EX REF then use match arms, c. next table. |
| let S(x) = get(); | Notably, let also destructures EX similar to the table below. |
| let S { x } = s; | Only x will be bound to value s.x. |
| let (_, b, _) = abc; | Only b will be bound to value abc.1. |
| let (a, ..) = abc; | Ignoring 'the rest' also works. |
| let (.., a, b) = (1, 2); | Specific bindings take precedence over 'the rest', here a is 1, b is 2. |
| let s @ S { x } = get(); | Bind s to S while x is bnd. to s.x, pattern binding, BK EX REF c. below 🝖 |
| let w @ t @ f = get(); | Stores 3 copies of get() result in each w, t, f. 🝖 |
| let (|x| x) = get(); | Pathological or-pattern,↓ not closure.🛑 Same as let x = get(); 🝖 |
| let Ok(x) = f(); | Won't work 🛑 if p. can be refuted, REF use let else or if let instead. |
| let Ok(x) = f(); | But can work if alternatives uninhabited, e.g., f returns Result<T, !> 1.82+ |
| let Ok(x) = f() else {}; | Try to assign RFC if not else {} w. must break, return, panic!, … 1.65+ 🔥 |
| if let Ok(x) = f() {} | Branch if pattern can be assigned (e.g., enum variant), syntactic sugar. * |
| if let … && let … { } | Let chains, REF use more than binding w.o. nesting. '24 |
| while let Ok(x) = f() {} | Equiv.; here keep calling f(), run {} as long as p. can be assigned. |
| fn f(S { x }: S) | Function param. also work like let, here x bound to s.x of f(s). 🝖 |
* Desugars to match get() { Some(x) => {}, _ => () }.
Pattern matching arms in match expressions. Left side of these arms can also be found in let expressions.
Generics & Constraintsurl
Generics combine with type constructors, traits and functions to give your users more flexibility.
| struct S<T> … | A generic BK EX type with a type parameter (T is placeholder here). |
| S<T> where T: R | Trait bound, BK EX REF limits allowed T, guarantees T has trait R. |
| where T: R, P: S | Independent trait bounds, here one for T and one for (not shown) P. |
| where T: R, S | Compile error, 🛑 you probably want compound bound R + S below. |
| where T: R + S | Compound trait bound, BK EX T must fulfill R and S. |
| where T: R + 'a | Same, but w. lifetime. T must fulfill R, if T has lt., must outlive 'a. |
| where T: ?Sized | Opt out of a pre-defined trait bound, here Sized. ? |
| where T: 'a | Type lifetime bound; EX if T has references, they must outlive 'a. |
| where T: 'static | Same; does not mean value t will 🛑 live 'static, only that it could. |
| where 'b: 'a | Lifetime 'b must live at least as long as (i.e., outlive) 'a bound. |
| where u8: R<T> | Can also make conditional statements involving other types. 🝖 |
| S<T: R> | Short hand bound, almost same as above, shorter to write. |
| S<const N: usize> | Generic const bound; REF user of type S can provide constant value N. |
| S<10> | Where used, const bounds can be provided as primitive values. |
| S<{5+5}> | Expressions must be put in curly brackets. |
| S<T = R> | Default parameters; BK makes S a bit easier to use, but keeps flexible. |
| S<const N: u8 = 0> | Default parameter for constants; e.g., in f(x: S) {} param N is 0. |
| S<T = u8> | Default parameter for types, e.g., in f(x: S) {} param T is u8. |
| S<'_> | Inferred anonymous lt.; asks compiler to 'figure it out' if obvious. |
| S<_> | Inferred anonymous type, e.g., as let x: Vec<_> = iter.collect() |
| S::<T> | Turbofish STD call site type disambiguation, e.g., f::<u32>(). |
| E::<T>::A | Generic enums can receive their type parameters on their type E … |
| E::A::<T> | … or at the variant (A here); allows Ok::<R, E>(r) and similar. |
| trait T<X> {} | A trait generic over X. Can have multiple impl T for S (one per X). |
| trait T { type X; } | Defines associated type BK REF RFC X. Only one impl T for S possible. |
| trait T { type X<G>; } | Defines generic associated type (GAT), RFC X can be generic Vec<>. |
| trait T { type X<'a>; } | Defines a GAT generic over a lifetime. |
| type X = R; | Set associated type within impl T for S { type X = R; }. |
| type X<G> = R<G>; | Same for GAT, e.g., impl T for S { type X<G> = Vec<G>; }. |
| impl<T> S<T> {} | Impl. fn's for any T in S<T> generically, REF here T ty. parameter. |
| impl S<T> {} | Impl. fn's for exactly S<T> inherently, REF here T specific type, e.g., u8. |
| fn f() -> impl T | Existential types (aka RPIT), BK returns an unknown-to-caller S that impl T. |
| -> impl T + 'a | Signals the hidden type lives at least as long as 'a. RFC |
| -> impl T + use<'a> | Signals instead the hidden type captured lifetime 'a, use bound. 🔗 ? |
| -> impl T + use<'a, R> | Also signals the hidden type may have captured lifetimes from R. |
| -> S<impl T> | The impl T part can also be used inside type arguments. |
| fn f(x: &impl T) | Trait bound via "impl traits", BK similar to fn f<S: T>(x: &S) below. |
| fn f(x: &dyn T) | Invoke f via dynamic dispatch, BK REF f will not be instantiated for x. |
| fn f<X: T>(x: X) | Fn. generic over X, f will be instantiated ('monomorphized') per X. |
| fn f() where Self: R; | In trait T {}, make f accessible only on types known to also impl R. |
| fn f() where Self: Sized; | Using Sized can opt f out of trait object vtable, enabling dyn T. |
| fn f() where Self: R {} | Other R useful w. dflt. fn. (non dflt. would need be impl'ed anyway). |
Higher-Ranked Items 🝖url
Actual types and traits, abstract over something, usually lifetimes.
| for<'a> | Marker for higher-ranked bounds. NOM REF 🝖 |
| trait T: for<'a> R<'a> {} | Any S that impl T would also have to fulfill R for any lifetime. |
| fn(&'a u8) | Function pointer type holding fn callable with specific lifetime 'a. |
| for<'a> fn(&'a u8) | Higher-ranked type1 🔗 holding fn call. with any lt.; subtype↓ of above. |
| fn(&'_ u8) | Same; automatically expanded to type for<'a> fn(&'a u8). |
| fn(&u8) | Same; automatically expanded to type for<'a> fn(&'a u8). |
| dyn for<'a> Fn(&'a u8) | Higher-ranked (trait-object) type, works like fn above. |
| dyn Fn(&'_ u8) | Same; automatically expanded to type dyn for<'a> Fn(&'a u8). |
| dyn Fn(&u8) | Same; automatically expanded to type dyn for<'a> Fn(&'a u8). |
1 Yes, the for<> is part of the type, which is why you write impl T for for<'a> fn(&'a u8) below.
Strings & Charsurl
Rust has several ways to create textual values.
| "..." | String literal, REF, 1 a UTF-8 &'static str, STD supporting these escapes: |
| "\n\r\t\0\\" | Common escapes REF, e.g., "\n" becomes new line. |
| "\x36" | ASCII e. REF up to 7f, e.g., "\x36" would become 6. |
| "\u{7fff}" | Unicode e. REF up to 6 digits, e.g., "\u{7fff}" becomes 翿. |
| r"..." | Raw string literal. REF, 1UTF-8, but won't interpret any escape above. |
| r#"..."# | Raw string literal, UTF-8, but can also contain ". Number of # can vary. |
| c"..." | C string literal, REF a NUL-terminated &'static CStr, STD for FFI. 1.77+ |
| cr"...", cr#"..."# | Raw C string literal, combination analog to above. |
| b"..." | Byte string literal; REF, 1 constructs ASCII-only &'static [u8; N]. |
| br"...", br#"..."# | Raw byte string literal, combination analog to above. |
| b'x' | ASCII byte literal, REF a single u8 byte. |
| '🦀' | Character literal, REF fixed 4 byte unicode 'char'. STD |
1 Supports multiple lines out of the box. Just keep in mind Debug↓ (e.g., dbg!(x) and println!("{x:?}")) might render them as \n, while Display↓ (e.g., println!("{x}")) renders them proper.
Documentationurl
Debuggers hate him. Avoid bugs with this one weird trick.
| /// | Outer line doc comment,1 BK EX REF use these on ty., traits, fn's, … |
| //! | Inner line doc comment, mostly used at top of file. |
| // | Line comment, use these to document code flow or internals. |
| /* … */ | Block comment. 2 🗑️ |
| /** … */ | Outer block doc comment. 2 🗑️ |
| /*! … */ | Inner block doc comment. 2 🗑️ |
1 Tooling Directives outline what you can do inside doc comments.
2 Generally discouraged due to bad UX. If possible use equivalent line comment instead with IDE support.
Miscellaneousurl
These sigils did not fit any other category but are good to know nonetheless.
| ! | Always empty never type. BK EX STD REF |
| fn f() -> ! {} | Function that never ret.; compat. with any ty. e.g., let x: u8 = f(); |
| fn f() -> Result<(), !> {} | Function that must return Result but signals it can never Err. 🚧 |
| fn f(x: !) {} | Function that exists, but can never be called. Not very useful. 🝖 🚧 |
| _ | Unnamed wildcard REF variable binding, e.g., |x, _| {}. |
| let _ = x; | Unnamed assign. is no-op, does not 🛑 move out x or preserve scope! |
| _ = x; | You can assign anything to _ without let, i.e., _ = ignore_rval(); 🔥 |
| _x | Variable binding that won't emit unused variable warnings. |
| 1_234_567 | Numeric separator for visual clarity. |
| 1_u8 | Type specifier for numeric literals EX REF (also i8, u16, …). |
| 0xBEEF, 0o777, 0b1001 | Hexadecimal (0x), octal (0o) and binary (0b) integer literals. |
| 12.3e4, 1E-8 | Scientific notation for floating-point literals. REF |
| r#foo | A raw identifier BK EX for edition compatibility. 🝖 |
| 'r#a | A raw lifetime label ? for edition compatibility. 🝖 |
| x; | Statement REF terminator, c. expressions EX REF |
Common Operatorsurl
Rust supports most operators you would expect (+, *, %, =, ==, …), including overloading. STD Since they behave no differently in Rust we do not list them here.
Arcane knowledge that may do terrible things to your mind, highly recommended.
The Abstract Machineurl
Like C and C++, Rust is based on an abstract machine.
OverviewWith rare exceptions you are never 'allowed to reason' about the actual CPU. You write code for an abstracted CPU. Rust then (sort of) understands what you want, and translates that into actual RISC-V / x86 / … machine code.
This abstract machine
- is not a runtime, and does not have any runtime overhead, but is a computing model abstraction,
- contains concepts such as memory regions (stack, …), execution semantics, …
- knows and sees things your CPU might not care about,
- is de-facto a contract between you and the compiler,
- and exploits all of the above for optimizations.
Language Sugarurl
If something works that "shouldn't work now that you think about it", it might be due to one of these.
Opinion 💬 — These features make your life easier using Rust, but stand in the way of learning it. If you want to develop a genuine understanding, spend some extra time exploring them.
Memory & Lifetimesurl
An illustrated guide to moves, references and lifetimes.
Types & Moves- Application memory is just array of bytes on low level.
- Operating environment usually segments that, amongst others, into:
- stack (small, low-overhead memory,1 most variables go here),
- heap (large, flexible memory, but always handled via stack proxy like Box<T>),
- static (most commonly used as resting place for str part of &str),
- code (where bitcode of your functions reside).
- Most tricky part is tied to how stack evolves, which is our focus.
1 For fixed-size values stack is trivially manageable: take a few bytes more while you need them, discarded once you leave. However, giving out pointers to these transient locations form the very essence of why lifetimes exist; and are the subject of the rest of this chapter.
Variables S(1) S(1) a t Variables let t = S(1);- Reserves memory location with name t of type S and the value S(1) stored inside.
- If declared with let that location lives on stack. 1
- Note the linguistic ambiguity, in the term variable, it can mean the:
- name of the location in the source file ("rename that variable"),
- location in a compiled app, 0x7 ("tell me the address of that variable"),
- value contained within, S(1) ("increment that variable").
- Specifically towards the compiler t can mean location of t, here 0x7, and value within t, here S(1).
1 Compare above,↑ true for fully synchronous code, but async stack frame might placed it on heap via runtime.
Move Semantics S(1) a t Moves let a = t;- This will move value within t to location of a, or copy it, if S is Copy.
- After move location t is invalid and cannot be read anymore.
- Technically the bits at that location are not really empty, but undefined.
- If you still had access to t (via unsafe) they might still look like valid S, but any attempt to use them as valid S is undefined behavior. ↓
- We do not cover Copy types explicitly here. They change the rules a bit, but not much:
- They won't be dropped.
- They never leave behind an 'empty' variable location.
- The type of a variable serves multiple important purposes, it:
- dictates how the underlying bits are to be interpreted,
- allows only well-defined operations on these bits
- prevents random other values or bits from being written to that location.
- Here assignment fails to compile since the bytes of M::new() cannot be converted to form of type S.
- Conversions between types will always fail in general, unless explicit rule allows it (coercion, cast, …).
- Once the 'name' of a non-vacated variable goes out of (drop-)scope, the contained value is dropped.
- Rule of thumb: execution reaches point where name of variable leaves {}-block it was defined in
- In detail more tricky, esp. temporaries, …
- Drop also invoked when new value assigned to existing variable location.
- In that case Drop::drop() is called on the location of that value.
- In the example above drop() is called on a, twice on c, but not on t.
- Most non-Copy values get dropped most of the time; exceptions include mem::forget(), Rc cycles, abort().
- When a function is called, memory for parameters (and return values) are reserved on stack.1
- Here before f is invoked value in a is moved to 'agreed upon' location on stack, and during f works like 'local variable' x.
1 Actual location depends on calling convention, might practically not end up on stack at all, but that doesn't change mental model.
S(1) a x x Nested Functions fn f(x: S) { if once() { f(x) } // <- We are here (before recursion) } let a = S(1); f(a);- Recursively calling functions, or calling other functions, likewise extends the stack frame.
- Nesting too many invocations (esp. via unbounded recursion) will cause stack to grow, and eventually to overflow, terminating the app.
- Stack that previously held a certain type will be repurposed across (even within) functions.
- Here, recursing on f produced second x, which after recursion was partially reused for m.
Key take away so far, there are multiple ways how memory locations that previously held a valid value of a certain type stopped doing so in the meantime. As we will see shortly, this has implications for pointers.
- A reference type such as &S or &mut S can hold the location of some s.
- Here type &S, bound as name r, holds location of variable a (0x3), that must be type S, obtained via &a.
- If you think of variable c as specific location, reference r is a switchboard for locations.
- The type of the reference, like all other types, can often be inferred, so we might omit it from now on:let r: &S = &a; let r = &a;
- References can read from (&S) and also write to (&mut S) locations they point to.
- The dereference *r means to use the location r points to (not the location of or value within r itself)
- In the example, clone d is created from *r, and S(2) written to *r.
- We assume S implements Clone, and r.clone() clones target-of-r, not r itself.
- On assignment *r = … old value in location also dropped (not shown above).
- While bindings guarantee to always hold valid data, references guarantee to always point to valid data.
- Esp. &mut T must provide same guarantees as variables, and some more as they can't dissolve the target:
- They do not allow writing invalid data.
- They do not allow moving out data (would leave target empty w/o owner knowing).
- In contrast to references, pointers come with almost no guarantees.
- They may point to invalid or non-existent data.
- Dereferencing them is unsafe, and treating an invalid *p as if it were valid is undefined behavior. ↓
- Every entity in a program has some (temporal / spatial) extent where it is relevant, i.e., alive.
- Loosely speaking, this alive time can be1
- the LOC (lines of code) where an item is available (e.g., a module name).
- the LOC between when a location is initialized with a value, and when the location is abandoned.
- the LOC between when a location is first used in a certain way, and when that usage stops.
- the LOC (or actual time) between when a value is created, and when that value is dropped.
- Within the rest of this section, we will refer to the items above as the:
- scope of that item, irrelevant here.
- scope of that variable or location.
- lifetime2 of that usage.
- lifetime of that value, might be useful when discussing open file descriptors, but also irrelevant here.
- Likewise, lifetime parameters in code, e.g., r: &'a S, are
- concerned with LOC any location r points to needs to be accessible or locked;
- unrelated to the 'existence time' (as LOC) of r itself (well, it needs to exist shorter, that's it).
- &'static S means address must be valid during all lines of code.
1 There is sometimes ambiguity in the docs differentiating the various scopes and lifetimes. We try to be pragmatic here, but suggestions are welcome.
2 Live lines might have been a more appropriate term …
▼ S(0) S(1) S(2) 0xa a b c r Meaning of r: &'c S- Assume you got a r: &'c S from somewhere it means:
- r holds an address of some S,
- any address r points to must and will exist for at least 'c,
- the variable r itself cannot live longer than 'c.
- Assume you got a mut r: &mut 'c S from somewhere.
- That is, a mutable location that can hold a mutable reference.
- As mentioned, that reference must guard the targeted memory.
- However, the 'c part, like a type, also guards what is allowed into r.
- Here assigning &b (0x6) to r is valid, but &a (0x3) would not, as only &b lives equal or longer than &c.
- Once the address of a variable is taken via &b or &mut b the variable is marked as borrowed.
- While borrowed, the content of the address cannot be modified anymore via original binding b.
- Once address taken via &b or &mut b stops being used (in terms of LOC) original binding b works again.
- When calling functions that take and return references two interesting things happen:
- The used local variables are placed in a borrowed state,
- But it is during compilation unknown which address will be returned.
- Since f can return only one address, not in all cases b and c need to stay locked.
- In many cases we can get quality-of-life improvements.
- Notably, when we know one parameter couldn't have been used in return value anymore.
- Lifetime parameters in signatures, like 'c above, solve that problem.
- Their primary purpose is:
- outside the function, to explain based on which input address an output address could be generated,
- within the function, to guarantee only addresses that live at least 'c are assigned.
- The actual lifetimes 'b, 'c are transparently picked by the compiler at call site, based on the borrowed variables the developer gave.
- They are not equal to the scope (which would be LOC from initialization to destruction) of b or c, but only a minimal subset of their scope called lifetime, that is, a minmal set of LOC based on how long b and c need to be borrowed to perform this call and use the obtained result.
- In some cases, like if f had 'c: 'b instead, we still couldn't distinguish and both needed to stay locked.
- A variable location is unlocked again once the last use of any reference that may point to it ends.
Here (M) means compilation fails because mutability error, (L) lifetime error. Also, dereference *rb not strictly necessary, just added for clarity.
- f_sr cases always work, short reference (only living 'b) can always be produced.
- f_sm cases usually fail simply because mutable chain to S needed to return &mut S.
- f_lr cases can fail because returning &'a S from &'a mut S to caller means there would now exist two references (one mutable) to same S which is illegal.
- f_lm cases always fail for combination of reasons above.
Note: This example is about the f functions, not compute. You can assume it to be defined as fn compute(x: &S, y: &S) {}. In that case the ra parameter would be automatically coerced ↓ from &mut S to &S, since you can't have a shared and a mutable reference to the same target.
S(1)▼ _ Drop and _ { let f = |x, y| (S(x), S(y)); // Function returning two 'Droppables'. let ( x1, y) = f(1, 4); // S(1) - Scope S(4) - Scope let ( x2, _) = f(2, 5); // S(2) - Scope S(5) - Immediately let (ref x3, _) = f(3, 6); // S(3) - Scope S(6) - Scope println!("…"); }Here Scope means contained value lives until end of scope, i.e., past the println!().
- Functions or expressions producing movable values must be handled by callee.
- Values stores in 'normal' bindings are kept until end of scope, then dropped.
- Values stored in _ bindings are usually dropped right away.
- However, sometimes references (e.g., ref x3) can keep value (e.g., the tuple (S(3), S(6))) around for longer, so S(6), being part of that tuple can only be dropped once reference to its S(3) sibling disappears).
↕️ Examples expand by clicking.
Byte representations of common types.
Basic Typesurl
Essential types built into the core of the language.
Boolean REF and Numeric Types REFurl
bool u8, i8 u16, i16 u32, i32 u64, i64 u128, i128 usize, isize Same as ptr on platform. f16 🚧 f32 f64 f128 🚧| u8 | 255 |
| u16 | 65_535 |
| u32 | 4_294_967_295 |
| u64 | 18_446_744_073_709_551_615 |
| u128 | 340_282_366_920_938_463_463_374_607_431_768_211_455 |
| usize | Depending on platform pointer size, same as u16, u32, or u64. |
| i8 | 127 |
| i16 | 32_767 |
| i32 | 2_147_483_647 |
| i64 | 9_223_372_036_854_775_807 |
| i128 | 170_141_183_460_469_231_731_687_303_715_884_105_727 |
| isize | Depending on platform pointer size, same as i16, i32, or i64. |
| i8 | -128 |
| i16 | -32_768 |
| i32 | -2_147_483_648 |
| i64 | -9_223_372_036_854_775_808 |
| i128 | -170_141_183_460_469_231_731_687_303_715_884_105_728 |
| isize | Depending on platform pointer size, same as i16, i32, or i64. |
| f16 🚧 | 65504.0 | 6.10 ⋅ 10 -5 | 2048 |
| f32 | 3.40 ⋅ 10 38 | 3.40 ⋅ 10 -38 | 16_777_216 |
| f64 | 1.79 ⋅ 10 308 | 2.23 ⋅ 10 -308 | 9_007_199_254_740_992 |
| f128 🚧 | 1.19 ⋅ 10 4932 | 3.36 ⋅ 10 -4932 | 2.07 ⋅ 10 34 |
1 The maximum integer M so that all other integers 0 <= X <= M can be losslessly represented in that type. In other words, there might be larger integers that could still be represented losslessly (e.g., 65504 for f16), but up until that value a lossless representation is guaranteed.
Float values approximated for visual clarity. Negative limits are values multipled with -1.
Sample bit representation* for a f32:
S E E E E E E E E F F F F F F F F F F F F F F F F F F F F F F FExplanation:
| Normalized number | ± | 1 to 254 | any | ±(1.F)2 * 2E-127 |
| Denormalized number | ± | 0 | non-zero | ±(0.F)2 * 2-126 |
| Zero | ± | 0 | 0 | ±0 |
| Infinity | ± | 255 | 0 | ±∞ |
| NaN | ± | 255 | non-zero | NaN |
Similarly, for f64 types this would look like:
| Normalized number | ± | 1 to 2046 | any | ±(1.F)2 * 2E-1023 |
| Denormalized number | ± | 0 | non-zero | ±(0.F)2 * 2-1022 |
| Zero | ± | 0 | 0 | ±0 |
| Infinity | ± | 2047 | 0 | ±∞ |
| NaN | ± | 2047 | non-zero | NaN |
| 3.9_f32 as u8 | 3 | Truncates, consider x.round() first. |
| 314_f32 as u8 | 255 | Takes closest available number. |
| f32::INFINITY as u8 | 255 | Same, treats INFINITY as really large number. |
| f32::NAN as u8 | 0 | - |
| _314 as u8 | 58 | Truncates excess bits. |
| _257 as i8 | 1 | Truncates excess bits. |
| _200 as i8 | -56 | Truncates excess bits, MSB might then also signal negative. |
| 200_u8 / 0_u8 | Compile error. | - |
| 200_u8 / _0 d, r | Panic. | Regular math may panic; here: division by zero. |
| 200_u8 + 200_u8 | Compile error. | - |
| 200_u8 + _200 d | Panic. | Consider checked_, wrapping_, … instead. STD |
| 200_u8 + _200 r | 144 | In release mode this will overflow. |
| -128_i8 * -1 | Compile error. | Would overflow (128_i8 doesn't exist). |
| -128_i8 * _1neg d | Panic. | - |
| -128_i8 * _1neg r | -128 | Overflows back to -128 in release mode. |
| 1_u8 / 2_u8 | 0 | Other integer division truncates. |
| 0.8_f32 + 0.1_f32 | 0.90000004 | - |
| 1.0_f32 / 0.0_f32 | f32::INFINITY | - |
| 0.0_f32 / 0.0_f32 | f32::NAN | - |
| x < f32::NAN | false | NAN comparisons always return false. |
| x > f32::NAN | false | NAN comparisons always return false. |
| f32::NAN == f32::NAN | false | Use f32::is_nan() STD instead. |
1 Expression _100 means anything that might contain the value 100, e.g., 100_i32, but is opaque to compiler.
d Debug build.
r Release build.
Textual Types REFurl
char Any Unicode scalar. str … U T F - 8 … unspecified times Rarely seen alone, but as &str instead. Basics| char | Always 4 bytes and only holds a single Unicode scalar value 🔗. |
| str | An u8-array of unknown length guaranteed to hold UTF-8 encoded code points. |
| let c = 'a'; | Often a char (unicode scalar) can coincide with your intuition of character. |
| let c = '❤'; | It can also hold many Unicode symbols. |
| let c = '❤️'; | But not always. Given emoji is two char (see Encoding) and can't 🛑 be held by c.1 |
| c = 0xffff_ffff; | Also, chars are not allowed 🛑 to hold arbitrary bit patterns. |
| let s = "a"; | A str is usually never held directly, but as &str, like s here. |
| let s = "❤❤️"; | It can hold arbitrary text, has variable length per c., and is hard to index. |
let s = "I ❤ Rust";
let t = "I ❤️ Rust";
| s.as_bytes() | 49 20 e2 9d a4 20 52 75 73 74 3 |
| t.as_bytes() | 49 20 e2 9d a4 ef b8 8f 20 52 75 73 74 4 |
| s.chars()1 | 49 00 00 00 20 00 00 00 64 27 00 00 20 00 00 00 52 00 00 00 75 00 00 00 73 00 … |
| t.chars()1 | 49 00 00 00 20 00 00 00 64 27 00 00 0f fe 01 00 20 00 00 00 52 00 00 00 75 00 … |
2 Values given in hex, on x86.
3 Notice how ❤, having Unicode Code Point (U+2764), is represented as 64 27 00 00 inside the char, but got UTF-8 encoded to e2 9d a4 in the str.
4 Also observe how the emoji Red Heart ❤️, is a combination of ❤ and the U+FE0F Variation Selector, thus t has a higher char count than s.
⚠️ For what seem to be browser bugs Safari and Edge render the hearts in Footnote 3 and 4 wrong, despite being able to differentiate them correctly in s and t above.
Custom Typesurl
Basic types definable by users. Actual layout REF is subject to representation; REF padding can be present.
T x T Sized type. T: ?Sized T Maybe sized. [T; n] T T T … n times Fixed array of n elements. [T] … T T T … unspecified times Slice type of unknown-many elements. NeitherSized (nor carries len information), and most
often lives behind reference as &[T]. ↓ struct S; ; Zero-sized type. (A, B, C) A B C or maybe B A C Unless a representation is forced
(e.g., via #[repr(C)]), type layout
unspecified. struct S { b: B, c: C } B C or maybe C ↦ B Compiler may also add padding.
Also note, two types A(X, Y) and B(X, Y) with exactly the same fields can still have differing layout; never transmute() STD without representation guarantees.
These sum types hold a value of one of their sub types:
enum E { A, B, C } Tag A exclusive or Tag B exclusive or Tag C Safely holds A or B or C, alsocalled 'tagged union', though
compiler may squeeze tag
into 'unused' bits. union { … } A unsafe or B unsafe or C Can unsafely reinterpret
memory. Result might
be undefined.
References & Pointersurl
References give safe access to 3rd party memory, raw pointers unsafe access. The corresponding mut types have an identical data layout to their immutable counterparts.
&'a T ptr2/4/8 meta2/4/8 | T Must target some valid t of T,and any such target must exist for
at least 'a. *const T ptr2/4/8 meta2/4/8 No guarantees.
Pointer Metaurl
Many reference and pointer types can carry an extra field, pointer metadata. STD It can be the element- or byte-length of the target, or a pointer to a vtable. Pointers with meta are called fat, otherwise thin.
&'a T ptr2/4/8 | T No meta forsized target.
(pointer is thin). &'a T ptr2/4/8 len2/4/8 | T If T is a DST struct such as
S { x: [u8] } meta field len is
count of dyn. sized content. &'a [T] ptr2/4/8 len2/4/8 | … T T … Regular slice reference (i.e., the
reference type of a slice type [T]) ↑
often seen as &[T] if 'a elided. &'a str ptr2/4/8 len2/4/8 | … U T F - 8 … String slice reference (i.e., the
reference type of string type str),
with meta len being byte length.
&'a dyn Trait ptr2/4/8 ptr2/4/8 | T |
| *Drop::drop(&mut T) |
| size |
| align |
| *Trait::f(&T, …) |
| *Trait::g(&T, …) |
Closuresurl
Ad-hoc functions with an automatically managed data block capturing REF, 1 environment where closure was defined. For example, if you had:
let y = ...; let z = ...; with_closure(move |x| x + y.f() + z); // y and z are moved into closure instance (of type C1) with_closure( |x| x + y.f() + z); // y and z are pointed at from closure instance (of type C2)Then the generated, anonymous closures types C1 and C2 passed to with_closure() would look like:
move |x| x + y.f() + z Y Z Anonymous closure type C1 |x| x + y.f() + z ptr2/4/8 ptr2/4/8 Anonymous closure type C2 | Y | ZAlso produces anonymous fn such as fc1(C1, X) or fc2(&C2, X). Details depend on which FnOnce, FnMut, Fn ... is supported, based on properties of captured types.
1 A bit oversimplified a closure is a convenient-to-write 'mini function' that accepts parameters but also needs some local variables to do its job. It is therefore a type (containing the needed locals) and a function. 'Capturing the environment' is a fancy way of saying that and how the closure type holds on to these locals, either by moved value, or by pointer. See Closures in APIs ↓ for various implications.
Standard Library Typesurl
Rust's standard library combines the above primitive types into useful types with special semantics, e.g.:
Option<T> STD Tag or Tag T Tag may be omitted forcertain T, e.g., NonNull.STD Result<T, E> STD Tag E or Tag T Either some error E or value
of T. ManuallyDrop<T> STD T Prevents T::drop() from
being called. AtomicUsize STD usize2/4/8 Other atomic similarly. MaybeUninit<T> STD U̼̟̔͛n̥͕͐͞d̛̲͔̦̳̑̓̐e̱͎͒̌fị̱͕̈̉͋ne̻̅ḓ̓ unsafe or T Uninitialized memory or
some T. Only legal way
to work with uninit data. PhantomData<T> STD Zero-sized helper to hold
otherwise unused lifetimes. Pin<P> STD P | 📌 P::Deref Signals tgt. of P is pinned 'forever'
even past lt. of Pin. Value within
may not be moved out (but new
one moved in), unless Unpin.STD
🛑 All depictions are for illustrative purposes only. The fields should exist in latest stable, but Rust makes no guarantees about their layouts, and you must not attempt to unsafely access anything unless the docs allow it.
Cellsurl
UnsafeCell<T> STD T Magic type allowingaliased mutability. Cell<T> STD T Allows T's
to move in
and out. RefCell<T> STD borrowed T Also support dynamic
borrowing of T. Like Cell this
is Send, but not Sync. OnceCell<T> STD
Tag or Tag T
Initialized at most once. LazyCell<T, F> STDTag Uninit<F> or Tag Init<T> or Tag Poisoned
Initialized on first access.Order-Preserving Collectionsurl
Box<T> STD ptr2/4/8 meta2/4/8 | T For some T stack proxy may carrymeta↑ (e.g., Box<[T]>). Vec<T> STD ptr2/4/8 len2/4/8 capacity2/4/8 |
T T … len
← capacity → Regular growable array vector of single type. LinkedList<T> STD🝖 head2/4/8 tail2/4/8 len2/4/8 | | next2/4/8 prev2/4/8 T Elements head and tail both null or point to nodes onthe heap. Each node can point to its prev and next node.
Eats your cache (just look at the thing!); don't use unless
you evidently must. 🛑 VecDeque<T> STD head2/4/8 len2/4/8 ptr2/4/8 capacity2/4/8 |
T … empty … TH
← capacity → Index head selects in array-as-ringbuffer. This means content may benon-contiguous and empty in the middle, as exemplified above.
Other Collectionsurl
HashMap<K, V> STD bmask2/4/8 ctrl2/4/8 left2/4/8 len2/4/8 | K:V K:V … K:V … K:V Oversimplified! Stores keys and values on heap according to hash value, SwissTableimplementation via hashbrown. HashSet STD identical to HashMap,
just type V disappears. Heap view grossly oversimplified. 🛑 BinaryHeap<T> STD ptr2/4/8 capacity2/4/8 len2/4/8 |
T0 T1 T1 T2 T2 … len
← capacity → Heap stored as array with 2N elements per layer. Each Tcan have 2 children in layer below. Each T larger than its
children.
Owned Stringsurl
String STD ptr2/4/8 capacity2/4/8 len2/4/8 |U T F - 8 … len
← capacity → Observe how String differs from &str and &[char]. CString STD ptr2/4/8 len2/4/8 |A B C … len … ∅
NUL-terminated but w/o NUL in middle. OsString STD Platform Defined | Encapsulates how operating systemrepresents strings (e.g., WTF-8 on
Windows). PathBuf STD OsString | Encapsulates how operating system
represents paths.
If the type does not contain a Cell for T, these are often combined with one of the Cell types above to allow shared de-facto mutability.
Rc<T> STD ptr2/4/8 meta2/4/8| strng2/4/8 weak2/4/8 T
Share ownership of T in same thread. Needs nested Cellor RefCellto allow mutation. Is neither Send nor Sync. Arc<T> STD ptr2/4/8 meta2/4/8
| strng2/4/8 weak2/4/8 T
Same, but allow sharing between threads IF containedT itself is Send and Sync.
Mutex<T> STD / RwLock<T> STD inner poison2/4/8 T Inner fields depend on platform. Needs to be
held in Arc to be shared between decoupled
threads, or via scope() STD for scoped threads. Cow<'a, T> STD Tag T::Owned or Tag ptr2/4/8
| T
Holds read-only reference tosome T, or owns its ToOwned STD
analog.
One-Linersurl
Snippets that are common, but still easy to forget. See Rust Cookbook 🔗 for more.
Thread Safetyurl
Assume you hold some variables in Thread 1, and want to either move them to Thread 2, or pass their references to Thread 3. Whether this is allowed is governed by SendSTD and SyncSTD respectively:
| Sync | Most types … Arc<T>1,2, Mutex<T>2 | MutexGuard<T>1, RwLockReadGuard<T>1 |
| !Sync | Cell<T>2, RefCell<T>2 | Rc<T>, &dyn Trait, *const T3 |
1 If T is Sync.
2 If T is Send.
3 If you need to send a raw pointer, create newtype struct Ptr(*const u8) and unsafe impl Send for Ptr {}. Just ensure you may send it.
1 This is a bit of pseudo-code to get the point across, the idea is to have an Rc before an .await point and keep using it beyond that point.
Atomics & Cache 🝖url
CPU cache, memory writes, and how atomics affect it.
S O M E D R A M D A T A Main Memory S O M E (E) D A T A (S) CPU1 Cache S R A M (M) D A T A (S) CPU2 Cache
Modern CPUs don't accesses memory directly, only their cache. Each CPU has its own cache, 100x faster than RAM, but much smaller. It comes in cache lines,🔗 some sliced window of bytes, which track if it's an exclusive (E), shared (S) or modified (M) 🔗 view of the main memory. Caches talk to each other to ensure coherence,🔗 i.e., 'small-enough' data will be 'immediately' seen by all other CPUs, but that may stall the CPU.
S O M E D X T A (M) Cycle 1 O3 S O M 4 D X T A Cycle 2 23 STALLED 1 4 (M) D X T Y (M) Cycle 3 23
Left: Both compiler and CPUs are free to re-order 🔗 and split R/W memory access. Even if you explicitly said write(1); write(23); write(4), your compiler might think it's a good idea to write 23 first; in addition your CPU might insist on splitting the write, doing 3 before 2. Each of these steps could be observable (even the impossible O3) by CPU2 via an unsafe data race. Reordering is also fatal for locks.
Right: Semi-related, even when two CPUs do not attempt to access each other's data (e.g., update 2 independent variables), they might still experience a significant performance loss if the underlying memory is mapped by 2 cache lines (false sharing).🔗
1 2 3 4 S R A M D X T Y Main Memory RA 1 R A M Cycle 4 RA 1 2 M Cycle 5 23 1 2 3 4 (M) Cycle 6 23
Atomics address the above issues by doing two things, they
- make sure a read / write / update is not partially observable by temporarily locking cache lines in other CPUs,
- force both the compiler and the CPU to not re-order 'unrelated' access around it (i.e., act as a fence STD). Ensuring multiple CPUs agree on the relative order of these other ops is called consistency. 🔗 This also comes at a cost of missed performance optimizations.
Note — The above section is greatly simplified. While the issues of coherence and consistency are universal, CPU architectures differ a lot in how they implement caching and atomics, and in their performance impact.
1 To be clear, when synchronizing memory access with 2+ CPUs, all must use Acquire or Release (or stronger). The writer must ensure that all other data it wishes to release to memory are put before the atomic signal, while the readers who wish to acquire this data must ensure that their other reads are only done after the atomic signal.
Iteratorsurl
Processing elements in a collection.
String Conversionsurl
If you want a string of type …
| String | x |
| CString | x.into_string()? |
| OsString | x.to_str()?.to_string() |
| PathBuf | x.to_str()?.to_string() |
| Vec<u8> 1 | String::from_utf8(x)? |
| &str | x.to_string() i |
| &CStr | x.to_str()?.to_string() |
| &OsStr | x.to_str()?.to_string() |
| &Path | x.to_str()?.to_string() |
| &[u8] 1 | String::from_utf8_lossy(x).to_string() |
| String | CString::new(x)? |
| CString | x |
| OsString | CString::new(x.to_str()?)? |
| PathBuf | CString::new(x.to_str()?)? |
| Vec<u8> 1 | CString::new(x)? |
| &str | CString::new(x)? |
| &CStr | x.to_owned() i |
| &OsStr | CString::new(x.to_os_string().into_string()?)? |
| &Path | CString::new(x.to_str()?)? |
| &[u8] 1 | CString::new(Vec::from(x))? |
| *mut c_char 2 | unsafe { CString::from_raw(x) } |
| String | OsString::from(x) i |
| CString | OsString::from(x.to_str()?) |
| OsString | x |
| PathBuf | x.into_os_string() |
| Vec<u8> 1 | unsafe { OsString::from_encoded_bytes_unchecked(x) } |
| &str | OsString::from(x) i |
| &CStr | OsString::from(x.to_str()?) |
| &OsStr | OsString::from(x) i |
| &Path | x.as_os_str().to_owned() |
| &[u8] 1 | unsafe { OsString::from_encoded_bytes_unchecked(x.to_vec()) } |
| String | PathBuf::from(x) i |
| CString | PathBuf::from(x.to_str()?) |
| OsString | PathBuf::from(x) i |
| PathBuf | x |
| Vec<u8> 1 | unsafe { PathBuf::from(OsString::from_encoded_bytes_unchecked(x)) } |
| &str | PathBuf::from(x) i |
| &CStr | PathBuf::from(x.to_str()?) |
| &OsStr | PathBuf::from(x) i |
| &Path | PathBuf::from(x) i |
| &[u8] 1 | unsafe { PathBuf::from(OsString::from_encoded_bytes_unchecked(x.to_vec())) } |
| String | x.into_bytes() |
| CString | x.into_bytes() |
| OsString | x.into_encoded_bytes() |
| PathBuf | x.into_os_string().into_encoded_bytes() |
| Vec<u8> 1 | x |
| &str | Vec::from(x.as_bytes()) |
| &CStr | Vec::from(x.to_bytes_with_nul()) |
| &OsStr | Vec::from(x.as_encoded_bytes()) |
| &Path | Vec::from(x.as_os_str().as_encoded_bytes()) |
| &[u8] 1 | x.to_vec() |
| String | x.as_str() |
| CString | x.to_str()? |
| OsString | x.to_str()? |
| PathBuf | x.to_str()? |
| Vec<u8> 1 | std::str::from_utf8(&x)? |
| &str | x |
| &CStr | x.to_str()? |
| &OsStr | x.to_str()? |
| &Path | x.to_str()? |
| &[u8] 1 | std::str::from_utf8(x)? |
| String | CString::new(x)?.as_c_str() |
| CString | x.as_c_str() |
| OsString | x.to_str()? |
| PathBuf | ?,3 |
| Vec<u8> 1,4 | CStr::from_bytes_with_nul(&x)? |
| &str | ?,3 |
| &CStr | x |
| &OsStr | ? |
| &Path | ? |
| &[u8] 1,4 | CStr::from_bytes_with_nul(x)? |
| *const c_char 1 | unsafe { CStr::from_ptr(x) } |
| String | OsStr::new(&x) |
| CString | ? |
| OsString | x.as_os_str() |
| PathBuf | x.as_os_str() |
| Vec<u8> 1 | unsafe { OsStr::from_encoded_bytes_unchecked(&x) } |
| &str | OsStr::new(x) |
| &CStr | ? |
| &OsStr | x |
| &Path | x.as_os_str() |
| &[u8] 1 | unsafe { OsStr::from_encoded_bytes_unchecked(x) } |
| String | Path::new(x) r |
| CString | Path::new(x.to_str()?) |
| OsString | Path::new(x.to_str()?) r |
| PathBuf | Path::new(x.to_str()?) r |
| Vec<u8> 1 | unsafe { Path::new(OsStr::from_encoded_bytes_unchecked(&x)) } |
| &str | Path::new(x) r |
| &CStr | Path::new(x.to_str()?) |
| &OsStr | Path::new(x) r |
| &Path | x |
| &[u8] 1 | unsafe { Path::new(OsStr::from_encoded_bytes_unchecked(x)) } |
| String | x.as_bytes() |
| CString | x.as_bytes() |
| OsString | x.as_encoded_bytes() |
| PathBuf | x.as_os_str().as_encoded_bytes() |
| Vec<u8> 1 | &x |
| &str | x.as_bytes() |
| &CStr | x.to_bytes_with_nul() |
| &OsStr | x.as_encoded_bytes() |
| &Path | x.as_os_str().as_encoded_bytes() |
| &[u8] 1 | x |
| *const c_char | CString | x.as_ptr() |
i Short form x.into() possible if type can be inferred.
r Short form x.as_ref() possible if type can be inferred.
1 You must ensure x comes with a valid representation for the string type (e.g., UTF-8 data for a String).
2 The c_char must have come from a previous CString. If it comes from FFI see &CStr instead.
3 No known shorthand as x will lack terminating 0x0. Best way to probably go via CString.
4 Must ensure x actually ends with 0x0.
String Outputurl
How to convert types into a String, or output them.
APIs Printable Types FormattingEach argument designator in format macro is either empty {}, {argument}, or follows a basic syntax:
{ [argument] ':' [[fill] align] [sign] ['#'] [width [$]] ['.' precision [$]] [type] }Project Anatomyurl
Basic project layout, and common files and folders, as used by cargo. ↓
* On stable consider Criterion.
Minimal examples for various entry points might look like:
Applications*See here for list of environment variables set.
Module trees and imports:
Module TreesModules BK EX REF and source files work as follows:
- Module tree needs to be explicitly defined, is not implicitly built from file system tree. 🔗
- Module tree root equals library, app, … entry point (e.g., lib.rs).
Actual module definitions work as follows:
- A mod m {} defines module in-file, while mod m; will read m.rs or m/mod.rs.
- Path of .rs based on nesting, e.g., mod a { mod b { mod c; }}} is either a/b/c.rs or a/b/c/mod.rs.
- Files not pathed from module tree root via some mod m; won't be touched by compiler! 🛑
Rust has three kinds of namespaces:
| mod X {} | fn X() {} | macro_rules! X { … } |
| X (crate) | const X: u8 = 1; | |
| trait X {} | static X: u8 = 1; | |
| enum X {} | ||
| union X {} | ||
| struct X {} | ||
| ← struct X;1 → | ||
| ← struct X();2 → | ||
1 Counts in Types and in Functions, defines type X and constant X.
2 Counts in Types and in Functions, defines type X and function X.
- In any given scope, for example within a module, only one item per namespace can exist, e.g.,
- enum X {} and fn X() {} can coexist
- struct X; and const X cannot coexist
- With a use my_mod::X; all items called X will be imported.
Due to naming conventions (e.g., fn and mod are lowercase by convention) and common sense (most developers just don't name all things X) you won't have to worry about these kinds in most cases. They can, however, be a factor when designing macros.
Cargourl
Commands and tools that are good to know.
Here cargo build means you can either type cargo build or just cargo b; and --release means it can be replaced with -r.
These are optional rustup components. Install them with rustup component add [tool].
A large number of additional cargo plugins can be found here.
Cross Compilationurl
🔘 Check target is supported.
🔘 Install target via rustup target install aarch64-linux-android (for example).
🔘 Install native toolchain (required to link, depends on target).
Get from target vendor (Google, Apple, …), might not be available on all hosts (e.g., no iOS toolchain on Windows).
Some toolchains require additional build steps (e.g., Android's make-standalone-toolchain.sh).
🔘 Update ~/.cargo/config.toml like this:
[target.aarch64-linux-android] linker = "[PATH_TO_TOOLCHAIN]/aarch64-linux-android/bin/aarch64-linux-android-clang"or
[target.aarch64-linux-android] linker = "C:/[PATH_TO_TOOLCHAIN]/prebuilt/windows-x86_64/bin/aarch64-linux-android21-clang.cmd"🔘 Set environment variables (optional, wait until compiler complains before setting):
set CC=C:\[PATH_TO_TOOLCHAIN]\prebuilt\windows-x86_64\bin\aarch64-linux-android21-clang.cmd set CXX=C:\[PATH_TO_TOOLCHAIN]\prebuilt\windows-x86_64\bin\aarch64-linux-android21-clang.cmd set AR=C:\[PATH_TO_TOOLCHAIN]\prebuilt\windows-x86_64\bin\aarch64-linux-android-ar.exe …Whether you set them depends on how compiler complains, not necessarily all are needed.
Some platforms / configurations can be extremely sensitive how paths are specified (e.g., \ vs /) and quoted.
✔️ Compile with cargo build --target=aarch64-linux-android
Special tokens embedded in source code used by tooling or preprocessing.
Macro Fragments Documentation #![globals] #[code] #[quality] #[macros] #[cfg] build.rsFor the On column in attributes:
C means on crate level (usually given as #![my_attr] in the top level file).
M means on modules.
F means on functions.
S means on static.
T means on types.
X means something special.
! means on macros.
* means on almost any item.
Types, Traits, Genericsurl
Allowing users to bring their own types and avoid code duplication.
Types & Traits- Set of values with given semantics, layout, …
| u8 | { 0u8, 1u8, …, 255u8 } |
| char | { 'a', 'b', … '🦀' } |
| struct S(u8, char) | { (0u8, 'a'), … (255u8, '🦀') } |
Sample types and sample values.
Type Equivalence and Conversions u8 &u8 &mut u8 [u8; 1] String- It may be obvious but u8, &u8, &mut u8, are entirely different from each other
- Any t: T only accepts values from exactly T, e.g.,
- f(0_u8) can't be called with f(&0_u8),
- f(&mut my_u8) can't be called with f(&my_u8),
- f(0_u8) can't be called with f(0_i8).
Yes, 0 != 0 (in a mathematical sense) when it comes to types! In a language sense, the operation ==(0u8, 0u16) just isn't defined to prevent happy little accidents.
| u8 | { 0u8, 1u8, …, 255u8 } |
| u16 | { 0u16, 1u16, …, 65_535u16 } |
| &u8 | { 0xffaa&u8, 0xffbb&u8, … } |
| &mut u8 | { 0xffaa&mut u8, 0xffbb&mut u8, … } |
How values differ between types.
- However, Rust might sometimes help to convert between types1
- casts manually convert values of types, 0_i8 as u8
- coercions ↑ automatically convert types if safe2, let x: &u8 = &mut 0_u8;
1 Casts and coercions convert values from one set (e.g., u8) to another (e.g., u16), possibly adding CPU instructions to do so; and in such differ from subtyping, which would imply type and subtype are part of the same set (e.g., u8 being subtype of u16 and 0_u8 being the same as 0_u16) where such a conversion would be purely a compile time check. Rust does not use subtyping for regular types (and 0_u8 does differ from 0_u16) but sort-of for lifetimes. 🔗
2 Safety here is not just physical concept (e.g., &u8 can't be coerced to &u128), but also whether 'history has shown that such a conversion would lead to programming errors'.
Implementations — impl S { } u8 impl { … } String impl { … } Port impl { … } impl Port { fn f() { … } }- Types usually come with inherent implementations, REF e.g., impl Port {}, behavior related to type:
- associated functions Port::new(80)
- methods port.close()
What's considered related is more philosophical than technical, nothing (except good taste) would prevent a u8::play_sound() from happening.
Traits — trait T { } ⌾ Copy ⌾ Clone ⌾ Sized ⌾ ShowHex- Traits …
- are way to "abstract" behavior,
- trait author declares semantically this trait means X,
- other can implement ("subscribe to") that behavior for their type.
- Think about trait as "membership list" for types:
Traits as membership tables, Self refers to the type included.
- Whoever is part of that membership list will adhere to behavior of list.
- Traits can also include associated methods, functions, …
- Traits without methods often called marker traits.
- Copy is example marker trait, meaning memory may be copied bitwise.
- Some traits entirely outside explicit control
- Sized provided by compiler for types with known size; either this is, or isn't
- Traits are implemented for types 'at some point'.
- Implementation impl A for B add type B to the trait membership list:
- Visually, you can think of the type getting a "badge" for its membership:
Interfaces
- In Java, Alice creates interface Eat.
- When Bob authors Venison, he must decide if Venison implements Eat or not.
- In other words, all membership must be exhaustively declared during type definition.
- When using Venison, Santa can make use of behavior provided by Eat:
Traits
- In Rust, Alice creates trait Eat.
- Bob creates type Venison and decides not to implement Eat (he might not even know about Eat).
- Someone* later decides adding Eat to Venison would be a really good idea.
- When using Venison Santa must import Eat separately:
* To prevent two persons from implementing Eat differently Rust limits that choice to either Alice or Bob; that is, an impl Eat for Venison may only happen in the crate of Venison or in the crate of Eat. For details see coherence. ?
- Vec<u8> is type "vector of bytes"; Vec<char> is type "vector of chars", but what is Vec<>?
| Vec<u8> | { [], [1], [1, 2, 3], … } |
| Vec<char> | { [], ['a'], ['x', 'y', 'z'], … } |
| Vec<> | - |
Types vs type constructors.
Vec<>- Vec<> is no type, does not occupy memory, can't even be translated to code.
- Vec<> is type constructor, a "template" or "recipe to create types"
- allows 3rd party to construct concrete type via parameter,
- only then would this Vec<UserType> become real type itself.
- Parameter for Vec<> often named T therefore Vec<T>.
- T "variable name for type" for user to plug in something specific, Vec<f32>, S<u8>, …
| struct Vec<T> {} | Vec<u8>, Vec<f32>, Vec<Vec<u8>>, … |
| [T; 128] | [u8; 128], [char; 128], [Port; 128] … |
| &T | &u8, &u16, &str, … |
Type vs type constructors.
// S<> is type constructor with parameter T; user can supply any concrete type for T. struct S<T> { x: T } // Within 'concrete' code an existing type must be given for T. fn f() { let x: S<f32> = S::new(0_f32); } Const Generics — [T; N] and S<const N: usize> [T; n] S<const N>- Some type constructors not only accept specific type, but also specific constant.
- [T; n] constructs array type holding T type n times.
- For custom types declared as MyArray<T, const N: usize>.
| [u8; N] | [u8; 0], [u8; 1], [u8; 2], … |
| struct S<const N: usize> {} | S<1>, S<6>, S<123>, … |
Type constructors based on constant.
let x: [u8; 4]; // "array of 4 bytes" let y: [f32; 16]; // "array of 16 floats" // `MyArray` is type constructor requiring concrete type `T` and // concrete usize `N` to construct specific type. struct MyArray<T, const N: usize> { data: [T; N], } Bounds (Simple) — where T: X 🧔 Num<T> → 🎅 Num<u8> Num<f32> Num<Cmplx> u8 ⌾ Absolute ⌾ Dim ⌾ Mul Port ⌾ Clone ⌾ ShowHex- If T can be any type, how can we reason about (write code) for such a Num<T>?
- Parameter bounds:
- limit what types (trait bound) or values (const bound ?) allowed,
- we now can make use of these limits!
- Trait bounds act as "membership check":
We add bounds to the struct here. In practice it's nicer add bounds to the respective impl blocks instead, see later this section.
Bounds (Compound) — where T: X + Y u8 ⌾ Absolute ⌾ Dim ⌾ Mul f32 ⌾ Absolute ⌾ Mul char Cmplx ⌾ Absolute ⌾ Dim ⌾ Mul ⌾ DirName ⌾ TwoD Car ⌾ DirName struct S<T> where T: Absolute + Dim + Mul + DirName + TwoD { … }- Long trait bounds can look intimidating.
- In practice, each + X addition to a bound merely cuts down space of eligible types.
When we write:
impl<T> S<T> where T: Absolute + Dim + Mul { fn f(&self, x: T) { … }; }It can be read as:
- here is an implementation recipe for any type T (the impl <T> part),
- where that type must be member of the Absolute + Dim + Mul traits,
- you may add an implementation block to the type family S<>,
- containing the methods …
You can think of such impl<T> … {} code as abstractly implementing a family of behaviors. REF Most notably, they allow 3rd parties to transparently materialize implementations similarly to how type constructors materialize types:
// If compiler encounters this, it will // - check `0` and `x` fulfill the membership requirements of `T` // - create two new version of `f`, one for `char`, another one for `u32`. // - based on "family implementation" provided s.f(0_u32); s.f('x'); Blanket Implementations — impl<T> X for T { … }Can also write "family implementations" so they apply trait to many types:
// Also implements Serialize for any type if that type already implements ToHex impl<T> Serialize for T where T: ToHex { … }These are called blanket implementations.
→ Whatever was in left table, may be added to right table, based on the following recipe (impl) →
| u8 |
| Port |
| … |
They can be neat way to give foreign types functionality in a modular way if they just implement another interface.
Notice how some traits can be "attached" multiple times, but others just once?
Port ⌾ From<u8> ⌾ From<u16> Port ⌾ Deref type u8;Why is that?
- Traits themselves can be generic over two kinds of parameters:
- trait From<I> {}
- trait Deref { type O; }
- Remember we said traits are "membership lists" for types and called the list Self?
- Turns out, parameters I (for input) and O (for output) are just more columns to that trait's list:
Input and output parameters.
Now here's the twist,
- any output O parameters must be uniquely determined by input parameters I,
- (in the same way as a relation X Y would represent a function),
- Self counts as an input.
A more complex example:
trait Complex<I1, I2> { type O1; type O2; }- this creates a relation of types named Complex,
- with 3 inputs (Self is always one) and 2 outputs, and it holds (Self, I1, I2) => (O1, O2)
| Player | u8 | char | f32 | f32 |
| EvilMonster | u16 | str | u8 | u8 |
| EvilMonster | u16 | String | u8 | u8 |
| NiceMonster | u16 | String | u8 | u8 |
| NiceMonster🛑 | u16 | String | u8 | u16 |
Various trait implementations. The last one is not valid as (NiceMonster, u16, String) has
already uniquely determined the outputs.
👩🦰 ⌾ B type O; 🧔 Car 👩🦰 / 🧔 Car ⌾ B T = u8; 🎅 car.b(0_u8) car.b(0_f32)
- Parameter choice (input vs. output) also determines who may be allowed to add members:
- I parameters allow "familes of implementations" be forwarded to user (Santa),
- O parameters must be determined by trait implementor (Alice or Bob).
Santa may add more members by providing his own type for T.
For given set of inputs (here Self), implementor must pre-select O.
Trait Authoring Considerations (Example) ⌾ Query vs. ⌾ Query<I> vs. ⌾ Query type O; vs. ⌾ Query<I> type O;Choice of parameters goes along with purpose trait has to fill.
No Additional Parameters
trait Query { fn search(&self, needle: &str); } impl Query for PostgreSQL { … } impl Query for Sled { … } postgres.search("SELECT …"); 👩🦰 ⌾ Query → 🧔 PostgreSQL ⌾ Query Sled ⌾ QueryTrait author assumes:
- neither implementor nor user need to customize API.
Input Parameters
trait Query<I> { fn search(&self, needle: I); } impl Query<&str> for PostgreSQL { … } impl Query<String> for PostgreSQL { … } impl<T> Query<T> for Sled where T: ToU8Slice { … } postgres.search("SELECT …"); postgres.search(input.to_string()); sled.search(file); 👩🦰 ⌾ Query<I> → 🧔 PostgreSQL ⌾ Query<&str> ⌾ Query<String> Sled ⌾ Query<T> ↲ where T is ToU8Slice.Trait author assumes:
- implementor would customize API in multiple ways for same Self type,
- users may want ability to decide for which I-types behavior should be possible.
Output Parameters
trait Query { type O; fn search(&self, needle: Self::O); } impl Query for PostgreSQL { type O = String; …} impl Query for Sled { type O = Vec<u8>; … } postgres.search("SELECT …".to_string()); sled.search(vec![0, 1, 2, 4]); 👩🦰 ⌾ Query type O; → 🧔 PostgreSQL ⌾ Query O = String; Sled ⌾ Query O = Vec<u8>;Trait author assumes:
- implementor would customize API for Self type (but in only one way),
- users do not need, or should not have, ability to influence customization for specific Self.
As you can see here, the term input or output does not (necessarily) have anything to do with whether I or O are inputs or outputs to an actual function!
Multiple In- and Output Parameters
trait Query<I> { type O; fn search(&self, needle: I) -> Self::O; } impl Query<&str> for PostgreSQL { type O = String; … } impl Query<CString> for PostgreSQL { type O = CString; … } impl<T> Query<T> for Sled where T: ToU8Slice { type O = Vec<u8>; … } postgres.search("SELECT …").to_uppercase(); sled.search(&[1, 2, 3, 4]).pop(); 👩🦰 ⌾ Query<I> type O; → 🧔 PostgreSQL ⌾ Query<&str> O = String; ⌾ Query<CString> O = CString; Sled ⌾ Query<T> O = Vec<u8>; ↲ where T is ToU8Slice.Like examples above, in particular trait author assumes:
- users may want ability to decide for which I-types ability should be possible,
- for given inputs, implementor should determine resulting output type.
- A type T is Sized STD if at compile time it is known how many bytes it occupies, u8 and &[u8] are, [u8] isn't.
- Being Sized means impl Sized for T {} holds. Happens automatically and cannot be user impl'ed.
- Types not Sized are called dynamically sized types BK NOM REF (DSTs), sometimes unsized.
- Types without data are called zero sized types NOM (ZSTs), do not occupy space.
- T can be any concrete type.
- However, there exists invisible default bound T: Sized, so S<str> is not possible out of box.
- Instead we have to add T : ?Sized to opt-out of that bound:
- Lifetimes act* as type parameters:
- user must provide specific 'a to instantiate type (compiler will help within methods),
- S<'p> and S<'q> are different types, just like Vec<f32> and Vec<u8> are
- meaning you can't just assign value of type S<'a> to variable expecting S<'b> (exception: subtype relationship for lifetimes, i.e., 'a outlives 'b).
- 'static is only globally available type of the lifetimes kind.
* There are subtle differences, for example you can create an explicit instance 0 of a type u32, but with the exception of 'static you can't really create a lifetime, e.g., "lines 80 - 100", the compiler will do that for you. 🔗
Examples expand by clicking.
Foreign Types and Traitsurl
A visual overview of types and traits in your crate and upstream.
Examples of traits and types, and which traits you can implement for which type.
Type Conversionsurl
How to get B when you have A?
Idiomatic Rusturl
If you are used to Java or C, consider these.
1 In most cases you should prefer ? over .unwrap(). In the case of locks however the returned PoisonError signifies a panic in another thread, so unwrapping it (thus propagating the panic) is often the better idea.
🔥 We highly recommend you also follow the API Guidelines (Checklist) for any shared project! 🔥
Performance Tipsurl
"My code is slow" sometimes comes up when porting microbenchmarks to Rust, or after profiling.
Entries marked 🚀 often come with a massive (> 2x) performance boost, 🍼 are easy to implement even after-the-fact, ⚖️ might have costly side effects (e.g., memory, complexity), ⚠️ have special risks (e.g., security, correctness).
Profiling Tips 💬
Profilers are indispensable to identify hot spots in code. For the best experience add this to your Cargo.toml:
[profile.release] debug = trueThen do a cargo build --release and run the result with Superluminal (Windows) or Instruments (macOS). That said, there are many performance opportunities profilers won't find, but that need to be designed in.
Async-Await 101url
If you are familiar with async / await in C# or TypeScript, here are some things to keep in mind:
Closures in APIsurl
There is a subtrait relationship Fn : FnMut : FnOnce. That means a closure that implements Fn STD also implements FnMut and FnOnce. Likewise a closure that implements FnMut STD also implements FnOnce. STD
From a call site perspective that means:
Notice how asking for a Fn closure as a function is most restrictive for the caller; but having a Fn closure as a caller is most compatible with any function.
From the perspective of someone defining a closure:
That gives the following advantages and disadvantages:
Unsafe, Unsound, Undefinedurl
Unsafe leads to unsound. Unsound leads to undefined. Undefined leads to the dark side of the force.
Safe CodeSafe Code
- Safe has narrow meaning in Rust, vaguely 'the intrinsic prevention of undefined behavior (UB)'.
- Intrinsic means the language won't allow you to use itself to cause UB.
- Making an airplane crash or deleting your database is not UB, therefore 'safe' from Rust's perspective.
- Writing to /proc/[pid]/mem to self-modify your code is also 'safe', resulting UB not caused intrinsincally.
Unsafe Code
- Code marked unsafe has special permissions, e.g., to deref raw pointers, or invoke other unsafe functions.
- Along come special promises the author must uphold to the compiler, and the compiler will trust you.
- By itself unsafe code is not bad, but dangerous, and needed for FFI or exotic data structures.
Undefined Behavior (UB)
- As mentioned, unsafe code implies special promises to the compiler (it wouldn't need be unsafe otherwise).
- Failure to uphold any promise makes compiler produce fallacious code, execution of which leads to UB.
- After triggering undefined behavior anything can happen. Insidiously, the effects may be 1) subtle, 2) manifest far away from the site of violation or 3) be visible only under certain conditions.
- A seemingly working program (incl. any number of unit tests) is no proof UB code might not fail on a whim.
- Code with UB is objectively dangerous, invalid and should never exist.
Unsound Code
- Any safe Rust that could (even only theoretically) produce UB for any user input is always unsound.
- As is unsafe code that may invoke UB on its own accord by violating above-mentioned promises.
- Unsound code is a stability and security risk, and violates basic assumption many Rust users have.
Responsible use of Unsafe 💬
- Do not use unsafe unless you absolutely have to.
- Follow the Nomicon, Unsafe Guidelines, always follow all safety rules, and never invoke UB.
- Minimize the use of unsafe and encapsulate it in small, sound modules that are easy to review.
- Never create unsound abstractions; if you can't encapsulate unsafe properly, don't do it.
- Each unsafe unit should be accompanied by plain-text reasoning outlining its safety.
Adversarial Code 🝖url
Adversarial code is safe 3rd party code that compiles but does not follow API expectations, and might interfere with your own (safety) guarantees.
Implications
- Generic code cannot be safe if safety depends on type cooperation w.r.t. most (std::) traits.
- If type cooperation is needed you must use unsafe traits (prob. implement your own).
- You must consider random code execution at unexpected places (e.g., re-assignments, scope end).
- You may still be observable after a worst-case panic.
As a corollary, safe-but-deadly code (e.g., airplane_speed<T>()) should probably also follow these guides.
API Stabilityurl
When updating an API, these changes can break client code.RFC Major changes (🔴) are definitely breaking, while minor changes (🟡) might be breaking:
Links & Servicesurl
Comprehensive lookup tables for common components.
Online services which provide information or tooling.
Printing & PDFurl
Want this Rust cheat sheet as a PDF? Download the latest PDF here (A4) and in Letter. Alternatively, generate it yourself via File > Print and then "Save as PDF" (works great in Chrome, has some issues in Firefox).
.png)

