Rust Cheat Sheet
Rust Cheat Sheet
5. July 2022
Data Structures
Data types and memory locations defined via keywords.
Example Explanation
struct S Define a struct [ ](https://doc.rust-lang.org/book/ch05-00-structs.html) [EX](https://doc.rust-lang.org/stable/rust-by-
BK
struct S Define struct with named field x of type T .
{ x: T }
struct S Define "tupled" struct with numbered field .0 of type T .
(T);
enum E {
A, B Define variants of enum; can be unit- A , tuple- B () and struct-like C{} .
(), C {}
}
enum E { If variants are only unit-like, allow discriminant values, e.g., for FFI.
A = 1 }
union U
{} Unsafe C-like union REF for FFI compatibility. 🝖
2
Technically mutable and immutable are misnomer. Immutable binding or shared reference may still contain Cell [STD](https://doc.rust-lang.org/std/cell/index.html), giving interior
mutability.
Creating and accessing data structures; and some more sigilic types.
Example Explanation
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.
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.
Same, via range (here full range), also x[a..b] , x[a..=b] , ... c. below.
x[..]
Example Explanation
Example Explanation
&str Special string slice reference that contains ( address , length ).
&mut S Exclusive reference to allow mutability (also &mut [S] , &mut dyn S , …).
Create raw pointer w/o going through reference; c. ptr:addr_of!() [STD](https://doc.rust-
&raw
const s
lang.org/std/ptr/macro.addr_of.html) 🝖🚧
&raw mut Same, but mutable. 🚧 Raw ptrs. are needed for unaligned, packed fields. 🝖
s
let S {
Mutable ref binding ( let x = &mut s.x ), shorthand destructuring ↓ version.
ref mut x
} = s;
If r is a mutable reference, move or copy s to target memory.
*r = s;
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 place.
s = *r;
🔗
Special case[ ](https://www.reddit.com/r/rust/comments/b4so6i/what_is_exactly/ej8xwg8) for Box that can also move out
s =
*my_box;
Box'ed content if it isn't Copy .
&'a S Only accepts an address holding an s ; addr. existing 'a or longer.
Same, but allow content of address to be changed.
&'a mut S
struct Signals S will contain address with lifetime 'a . Creator of S decides 'a .
S<'a> {}
Example Explanation
trait Signals a S which impl T for S might contain address.
T<'a> {}
fn f<'a>
Same, for function. Caller decides 'a .
(t: &'a
T)
Example Explanation
trait T Define a trait; [ ](https://doc.rust-lang.org/book/ch10-02-traits.html) [EX](https://doc.rust-lang.org/stable/rust-by-
BK
impl T
for S {} Implement trait T for type S .
impl !T
for S {} Disable an automatically derived auto trait. [NOM](https://doc.rust-lang.org/nightly/nomicon/send-and-sync.html) REF 🚧🝖
fn f() Definition of a function; [BK](https://doc.rust-lang.org/book/ch03-03-how-functions-work.html) [EX](https://doc.rust-
{} lang.org/stable/rust-by-example/fn.html) REF or associated function if inside impl .
fn f() - Same, returning a value of type S.
> S {}
fn Define a method, [BK](https://doc.rust-lang.org/book/ch05-03-method-syntax.html) [EX](https://doc.rust-lang.org/stable/rust-
f(&self) by-example/fn/methods.html) REF e.g., within an impl S {} .
{}
struct S
More arcanely, also↑ defines fn S(x: T) -> S constructor function. RFC 🝖
(T);
const fn
f() {} Constant fn usable at compile time, e.g., const X: u32 = f(Y) . '18
async fn Async REF '18 function transformation, ↓ makes f return an impl Future . [STD](https://doc.rust-
f() {} lang.org/std/future/trait.Future.html)
async fn
Same, but make f return an impl Future<Output=S> .
f() -> S
{}
async { Used within a function, make { x } an impl Future<Output=X> .
x }
|x| x + Same, without block expression; may only consist of single expression.
x
move |x| Closure taking ownership of its captures; i.e., y transferred to closure.
x + y
return Closures sometimes look like logical ORs (here: return a closure).
|| true
unsafe
Means "calling can cause UB, ↓ YOU must check requirements".
fn f()
{}
unsafe
Means "careless impl. of T can cause UB; implementor must check".
trait T
{}
unsafe { Guarantees to compiler "I have checked requirements, trust me".
f(); }
unsafe
Guarantees S is well-behaved w.r.t T ; people may use T on S safely.
impl T
for S {}
Control Flow
Control execution within a function.
Example Explanation
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 Syntactic sugar to loop over iterators. [BK](https://doc.rust-lang.org/book/ch13-02-iterators.html) [STD](https://doc.rust-
iter {} lang.org/std/iter/index.html) REF
if x {}
else {} Conditional branch REF if expression is true.
Same, but make x value of the loop expression (only in actual loop ).
break x
break Exit not only this loop, but the enclosing one marked with 'label .
'label
break Same, but make x the value of the enclosing loop marked with 'label .
'label x
continue Continue expression REF to the next loop iteration of this loop.
Example Explanation
continue
'label Same but instead of this loop, enclosing loop marked with 'label.
Only works inside async . Yield flow until Future [STD](https://doc.rust-lang.org/std/future/trait.Future.html) or Stream x
x.await
ready. REF '18
return x Early return from function. More idiomatic way is to end with expression.
f() Invoke callable f (e.g., a function, closure, function pointer, Fn , …).
x.f() Call member function, requires f takes self , &self , … as first argument.
Same as x.f() . Unless impl Copy for X {} , f can only be called once.
X::f(x)
Same as x.f() .
X::f(&x)
X::f(&mut Same as x.f() .
x)
Same as x.f() if X derefs to S , i.e., x.f() finds methods of S .
S::f(&x)
Same as x.f() if X impl T , i.e., x.f() finds methods of T if in scope.
T::f(&x)
<X as Call trait method T::f() implemented for X .
T>::f()
Organizing Code
Segment projects into smaller units and minimize dependencies.
Example Explanation
Define a module, [ ](https://doc.rust-lang.org/book/ch07-02-defining-modules-to-control-scope-and-privacy.html) [EX]
BK
mod m {}
(https://doc.rust-lang.org/stable/rust-by-example/mod.html#modules) REF get definition from inside {} . ↓
mod m; Define a module, get definition from m.rs or m/mod.rs . ↓
::b Search b in crate root '15 REF or external prelude '18 REF; global path. REF 🗑️
Search b in crate root. '18
crate::b
Search b in parent module.
super::b
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 anonymously into scope, useful for traits with conflicting names.
Example Explanation
Bring everything from a in, only recommended if a is some prelude. [STD](https://doc.rust-
use a::*;
🔗
lang.org/std/prelude/index.html#other-preludes) [ ](https://stackoverflow.com/questions/36384840/what-is-the-prelude)
pub use
a::b; Bring a::b into scope and reexport from here.
pub(crate) Visible at most1 in current crate.
T
pub(super) Visible at most1 in parent.
T
pub(self) Visible at most1 in current module (default, same as no pub ).
T
pub(in Visible at most1 in ancestor a::b .
a::b) T
Declare external dependencies and ABI (e.g., "C" ) from FFI. [BK](https://doc.rust-lang.org/book/ch19-01-unsafe-
extern "C" rust.html#using-extern-functions-to-call-external-code) [EX](https://doc.rust-lang.org/stable/rust-by-
{} example/std_misc/ffi.html#foreign-function-interface) [NOM](https://doc.rust-lang.org/nightly/nomicon/ffi.html#calling-
foreign-functions) 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.
Example Explanation
type T = Create a type alias, [BK](https://doc.rust-lang.org/book/ch19-04-advanced-types.html#creating-type-synonyms-with-type-
S;
aliases) REF i.e., another name for S .
Self Type alias for implementing type, REF e.g. fn new() -> Self .
self Method subject in fn f(self) {} , same as fn f(self: Self) {} .
&self Same, but refers to self as borrowed, same as f(self: &Self)
Same, but mutably borrowed, same as f(self: &mut Self)
&mut self
self: Arbitrary self type, add methods to smart pointers ( my_box.f_of_self() ).
Box<Self>
Disambiguate [BK](https://doc.rust-lang.org/book/ch19-03-advanced-traits.html#fully-qualified-syntax-for-disambiguation-
S as T
calling-methods-with-the-same-name) REF type S as trait T , e.g., <S as T>::f() .
S as R In use of symbol, import S as R , e.g., use a::S as R .
Example Explanation
Macro [BK](https://doc.rust-lang.org/book/ch19-06-macros.html) [STD](https://doc.rust-lang.org/std/index.html#macros) REF
m!()
invocation, also m!{} , m![] (depending on macro).
#
[attr]
Outer attribute, [EX](https://doc.rust-lang.org/stable/rust-by-example/attribute.html) REF annotating the following item.
#!
[attr]
Inner attribute, annotating the upper, surrounding item.
Inside Macros Explanation
$x:ty Macro capture, the :... fragment REF declares what is allowed for $x . 1
$x Macro substitution, e.g., use the captured $x:ty from above.
$(x),* Macro repetition REF zero or more times in macros by example.
$(x)<<+ In fact separators other than , are also accepted. Here: << .
1 See Tooling Directives below for all captures.
Pattern Matching
Constructs found in match or let expressions, or function parameters.
Example Explanation
Initiate pattern matching, [BK](https://doc.rust-lang.org/book/ch06-02-match.html) [EX](https://doc.rust-
match m {}
lang.org/stable/rust-by-example/flow_control/match.html) REF then use match arms, c. next table.
let S(x) = Notably, let also destructures [EX](https://doc.rust-lang.org/stable/rust-by-
get(); example/flow_control/match/destructuring.html) similar to the table below.
let S { x } Only x will be bound to value s.x .
= s;
let (_, b, Only b will be bound to value abc.1 .
_) = abc;
let (a, ..) Ignoring 'the rest' also works.
= abc;
let (.., a,
Specific bindings take precedence over 'the rest', here a is 1 , b is 2 .
b) = (1,
2);
let w @ t @ Stores 3 copies of get() result in each w , t , f . 🝖
f = get();
let Some(x) Won't work 🛑 if pattern can be refuted, REF use if let instead.
= get();
if let
Some(x) = Branch if pattern can be assigned (e.g., enum variant), syntactic sugar. *
get() {}
Example Explanation
while let
Some(x) = Equiv.; here keep calling get() , run {} as long as pattern can be assigned.
get() {}
fn f(S { x
}: S)
Function parameters also work like let , here x bound to s.x of f(s) . 🝖
Pattern matching arms in match expressions. Left side of these arms can also be found in let expressions.
Within Match
Explanation
Arm
E::C { ..
} => {} Match enum struct variant C , wildcard any field.
S { x: 0,
y: 1 } => Match struct with specific values (only accepts s with s.x of 0 and s.y of 1 ).
{}
S { x: a,
y: b } => Match struct with any(!) values and bind s.x to a and s.y to b .
{}
S { x, y } Same, but shorthand with s.x and s.y bound as x and y respectively.
=> {}
S { .. }
=> {} Match struct with any values.
E::A | Same, but on enum variants.
E::Z => {}
E::C {x} |
Same, but bind x if all variants have it.
E::D {x}
=> {}
Some(A | Same, can also match alternatives deeply nested.
B) => {}
(a, 0) =>
{} Match tuple with any value for a and 0 for second.
[a, 0] => 🔗
Slice pattern, REF [ ](https://doc.rust-lang.org/edition-guide/rust-2018/slice-patterns.html) match array with any value for
{} a and 0 for second.
[1, ..] => Match array starting with 1 , any value for rest; subslice pattern. ?
{}
[1, .., 5] Match array starting with 1 , ending with 5 .
=> {}
Within Match
Explanation
Arm
[1, x @
Same, but also bind x to slice representing middle (c. pattern binding).
.., 5] =>
{}
[a, x @
Same, but match any first, last, bound as a , b respectively.
.., b] =>
{}
Open range pattern, matches 1 and any larger number.
1 .. => {}
Err(x @
Error Also works nested, here x binds to Error , esp. useful with if below.
{..}) =>
{}
Example Explanation
A generic [BK](https://doc.rust-lang.org/book/ch10-01-syntax.html) [EX](https://doc.rust-lang.org/stable/rust-by-
S<T>
example/generics.html) type with a type parameter ( T is placeholder name here).
Same, but w. lifetime. T must fulfill R , if T has lifetimes, must outlive 'a .
T: R + 'a
Opt out of a pre-defined trait bound, here Sized . ?
T: ?Sized
Same; does esp. not mean value t will 🛑 live 'static , only that it could.
T: 'static
'b: 'a Lifetime 'b must live at least as long as (i.e., outlive) 'a bound.
Example Explanation
S<const N:
usize>
Generic const bound; ? user of type S can provide constant value N .
S<10> Where used, const bounds can be provided as primitive values.
Expressions must be put in curly brackets.
S<{5+5}>
S<T> where
T: R
Almost same as S<T: R> but more pleasant to read for longer bounds.
S<T> where Also allows you to make conditional statements involving other types.
u8: R<T>
S<const N: Default parameter for constants; e.g., in f(x: S) {} param N is 0 .
u8 = 0>
Default parameter for types, e.g., in f(x: S) {} param T is u8 .
S<T = u8>
type X = Set associated type within impl T for S { type X = R; } .
R;
impl<T>
S<T> {}
Implement functionality for any T in S<T> , here T type parameter.
impl S<T>
{}
Implement functionality for exactly S<T> , here T specific type (e.g., S<u32> ).
fn f()
where Using Sized can opt f out of dyn T trait object vtable, enabling trait obj.
Self:
Sized;
fn f()
Other R useful w. dflt. methods (non dflt. would need be impl'ed anyway).
where
Self: R {}
Higher-Ranked Items 🝖
Actual types and traits, abstract over something, usually lifetimes.
Example Explanation
for<'a> Marker for higher-ranked bounds. [NOM](https://doc.rust-lang.org/nightly/nomicon/hrtb.html) 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 .
fn(&'_ u8) Same; automatically expanded to type for<'a> fn(&'a u8) .
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.
Implementing Traits Explanation
impl<'a> T for fn(&'a u8) {} For fn. pointer, where call accepts specific lt. 'a , impl trait T .
impl T for for<'a> fn(&'a u8) {} For fn. pointer, where call accepts any lt., impl trait T .
Example Explanation
"..." String literal, REF, 1 UTF-8, will interpret \n as line break 0xA , …
r"..." Raw string literal. REF, 1 UTF-8, won't interpret \n , …
r#"..."# Raw string literal, UTF-8, but can also contain " . Number of # can vary.
b"..." Byte string literal; REF, 1 constructs ASCII [u8] , not a string.
br"..." , br#"..."# Raw byte string literal, ASCII [u8] , combination of the above.
'🦀' Character literal, REF fixed 4 byte unicode 'char'. [STD](https://doc.rust-lang.org/std/primitive.char.html)
b'x' ASCII byte literal. REF
1 Debug ↓ Display ↓
Supports multiple lines out of the box. Just keep in mind (e.g., dbg!(x) and println!("{x:?}") ) might render them as \n , while (e.g., println!("{x}") )
renders them proper.
Documentation
Debuggers hate him. Avoid bugs with this one weird trick.
Example Explanation
Outer line doc comment,1 [BK](https://doc.rust-lang.org/book/ch14-02-publishing-to-crates-io.html#making-useful-
/// documentation-comments) [EX](https://doc.rust-lang.org/stable/rust-by-example/meta/doc.html#documentation) REF use
these on types, traits, functions, …
//! Inner line doc comment, mostly used at start of file to document module.
// Line comment, use these to document code flow or internals.
/*...*/ Block comment. 2 🗑️
Example Explanation
/**...*/ Outer block doc comment. 🗑️ 2
2
Generally discouraged due to bad UX. If possible use equivalent line comment instead with IDE support.
Miscellaneous
These sigils did not fit any other category but are good to know nonetheless.
Example Explanation
Always empty never type. 🚧 [BK](https://doc.rust-lang.org/book/ch19-04-advanced-types.html#the-never-type-that-never-
! returns) [EX](https://doc.rust-lang.org/stable/rust-by-example/fn/diverging.html#diverging-functions) [STD](https://doc.rust-
lang.org/std/primitive.never.html) REF
_ Unnamed variable binding, e.g., |x, _| {} .
let _ = Unnamed assignment is no-op, does not 🛑 move out x or preserve scope!
x;
Common Operators
Rust supports most operators you would expect ( + , * , % , = , == , …), including overloading. [STD](https://doc.rust-
lang.org/std/ops/index.html) Since they behave no differently in Rust we do not list them here.
Overview
Rust
→
CPU
Rust
→
Abstract Machine
→
CPU
🛑 Misleading.
Correct.
With 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,
forms a contract between programmer and machine,
and exploits all of the above for optimizations.
Misconceptions
Things people may incorrectly assume they should get away with if Rust targeted CPU directly, and more
correct counterparts:
Without AM With AM
0xffff_ffff would make a valid char . 🛑 Memory more than just bits.
0xff and 0xff are same pointer. 🛑 Pointers can come from different domains.
Any r/w pointer on 0xff always fine. 🛑 Read and write reference may not exist same time.
Null reference is just 0x0 in some register. 🛑 Holding 0x0 in reference summons Cthulhu.
Language Sugar
If something works that "shouldn't work now that you think about it", it might be due to one of these.
Name Description
Weakens types to match signature,
Coercions [NOM](https://doc.rust-lang.org/nightly/nomicon/coercions.html) e.g., &mut T to &T ; c. type
conversions. ↓
Deref [NOM](https://doc.rust-lang.org/nightly/nomicon/vec-deref.html) [ ] 🔗
Derefs x: T until *x , **x , …
(https://stackoverflow.com/questions/28519997/what-are-rusts-exact-auto-dereferencing-
compatible with some target S .
rules)
(e.g., fn S ).
Opinion 💬 — The features above will make your life easier, but might hinder your understanding. If any (type-related) operation ever
feels inconsistent it might be worth revisiting this list.
Application Memory ↕️
S(1)
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,2 in the term variable, it can mean the:
1. name of the location in the source file ("rename that variable"),
2. location in a compiled app, 0x7 ("tell me the address of that variable"),
3. 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.
S(1)
a t Moves ↕️
let a = t;
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.
}
...
M {
c Type Safety ↕️
let c: S = M::new();
)
S(3
S(1)
S(2)
▼ ▼
t Scope & Drop ↕️
let t = S(1);
let a = t;
} // <- Scope of `a`, `t`, `c` ends here, drop called on `a`, `c`.
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() .
Call Stack
S(1)
a x Function Boundaries ↕️
fn f(x: S) { ... }
f(a);
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) {
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.
S(1) M { }
a x m Repurposing Memory ↕️
fn f(x: S) {
if once() { f(x) }
let a = S(1);
f(a);
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.
▼
S(1) 0x3
a r References as Pointers ↕️
let a = S(1);
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 = &a;
▼
S(2)
0x3 S(1)
a
r d Access to Non-Owned Memory ↕️
let r = &mut a;
References can read from ( &S ) and also write to ( &mut S ) location they point to.
The dereference *r means to neither use the location of or value within r, but the location r points
to.
In example above, clone d is created from *r , and S(2) written to *r .
Method Clone::clone(&T) expects a reference itself, which is why we can use r, not *r .
On assignment *r = ... old value in location also dropped (not shown above).
⛔ M { x }
⛔
0x3
a
r d References Guard Referents ↕️
let r = &mut a;
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).
▼
0x3
p Raw Pointers ↕️
Lifetime Basics
"Lifetime" of Things ↕️
Every entity in a program has some (temporal / spatial) room where it is relevant, i.e., alive.
Loosely speaking, this alive time can be1
1. the LOC (lines of code) where an item is available (e.g., a module name).
2. the LOC between when a location is initialized with a value, and when the location is abandoned.
3. the LOC between when a location is first used in a certain way, and when that usage stops.
4. 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:
1. scope of that item, irrelevant here.
2. scope of that variable or location.
3. lifetime2 of that usage.
4. 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.
▼
S(2) 0xa
c r Meaning of r: &'c S ↕️
⛔ ▼
S(0)
S(3)
S(2) 0x6
a
b c r Typelikeness of Lifetimes ↕️
let b = S(3);
let c = S(2);
let r: &'c S = &c; // Does not quite work since we can't name lifetimes of local
r = &a; // Location of `a` does not live sufficient many lines -> not ok.
r = &b; // Location of `b` lives all lines of `c` and more -> ok.
▼
0x6
b Borrowed State ↕️
let r = &mut b;
print_byte(r);
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.
Lifetimes in Functions
S(1)
S(2)
? 0x6 0xa
b
c
r x y Function Parameters ↕️
let b = S(1);
let c = S(2);
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.
S(1)
S(2)
?
a
b
c
r Problem of 'Borrowed' Propagation ↕️
let b = S(1);
let c = S(2);
print_byte(r);
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.
▼
S(1)
S(2)
y + _
a
b
c
r Lifetimes Propagate Borrowed State ↕️
let b = S(1);
let c = S(2);
let r = f(&b, &c); // We know returned reference is `c`-based, which must stay locked,
let a = b;
print_byte(r);
S(2)
a
c
Unlocking ↕️
let mut c = S(2);
let r = f(&c);
let s = r;
print_byte(s);
A variable location is unlocked again once the last use of any reference that may point to it ends.
Memory Layout
Byte representations of common types.
Basic Types
Essential types built into the core of the language.
u8 , i8
u16 , i16
u32 , i32
u64 , i64
u128 , i128
f32
f64
usize , isize
Unsigned Types
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
Signed Types
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
Type Min Value
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
Float Types🝖
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
F
Explanation:
Casting Pitfalls 🛑
Arithmetical Pitfalls 🛑
200_u8 / _0
d, r
Panic. Regular math may panic; here: division by zero.
200_u8 +
200_u8
Compile error. -
200_u8 +
_200
Consider checked_ , wrapping_ , ... instead. [STD](https://doc.rust-
Panic.
d lang.org/std/primitive.isize.html#method.checked_add)
200_u8 +
_200 144 In release mode this will overflow.
r
1.0_f32 /
0.0_f32
f32::INFINITY -
Operation1 Gives Note
0.0_f32 /
0.0_f32
f32::NAN -
x <
f32::NAN
false NAN comparisons always return false.
x >
f32::NAN
false -
f32::NAN ==
f32::NAN
false -
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.
char
str
...
U
T
F
-
8
... unspecified times
Basics
Type Description
Always 4 bytes and only holds a single Unicode scalar value [ ] 🔗
char
(https://www.unicode.org/glossary/#unicode_scalar_value).
str An u8 -array of unknown length guaranteed to hold UTF-8 encoded code points.
Usage
Chars Description
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.
1
Fun fact, due to the Zero-width joiner (⨝) what the user perceives as a character can get even more unpredictable: 👨👩👧 is in fact 5 chars 👨⨝👩
⨝ 👧, and rendering engines are free to either show them fused as one, or separately as three, depending on their abilities.
Strings Description
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.
Encoding🝖
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.as_bytes() 49 20 e2 9d a4 ef b8 8f 20 52 75 73 74 4
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 …
1
Result then collected into array and transmuted to bytes.
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 Types
Basic types definable by users. Actual layout REF is subject to representation; REF padding can be present.
T
T: ?Sized
[T; n
n]
]
[T]
T ←T→ T
T
T
... n times ...
T
T
T
... unspecified times
Sized ↓ Maybe DST ↓ Fixed array of n elements. Slice type of unknown-many elements. Neither
struct
(A, B, C)
struct S { b
b:
: B, c
c:
: C }
S;
A
B B
C
Zero-Sized ↓ C or maybe
or maybe C
↦
B
B
A Compiler may also add padding.
unspecified.
Also note, two types A(X, Y) and B(X, Y) with exactly the same fields can still have differing layout; never transmute() without representation guarantees.
These sum types hold a value of one of their sub types:
enum E { A, B, C }
union { ... }
Tag
A A
exclusive or unsafe or
Tag
B B
exclusive or unsafe or
Tag
C C
&'a T
*const T
ptr 2/4/8
meta 2/4/8 ptr 2/4/8
meta 2/4/8
|
No guarantees.
←T
→
(any mem)
at least 'a .
Pointer Meta
Many reference and pointer types can carry an extra field, pointer metadata. [STD](https://doc.rust-
lang.org/nightly/std/ptr/trait.Pointee.html#pointer-metadata)
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
&'a T
&'a [T]
| | |
T ←T ...
T
T
...
(any mem)
→ (any mem)
No meta for
(any mem) Regular slice reference (i.e., the
sized target.
reference type of a slice type [T] ) ↑
&'a str
ptr 2/4/8
len 2/4/8
|
...
U
T
F
-
8
...
(any mem)
ptr 2/4/8
ptr 2/4/8
| |
←T *Drop
Drop::
::drop
drop(
(&mut T)
→ size
(any mem)
align
*Trait
Trait::
::f
f(&T, ...
...)
)
*Trait
Trait::
::g
g(&T, ...
...)
)
(static vtable)
Closures
Ad-hoc functions with an automatically managed data block capturing REF
environment where closure was defined. For example:
move |x| x + y
y.
.f() + z
|x| x + y
y.
.f() + z
Y
Z ptr 2/4/8
ptr 2/4/8
Y Z
(any mem) (any mem)
Also produces anonymous fn such as fc1(C1, X) or fc2(&C2, X) . Details depend which FnOnce , FnMut , Fn ... is supported, based on properties of captured types.
UnsafeCell<
UnsafeCell<T>
Cell
Cell<
<T>
RefCell
RefCell<
<T>
AtomicUsize
Result
Result<
<T, E>
aliased mutability.
to move in
borrowing of T . Like Cell this
Tag
T
and out. is Send , but not Sync .
Option<
Option<T>
Tag
or
Tag
T
Order-Preserving Collections
Box<
Box<T>
Vec
Vec<
<T>
ptr 2/4/8
meta 2/4/8 ptr 2/4/8
capacity 2/4/8
len 2/4/8
| |
←T
T
... len
T
→ ← capacity → (heap)
Regular growable array vector of single type.
(heap)
LinkedList<
LinkedList<T> 🝖
VecDeque
VecDeque<
<T>
head 2/4/8
tail 2/4/8
len 2/4/8 tail 2/4/8
head 2/4/8
ptr 2/4/8
capacity 2/4/8
| | |
next 2/4/8
prev 2/4/8
T TT
... empty ...
TH
(heap) ← capacity → (heap)
Elements head and tail both null or point to nodes on
Index tail and head select in array-as-ringbuffer. This means content
the heap. Each node can point to its prev and next node.
may be non-contiguous and empty in the middle, as exemplified above.
Eats your cache like no tomorrow (just look at the thing!);
Other Collections
HashMap<
HashMap<K, V>
BinaryHeap
BinaryHeap<
<T>
bmask 2/4/8
ctrl 2/4/8
left 2/4/8
len 2/4/8 ptr 2/4/8
capacity 2/4/8
len 2/4/8
| |
K:V
K:V
...
K:V
...
K:V T0
T1
T1
T2
T2
... len
Oversimplified! (heap) ← capacity → (heap)
Owned Strings
String
CString
OsString ?
ptr 2/4/8
capacity 2/4/8
len 2/4/8 ptr 2/4/8
len 2/4/8 Platform Defined
| | |
U
T
F
-
8
... len A
B
C
... len ...
␀ ?
?
/
?
?
← capacity → (heap) (heap) (heap)
Windows).
PathBuf ?
OsString
|
?
?
/
?
?
(heap)
represents paths.
Shared Ownership
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<
Rc<T>
Arc
Arc<
<T>
ptr 2/4/8
meta 2/4/8 ptr 2/4/8
meta 2/4/8
| |
strng 2/4/8
weak 2/4/8
← T → strng 2/4/8
weak 2/4/8
← T →
(heap) (heap)
or RefCell to allow mutation. Is neither Send nor Sync . T itself is Send and Sync .
Mutex<
Mutex<T> / RwLock<
RwLock<T>
ptr 2/4/8
poison 2/4/8
←T→
|
lock
(heap)
threads,
always Send and Sync . Consider using
Standard Library
One-Liners
🔗
Snippets that are common, but still easy to forget. See Rust Cookbook [ ](https://rust-lang-nursery.github.io/rust-cookbook/) for more.
Strings
Intent Snippet
Concatenate strings (any Display ↓ that is). 1 '21 format!("{x}{y}")
(https://stackoverflow.com/a/38138985)
... with &str s.split("abc")
Regex::new(r"\s")?.split("one
Split by regular expression.2 two three")
1
Allocates; if x or y are not going to be used afterwards consider using write! or std::ops::Add .
2
Requires regex crate.
I/O
Intent Snippet
Create a new file File::create(PATH)?
Macros
Intent Snippet
Macro w. variable arguments macro_rules! var_args { ($($args:expr),*) => {{ }} }
Esoterics🝖
Intent Snippet
wants_closure({ let c = outer.clone(); move || use_clone(c)
Cleaner closure captures })
Fix inference in ' try ' closures iter.try_for_each(|x| { Ok::<(), Error>(()) })?;
Thread Safety
Examples Send * !Send
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.
Iterators
Obtaining Iterators
Basics
Assume you have a collection c of type C:
The Iterator
For Loops
Implementing Iterators
Basics
struct IntoIter<T> {} — Create a struct to hold your iteration status (e.g., an index) for value iteration.
impl Iterator for IntoIter {} — Implement Iterator::next() so it can produce elements.
Collection<T>
IntoIter<T>
⌾ Iterator
Item = T;
Iter<T>
IterMut<T>
⌾ Iterator ⌾ Iterator
Item = &T; Item = &mut T;
Making Loops Work
Collection<T>
&Collection<T>
&mut Collectn<T>
Number Conversions
As-correct-as-it-currently-gets number conversions.
2
Truncating ( 11.9_f32 as u8 gives 11 ) and saturating ( 1024_f32 as u8 gives 255 ); c. below.
String Conversions
If you want a string of type …
String
CString x.into_string()?
OsString x.to_str()?.to_string()
PathBuf x.to_str()?.to_string()
Vec<u8> 1 String::from_utf8(x)?
i
&str x.to_string()
&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()
CString
CString x
OsString 2 CString::new(x.to_str()?)?
PathBuf CString::new(x.to_str()?)?
Vec<u8> 1 CString::new(x)?
&str CString::new(x)?
i
&CStr x.to_owned()
&OsStr 2 CString::new(x.to_os_string().into_string()?)?
&Path CString::new(x.to_str()?)?
&[u8] 1 CString::new(Vec::from(x))?
OsString
CString OsString::from(x.to_str()?)
OsString x
PathBuf x.into_os_string()
Vec<u8> 1 ?
i
&str OsString::from(x)
&CStr OsString::from(x.to_str()?)
i
&OsStr OsString::from(x)
&Path x.as_os_str().to_owned()
&[u8] 1 ?
PathBuf
CString PathBuf::from(x.to_str()?)
i
OsString PathBuf::from(x)
PathBuf x
Vec<u8> 1 ?
If you have x of type … Use this …
i
&str PathBuf::from(x)
&CStr PathBuf::from(x.to_str()?)
i
&OsStr PathBuf::from(x)
i
&Path PathBuf::from(x)
&[u8] 1 ?
Vec<u8>
CString x.into_bytes()
OsString ?
PathBuf ?
Vec<u8> 1 x
&str Vec::from(x.as_bytes())
&CStr Vec::from(x.to_bytes_with_nul())
&OsStr ?
&Path ?
&[u8] 1 x.to_vec()
&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)?
&CStr
If you have x of type … Use this …
String CString::new(x)?.as_c_str()
CString x.as_c_str()
OsString 2 x.to_str()?
PathBuf ?,4
&str ?,4
&CStr x
&OsStr 2 ?
&Path ?
&OsStr
CString ?
OsString x.as_os_str()
PathBuf x.as_os_str()
Vec<u8> 1 ?
&str OsStr::new(x)
&CStr ?
&OsStr x
&Path x.as_os_str()
&[u8] 1 ?
&Path
CString Path::new(x.to_str()?)
r
OsString Path::new(x.to_str()?)
r
PathBuf Path::new(x.to_str()?)
Vec<u8> 1 ?
r
&str Path::new(x)
&CStr Path::new(x.to_str()?)
If you have x of type … Use this …
r
&OsStr Path::new(x)
&Path x
&[u8] 1 ?
&[u8]
CString x.as_bytes()
OsString ?
PathBuf ?
Vec<u8> 1 &x
&str x.as_bytes()
&CStr x.to_bytes_with_nul()
&OsStr x.as_bytes() 2
&Path ?
&[u8] 1 x
Other
1 You should, or must if call is unsafe , ensure raw data comes with a valid representation for the string type (e.g., UTF-8 data for a String ).
2 Only on some platforms std::os::<your_os>::ffi::OsStrExt exists with helper methods to get a raw &[u8] representation of the underlying OsStr . Use the rest of the table to
go from there, e.g.:
use std::os::unix::ffi::OsStrExt;
CString::new(bytes)?
3 The c_char must have come from a previous CString . If it comes from FFI see &CStr instead.
4 No known shorthand as x will lack terminating 0x0 . Best way to probably go via CString .
String Output
How to convert types into a String , or output them.
APIs
Rust has, among others, these APIs to convert types to stringified output, collectively called format macros:
Method Notes
x.to_string() [STD](https://doc.rust- Produces String , implemented for any
lang.org/std/string/trait.ToString.html) Display type.
Here fmt is string literal such as "hello {}" , that specifies output (compare "Formatting" tab) and additional
parameters.
Printable Types
In format! and friends, types convert via trait Display "{}" [STD](https://doc.rust-
Type Implements
String Debug, Display
CString Debug
OsString Debug
PathBuf Debug
Vec<u8> Debug
&CStr Debug
&OsStr Debug
&Path Debug
&[u8] Debug
! Debug, Display
() Debug
In short, pretty much everything is Debug ; more special types might need special handling or conversion ↑ to
Display .
Formatting
Each argument designator in format macro is either empty {} , {argument} , or follows a basic syntax:
{ [argument] ':' [[fill] align] [sign] ['#'] [width [$]] ['.' precision [$]] [type] }
Element Meaning
argument Number ( 0 , 1, ...), variable '21 or name,'18 e.g., print!("{x}") .
fill The character to fill empty spaces with (e.g., 0 ), if width is specified.
align Left ( < ), center ( ^ ), or right ( > ), if width is specified.
sign Can be + for sign to always be printed.
Alternate formatting, e.g. prettify Debug [STD](https://doc.rust-lang.org/std/fmt/trait.Debug.html)
#
formatter ? or prefix hex with 0x .
width Minimum width (≥ 0), padding with fill (default to space). If starts with 0, zero-padded.
precision Decimal digits (≥ 0) for numerics, or max width for non-numerics.
$ Interpret width or precision as argument identifier instead to allow for dynamic formatting.
Debug [STD](https://doc.rust-lang.org/std/fmt/trait.Debug.html) ( ? ) formatting, hex ( x ), binary (
type
b ), octal ( o ), pointer ( p ), exp ( e ) ... see more.
Format
Explanation
Example
Print the next argument using Display .[STD](https://doc.rust-
{}
lang.org/std/fmt/trait.Display.html)
{x} Same, but use variable x from scope. '21
{:?} Print the next argument using Debug .[STD](https://doc.rust-lang.org/std/fmt/trait.Debug.html)
Full Example Explanation
Full Example Explanation
println!("{}", Print x using Display [STD](https://doc.rust-lang.org/std/fmt/trait.Display.html) on std.
x) out and append new line. '15 🗑️
println!("{x}") Same, but use variable x from scope. '21
format!("{a:.3} Convert PI with 3 digits, add space, b with Debug [STD](https://doc.rust-
{b:?}") lang.org/std/fmt/trait.Debug.html), return String . '21
Tooling
Project Anatomy
Basic project layout, and common files and folders, as used by cargo . ↓
Entry Code
🔗
📁 .cargo/
Project-local cargo configuration, may contain config.toml . [ ](https://doc.rust-
lang.org/cargo/reference/config.html) 🝖
📁 benches/ Benchmarks for your crate, run via cargo bench , requires nightly by default. * 🚧
📁 examples/ Examples how to use your crate, they see your crate like external user would.
my_example.rs Individual examples are run like cargo run --example my_example .
main.rs Default entry point for applications, this is what cargo run uses.
lib.rs Default entry point for libraries. This is where lookup for my_crate::f() starts.
extra.rs Additional binary, run with cargo run --bin extra .
📁 tests/ Integration tests go here, invoked via cargo test . Unit tests often stay in src/ file.
.rustfmt.toml In case you want to customize how cargo fmt works.
.clippy.toml Special configuration for certain clippy lints, utilized via cargo clippy 🝖
🔗
Pre-build script, [ ](https://doc.rust-lang.org/cargo/reference/build-scripts.html) useful when compiling C / FFI,
build.rs
...
🔗
Main project manifest, [ ](https://doc.rust-lang.org/cargo/reference/manifest.html) Defines dependencies,
Cargo.toml
artifacts ...
Cargo.lock Dependency details for reproducible builds; add to git for apps, not for libs.
🔗
Define toolchain override[ ](https://rust-lang.github.io/rustup/overrides.html) (channel, components, targets)
rust-toolchain.toml
for this project.
* On stable consider Criterion.
Minimal examples for various entry points might look like:
Applications
Libraries
// src/main.rs (default application entry point)
fn main() {
println!("Hello, world!");
pub fn f() {} // Is a public item in root, so it's accessible from the outside.
mod m {
pub fn g() {} // No public path (`m` not public) from root, so `g`
Unit Tests
#[cfg(test)]
mod test {
#[test]
fn ff() {
assert_eq!(f(), 0);
Integration Tests
#[test]
fn my_sample() {
assert_eq!(my_crate::f(), 123); // Integration tests (and benchmarks) 'depend' to the crate lik
} // a 3rd party would. Hence, they only see public items.
Benchmarks
extern crate test; // Even in '18 this is needed ... for reasons.
#[bench]
Build Scripts
fn main() {
// You need to rely on env. vars for target; `#[cfg(...)]` are for host.
Proc Macros🝖
use proc_macro::TokenStream;
item
// Cargo.toml
[package]
name = "my_crate"
version = "0.1.0"
[lib]
proc-macro = true
Module trees and imports:
Module Trees
Module tree needs to be explicitly defined, is not implicitly built from file system tree. [ ] 🔗
(http://www.sheshbabu.com/posts/rust-module-system/)
Module tree root equals library, app, … entry point (e.g., lib.rs ).
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! 🛑
Namespaces🝖
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 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.
Cargo
Commands and tools that are good to know.
Command Description
cargo init Create a new project for the latest edition.
cargo build Build the project in debug mode ( --release for all optimization).
cargo check Check if project would compile (much faster).
cargo test Run tests for the project.
cargo doc --open Locally generate documentation for your code and dependencies.
cargo run Run your project, if a binary is produced (main.rs).
cargo run --bin b Run binary b . Unifies features with other dependents (can be confusing).
cargo run -p w Run main of sub-workspace w . Treats features more as you would expect.
cargo ... --timings Show what crates caused your build to take so long, highly useful. 🔥
cargo tree Show dependency graph.
cargo +{nightly, stable} ... Use given toolchain for command, e.g., for 'nightly only' tools.
cargo +nightly ... Some nightly-only commands (substitute ... with command below)
These are optional rustup components.
Install them with rustup component add [tool] .
Tool Description
cargo clippy 🔗
Additional (lints) catching common API misuses and unidiomatic code. [ ](https://github.com/rust-lang/rust-clippy)
cargo fmt 🔗
Automatic code formatter ( rustup component add rustfmt ). [ ](https://github.com/rust-lang/rustfmt)
A large number of additional cargo plugins can be found here.
Cross Compilation
🔘 Check target is supported.
🔘 Install target via .
rustup target install X
[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.
Tooling Directives
Special tokens embedded in source code used by tooling or preprocessing.
Macros
Documentation
```X,Y ...``` Same, and include optional configurations; with X, Y being ...
rust Make it explicit test is written in Rust; implied by Rust tooling.
- Compile test. Run test. Fail if panic. Default behavior.
should_panic Compile test. Run test. Execution should panic. If not, fail test.
no_run Compile test. Fail test if code can't be compiled, Don't run test.
compile_fail Compile test but fail test if code can be compiled.
ignore Do not compile. Do not run. Prefer option above instead.
edition2018 Execute code as Rust '18; default is '15.
# Hide line from documentation ( ``` # use x::hidden; ``` ).
[`S`] Create a link to struct, enum, trait, function, … S.
#![globals]
Opt-Out's On Explanation
Don't (automatically) import std [STD](https://doc.rust-lang.org/std/) ; use
#![no_std] C
core [STD](https://doc.rust-lang.org/core/) instead. REF
#![crate_type = "bin"] C Specifiy current crate type ( bin , lib , dylib , cdylib , ...). REF 🝖
#![recursion_limit = "123"] C Set compile-time recursion limit for deref, macros, ... REF 🝖
#![type_length_limit = "456"] C Limits maximum number of type substitutions. REF 🝖
Handlers On Explanation
#[panic_handler] F Make some fn f(&PanicInfo) -> ! app's panic handler. REF
# Make static item impl. GlobalAlloc [STD](https://doc.rust-
S
[global_allocator] lang.org/alloc/alloc/trait.GlobalAlloc.html) global allocator. REF
#[code]
Developer UX On Explanation
#[non_exhaustive] T Future-proof struct or enum ; hint it may grow in future. REF
#[path = "x.rs"] M Get module from non-standard file. REF
Codegen On Explanation
#[inline] F Nicely suggest compiler should inline function at call sites. REF
#[inline(always)] F Emphatically threaten compiler to inline call, or else. REF
Instruct compiler to feel disappointed if it still inlines the function.
#[inline(never)] F
REF
#[repr(C, u8)] enum Give enum discriminant the specified type. REF
#[repr(transparent)] T Give single-element type same layout as contained field. REF
Lower alignment of struct and contained fields, mildly UB prone.
#[repr(packed(1))] T
REF
Codegen On Explanation
Raise alignment of struct to given value, e.g., for SIMD types.
#[repr(align(8))] T
REF
1
Some representation modifiers can be combined, e.g., #[repr(C, packed(1))] .
Linking On Explanation
#[no_mangle] * Use item name directly as symbol name, instead of mangling. REF
#[no_link] X Don't link extern crate when only wanting macros. REF
#[link(name="x", kind="y")] X Native lib to link against when looking up symbol. REF
#[link_name = "foo"] F Name of symbol to search for resolving extern fn . REF
#[link_section = ".sample"] FS Section name of object file where item should be placed. REF
#[export_name = "foo"] FS Export a fn or static under a different name. REF
#[used] S Don't optimize away static variable despite it looking unused. REF
#[quality]
Tests On Explanation
#[test] F Marks the function as a test, run with cargo test . 🔥 REF
#[ignore = "msg"] F Compiles but does not execute some #[test] for now. REF
#[should_panic] F Test must panic!() to actually succeed. REF
#[bench] F Mark function in bench/ as benchmark for cargo bench . 🚧 REF
Formatting On Explanation
Prevent cargo fmt from cleaning up item. [ ] 🔗
#[rustfmt::skip] *
(https://github.com/rust-lang/rustfmt)
... from cleaning up macro x. 🔗
[ ](https://github.com/rust-
#![rustfmt::skip::macros(x)] CM
lang/rustfmt)
Formatting On Explanation
#! ... from cleaning up attribute x. 🔗
[ ](https://github.com/rust-
CM
[rustfmt::skip::attributes(x)] lang/rustfmt)
Documentation On Explanation
Same as adding a /// 🔗
doc comment. [ ](https://doc.rust-
#[doc = "Explanation"] *
lang.org/rustdoc/the-doc-attribute.html)
Provide another name users can search for in the docs. [ ] 🔗
#[doc(alias = "other")] *
(https://github.com/rust-lang/rust/issues/50146)
🔗
Prevent item from showing up in docs. [ ](https://doc.rust-
#[doc(hidden)] *
lang.org/rustdoc/write-documentation/the-doc-attribute.html#hidden)
Sets the favicon 🔗
for the docs. [ ](https://doc.rust-
#![doc(html_favicon_url
= "")]
C lang.org/rustdoc/write-documentation/the-doc-
attribute.html#html_favicon_url)
#] documentation/the-doc-attribute.html#html_logo_url)
#! Generates Run 🔗
buttons and uses given service. [ ](https://doc.rust-
[doc(html_playground_url C lang.org/rustdoc/write-documentation/the-doc-
= "")]
attribute.html#html_playground_url)
🔗
Base URL for links to external crates. [ ](https://doc.rust-
#![doc(html_root_url =
"")]
C lang.org/rustdoc/write-documentation/the-doc-
attribute.html#html_root_url)
🔗
Prevents source from being included in docs. [ ](https://doc.rust-
#![doc(html_no_source)] C lang.org/rustdoc/write-documentation/the-doc-
attribute.html#html_no_source)
#[macros]
Proc Macros On Explanation
#[proc_macro] F Mark fn as function-like procedural macro callable as m!() . REF
#[proc_macro_attribute] F Mark fn as attribute macro which can understand new #[x] . REF
Derives On Explanation
#[derive(X)] T Let some proc macro provide a goodish impl of trait X . 🔥 REF
#[cfg]
⚠️ Note, options can generally be set multiple times, i.e., the same key can show up with multiple values. One
can expect #[cfg(target_feature = "avx")] and #[cfg(target_feature = "avx2")] to be true at the same
time.
Known Options On Explanation
#[cfg(target_arch = "x86_64")] * The CPU architecture crate is compiled for. REF
#[cfg(target_feature = "avx")] * Whether a particular class of instructions is available. REF
#[cfg(target_os = "macos")] * Operating system your code will run on. REF
#[cfg(target_family = "unix")] * Family operating system belongs to. REF
#[cfg(target_env = "msvc")] * How DLLs and functions are interfaced with on OS. REF
#[cfg(target_endian = "little")] * Main reason your cool new zero-cost protocol fails. REF
#[cfg(target_pointer_width = "64")] * How many bits pointers, usize and CPU words have. REF
#[cfg(target_vendor = "apple")] * Manufacturer of target. REF
#[cfg(debug_assertions)] * Whether debug_assert!() and friends would panic. REF
#[cfg(panic = "unwind")] * Whether unwind or abort will happen on panic. ?
#[cfg(proc_macro)] * Wheter crate compiled as proc macro. REF
#[cfg(test)] * Whether compiled with cargo test . 🔥 REF
#[cfg(feature = "serde")] * When your crate was compiled with feature serde . 🔥 REF
build.rs
🔗
Explanation [ ](https://doc.rust-
Input Environment
lang.org/cargo/reference/environment-variables.html)
CARGO_FEATURE_X Environment variable set for each feature x activated.
CARGO_FEATURE_SERDE If feature serde were enabled.
CARGO_FEATURE_SOME_FEATURE If feature some-feature were enabled; dash - converted to _.
🔗
Explanation [ ](https://doc.rust-
Input Environment
lang.org/cargo/reference/environment-variables.html)
CARGO_CFG_X Exposes cfg's; joins mult. opts. by , and converts - to _.
If target_feature were set to avx and avx2 .
CARGO_CFG_TARGET_FEATURE=avx,avx2
🔗
Explanation [ ](https://doc.rust-lang.org/cargo/reference/build-
Output String
scripts.html)
cargo:rerun-if-changed=PATH (Only) run this build.rs again if PATH changed.
cargo:rerun-if-env-changed=VAR (Only) run this build.rs again if environment VAR changed.
cargo:rustc-link-lib=[KIND=]NAME Link native library as if via -l option.
cargo:rustc-link-search=[KIND=]PATH Search path for native library as if via -L option.
cargo:rustc-flags=FLAGS Add special flags to compiler. ?
cargo:rustc-cfg=KEY[="VALUE"] Emit given cfg option to be used for later compilation.
cargo:rustc-env=VAR=VALUE Emit var accessible via env!() in crate during compilation.
cargo:rustc-cdylib-link-arg=FLAG When building a cdylib , pass linker flag.
cargo:warning=MESSAGE Emit compiler warning.
Emitted from build.rs via println!() . List not exhaustive.
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.
! means on macros.
u8
String
Device
Type Values
u8 { 0u8, 1u8, ..., 255u8 }
char 🦀' }
{ 'a', 'b', ... '
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.
Type Values
u8 { 0u8, 1u8, ..., 255u8 }
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. [ ](https://featherweightmusings.blogspot.com/2014/03/subtyping-and-coercion-in-rust.html)
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
String
Port
impl Port {
fn f() { ... }
Types usually come with implementation, 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:
u8 u8 char
trait ShowHex {
fn print_hex() {}
⌾ Copy
trait Copy { }
⌾ Sized
ShowHex Trait
Self
Port
Visually, you can think of the type getting a "badge" for its membership:
u8
Device
Port
⌾ Clone ⌾ Clone
⌾ Copy ⌾ ShowHex
👩🦰
⌾ Eat
🧔
Venison
🎅
venison.eat()
⌾ Eat
Interfaces
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 :
import food.Venison;
new Venison("rudolph").eat();
👩🦰
⌾ Eat
🧔
Venison
👩🦰 / 🧔
Venison
🎅
venison.eat()
⌾ Eat
Traits
// Santa needs to import `Venison` to create it, and import `Eat` for trait method.
use food::Venison;
use tasks::Eat;
// Ho ho ho
Venison::new("rudolph").eat();
*
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. ?
Generics
Vec<u8>
Vec<char>
Vec<u8> is type "vector of bytes"; Vec<char> is type "vector of chars", but what is Vec<> ?
Construct Values
Vec<u8> { [], [1], [1, 2, 3], ... }
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.
Vec<T>
[T; 128]
&T
&mut T
S<T>
// S<> is type constructor with parameter T; user can supply any concrete type for T.
struct S<T> {
x: T
fn f() {
let x: S<f32> = S::new(0_f32);
[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> .
🧔
Num<T>
→
🎅
Num<u8>
Num<f32>
Num<Cmplx>
u8
Port
⌾ Absolute ⌾ Clone
⌾ Dim ⌾ ShowHex
⌾ Mul
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":
Absolute Trait
Self
u8
// Type can only be constructed for some `T` if that
u16
// T is part of `Absolute` membership list.
...
We add bounds to the struct here. In practice it's nicer add bounds to the respective impl blocks instead, see later this section.
u8
f32
char
Cmplx
Car
⌾ Mul ⌾ Mul
⌾ DirName
⌾ TwoD
struct S<T>
where
{ ... }
fn f(&self, x: T) { ... };
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 S<T> ,
containing the methods ...
You can think of such impl<T> ... {} code as abstractly implementing a family of behaviors. Most notably,
rd
they allow 3 parties to transparently materialize implementations similarly to how type constructors materialize
types:
// - create two new version of `f`, one for `char`, another one for `u32`.
s.f(0_u32);
s.f('x');
// Also implements Serialize for any type if that type already implements ToHex
→ Whatever
ToHex Serialize Trait
was in left table,
Self Self
may be added to
Port right table, u8
They can be neat way to give foreign types functionality in a modular way if they just implement another interface.
Advanced Concepts🝖
Port
Port
⌾ From<u8> ⌾ Deref
type u8;
⌾ From<u16>
Why is that?
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:
impl From<u8> for u16 {}
From Deref
Self I Self O
u16 u8 Port u8
... ...
type O1;
type O2;
Complex
Self [I] I1 I2 O1 O2
NiceMonster
🛑 u16 String u8 u16
Various trait implementations. The last one is not valid as (NiceMonster, u16, String) has
car.a(0_f32)
⌾ A<I>
👩🦰
⌾ B
🧔
Car
👩🦰 / 🧔
Car
🎅
car.b(0_u8)
type O; car.b(0_f32)
⌾ B
T = u8;
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).
trait A<I> { }
trait B { type O; }
A B
Self I Self O
Y ... X u32
Santa may add more members by For given set of inputs (here Self ),
providing his own type for T.
implementor must pre-select O .
⌾ Query
vs.
⌾ Query<I>
vs.
⌾ Query
vs.
⌾ Query<I>
type O; type O;
Choice of parameters goes along with purpose trait has to fill.
No Additional Parameters
trait Query {
postgres.search("SELECT ...");
👩🦰
⌾ Query
→
🧔
PostgreSQL
Sled
⌾ Query ⌾ Query
Trait author assumes:
Input Parameters
trait Query<I> {
postgres.search("SELECT ...");
postgres.search(input.to_string());
sled.search(file);
👩🦰
⌾ Query<I>
→
🧔
PostgreSQL
Sled
⌾ Query<&str> ⌾ Query<T>
⌾ where T is ToU8Slice
↲
Query<String>
.
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;
postgres.search("SELECT ...".to_string());
sled.search(vec![0, 1, 2, 4]);
👩🦰
⌾ Query
→
🧔
PostgreSQL
Sled
type O;
⌾ Query ⌾ Query
O = String; 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!
trait Query<I> {
type O;
postgres.search("SELECT ...").to_uppercase();
sled.search(&[1, 2, 3, 4]).pop();
👩🦰
⌾ Query<I>
→
🧔
PostgreSQL
Sled
type O;
⌾ Query<&str> ⌾ Query<T>
O = String; O = Vec<u8>;
⌾ where T is ToU8Slice
↲
Query<CString>
.
O = CString;
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.
MostTypes
vs.
Z
vs.
str
[u8]
dyn Trait
...
Example Explanation
struct A { x: u8 } Type A is sized, i.e., impl Sized for A holds, this is a 'regular' type.
struct B { x: [u8] } Since [u8] is a DST, B in turn becomes DST, i.e., does not impl Sized .
Type params have implicit T: Sized bound, e.g., C<A> is valid, C<B> is
struct C<T> { x: T }
not.
struct D<T: ?Sized> { x: T
Using ?Sized REF allows opt-out of that bound, i.e., D<B> is also valid.
}
struct E; Type E is zero-sized (and also sized) and will not consume memory.
trait F { fn f(&self); } Traits do not have an implicit Sized bound, i.e., impl F for B {} is valid.
trait F: Sized {} Traits can however opt into Sized via supertraits.↑
trait G { fn g(self); } For Self -like params DST impl may still fail as params can't go on stack.
?Sized
S<T>
→
S<u8>
S<char>
S<str>
S<T>
→
S<u8>
S<char>
S<str>
S<'a>
&'a f32
&'a mut u8
S<'a>
→
S<'auto>
S<'static>
struct S<'a> {
x: &'a u32
// In non-generic code, 'static is the only nameable lifetime we can explicitly put in here.
let a: S<'static>;
// Alternatively, in non-generic code we can (often must) omit 'a and have Rust determine
let b: S;
*
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. [ ](https://medium.com/nearprotocol/understanding-rust-
lifetimes-e813bcd405fa)
Note to self and TODO: that analogy seems somewhat flawed, as if S<'a> is to S<'static> like S<T> is to
S<u32> , then 'static would be a type; but then what's the value of that type?
Type Zoo
A visual overview of types and traits in crates.
bool ⌾ Copy
u8
Builder
⌾ From<T>
From<T>
f32 ⌾ ⌾ From<T> File
f32 ⌾ From<T> File
⌾ Deref
u16 char
type Tgt; String
&'a TT
&'a
&'a T
Vec<T>
Vec<T> Vec<T>
Vec<T>
Vec<T> f<T>() {}
PI
&mut 'a
&mut 'a T
&mut 'a TT
drop() {}
[T; n]
[T; n] dbg!
[T; n]
T
⌾ Serialize Device String String Port Container TT
Your c
A walk through the jungle of types, traits, and implementations that (might possibly) exist in your application.
Type Conversions
How to get B when you have A ?
Intro
fn f(x: A) -> B {
Method Explanation
Identity Trivial case, B is exactly A.
Computation (Traits)
fn f(x: A) -> B {
x.into()
Bread and butter way to get B from A. Some traits provide canonical, user-computable type relations:
Casts
fn f(x: A) -> B {
x as B
Convert types with keyword as if conversion relatively obvious but might cause issues. [NOM](https://doc.rust-
lang.org/nightly/nomicon/casts.html)
A B Example Explanation
Ptr Ptr device_ptr as *const u8 If *A , *B are Sized .
Where Ptr , Integer , Number are just used for brevity and actually mean:
Coercions
fn f(x: A) -> B {
A B Explanation
&mut T &T Pointer weakening.
&mut T *mut T -
&T *const T -
*mut T *const T -
&T &U Deref, if impl Deref<Target=U> for T .
T U Unsizing, if impl CoerceUnsized<U> for T .2 🚧
T V Transitivity, if T coerces to U and U to V.
2
Does not quite work in example above as unsized can't be on stack; imagine f(x: &A) -> &B instead. Unsizing
works by default for:
[T; n] to [T]
T to dyn Trait if impl Trait for T {} .
Foo<..., T, ...> to Foo<..., U, ...> 🔗
under arcane [ ](https://doc.rust-
lang.org/nomicon/coercions.html) circumstances.
Subtyping🝖
fn f(x: A) -> B {
Box<&'a u8>
Box<&'static
u8>
🛑 Invalid, Box with transient may not be with forever.
Box<&'a mut u8> Box<&'a u8> 🛑 ⚡ Invalid, see table below, &mut u8 never was a &u8 .
Cell<&'static u8> Cell<&'a u8> 🛑 Invalid, Cell are never something else; invariant.
fn(&'static u8) fn(&'a u8) 🛑 If fn needs forever it may choke on transients; contravar.
fn(&'a u8) fn(&'static u8) But sth. that eats transients can be(!) sth. that eats forevers.
for<'r> fn(&'r u8) fn(&'a u8) Higher-ranked type for<'r> fn(&'r u8) is also fn(&'a u8).
In contrast, these are not 🛑 examples of subtyping:
A B Explanation
u16 u8 🛑 Obviously invalid; u16 should never automatically be u8 .
u8 u16 🛑 Invalid by design; types w. different data still never subtype even if they could.
&'a mut u8 &'a u8 🛑 Trojan horse, not subtyping; but coercion (still works, just not subtyping).
Variance🝖
fn f(x: A) -> B {
Construct1 'a T U
Invariant means even if A is subtype of B , neither T[A] nor T[B] will be subtype of the other.
1
Compounds like struct S<T> {} obtain variance through their used fields, usually becoming invariant if multiple variances are mixed.
💡 In other words, 'regular' types are never subtypes of each other (e.g., u8 is not subtype of u16 ),
and a
Box<u32> would never be sub- or supertype of anything.
However, generally a Box<A> , can be subtype of
Box<B> (via covariance) if A is a subtype
of B, which can only happen if A and B are 'sort of the same type
that only differed in lifetimes', e.g., A being &'static u32 and B being &'a u32 .
Coding Guides
Idiomatic Rust
If you are used to Java or C, consider these.
Idiom Code
Think in
y = if x { a } else { b };
Expressions
y = loop { break 5 };
names.iter().filter(|x| x.starts_with("A"))
Handle Absence
y = try_something()?;
with ?
get_option()?.run()?
Idiom Code
Use Strong Types enum E { Invalid, Valid { ... } } over ERROR_INVALID = -1
enum E { Visible, Hidden } over visible: bool
struct Charge(f32) over f32
Don't Panic Panics are not exceptions, they may abort() entire process!
Only raise panic! if impossible to handle error, better return Option or Result .
Split
Generic types S<T> can have a separate impl per T .
Implementations
Rust doesn't have OO, but with separate impl you can get specialization.
Unsafe Avoid unsafe {} , often safer, faster solution without it. Exception: FFI.
Implement Traits #[derive(Debug, Copy, ...)] and custom impl where needed.
Documentation Annotate your APIs with doc comments that can show up on docs.rs.
Async-Await 101
If you are familiar with async / await in C# or TypeScript, here are some things to keep in mind:
Basics
Construct Explanation
Anything declared async always returns an impl Future<Output=_> . [STD]
async
(https://doc.rust-lang.org/std/future/trait.Future.html)
async fn f() {} Function f returns an impl Future<Output=()> .
Function f returns an impl Future<Output=S> .
async fn f() -> S {}
Likewise, does not execute the { g() } block; produces state machine.
sm = async { g() };
2
The state machine always impl Future , possibly Send & co, depending on types used inside async .
3
State machine driven by worker thread invoking Future::poll() via runtime directly, or parent .await indirectly.
4
Rust doesn't come with runtime, need external crate instead, e.g., tokio. Also, more helpers in futures crate.
Execution Flow
At each x.await , state machine passes control to subordinate state machine x . At some point a low-level state
machine invoked via .await might not be ready. In that the case worker
thread returns all the way up to runtime
so it can drive another Future. Some time later the runtime:
// ^ ^ ^ Future<Output=X> ready -^
// or an external .await | This might resume on another thread (next best available),
// |
Caveats 🛑
With the execution flow in mind, some considerations when writing code inside an async construct:
Constructs 1 Explanation
sleep_or_block(); Definitely bad 🛑, never halt current thread, clogs executor.
set_TL(a); x.await; TL(); Definitely bad 🛑, await may return from other thread, thread local invalid.
s.no(); x.await; s.go(); Maybe bad 🛑, await will not return if Future dropped while waiting. 2
Rc::new(); x.await; rc(); Non- Send types prevent impl Future from being Send ; less compatible.
1
Here we assume s is any non-local that could temporarily be put into an invalid state;
TL is any thread local storage, and that the async {}
containing the code is written
without assuming executor specifics.
2
Since Drop is run in any case when Future is dropped, consider using drop guard that cleans up / fixes application state if it has to be left in
bad condition across .await points.
Closures in APIs
There is a subtrait relationship Fn : FnMut : FnOnce . That means a closure that
implements Fn [STD](https://doc.rust-
lang.org/std/ops/trait.Fn.html) also implements FnMut and FnOnce . Likewise a closure
that implements FnMut [STD](https://doc.rust-
lang.org/std/ops/trait.FnMut.html) also implements FnOnce . [STD](https://doc.rust-lang.org/std/ops/trait.FnOnce.html)
From the perspective of someone defining a closure:
That gives the following advantages and disadvantages:
Safe Code
Safe 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.
let y = x + x; // Safe Rust only guarantees the execution of this code is consistent with
// (X::add might be implemented badly) nor that y is printed (Y::fmt may panic).
Unsafe Code
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.
my_native_lib(x);
Undefined Behavior
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.
if maybe_true() {
let r: &u8 = unsafe { &*ptr::null() }; // Once this runs, ENTIRE app is undefined. Even if
Unsound Code
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.
} // everything else.
Responsible use of Unsafe 💬
Do not use unsafe unless you absolutely have to.
Follow the Nomicon, Unsafe Guidelines, always uphold all safety invariants, 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 🝖
Adversarial code is safe code that compiles but does not follow API expectations, and might interfere with your own (safety) guarantees.
impl Deref for S {} May randomly Deref , e.g., s.x != s.x , or panic.
impl Eq for S {} May cause s != s ; panic; must not use s in HashMap & co.
impl Hash for S {} May violate hashing rules; panic; must not use s in HashMap & co.
impl Ord for S {} May violate ordering rules; panic; must not use s in BTreeMap & co.
impl Index for S {} May randomly index, e.g. s[x] != s[x] , or panic.
impl Drop for S {} May run code or panic end of scope {} , during assignment s = new_s .
panic!() User code can panic any time, doing abort, or unwind.
catch_unwind(|| s.f(panicky)) Also, caller might force observation of broken state in s .
let ... = f(); Variable name affects order of Drop execution. 1 🛑
1 Notably, when you rename a variable from _x to _ you will also change the Drop behavior of whatever was assigned to that variable. A variable named _x will have
Drop::drop() executed at the end of scope, a variable named _ will have it executed immediately on assignment!
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 Stability
When updating an API, these changes can break client code. RFC Major changes ( 🔴) are definitely breaking, while minor changes (🟡) might
be breaking:
Crates
🔴 Making a crate that previously compiled for stable require nightly.
🟡 Altering use of Cargo features (e.g., adding or removing features).
Modules
🔴 Renaming / moving / removing any public items.
🟡 Adding new public items, as this might break code that does use your_crate::* .
Structs
🔴 Adding private field when all current fields public.
🔴 Adding public field when no private field exists.
🟡 Adding or removing private fields when at least one already exists (before and after the change).
🟡 Going from a tuple struct with all private fields (with at least one field) to a normal struct, or vice versa.
Enums
🔴 Adding new variants; can be mitigated with early #[non_exhaustive] REF
🔴 Any non-trivial change to item signatures, will affect either consumers or implementors.
🟡 Adding a defaulted item; might cause dispatch ambiguity with other existing trait.
🟡 Adding a defaulted type parameter.
Traits
🔴 Implementing any "fundamental" trait, as not implementing a fundamental trait already was a promise.
🟡 Implementing any non-fundamental trait; might also cause dispatch ambiguity.
Inherent Implementations
🟡 Adding any inherent items; might cause clients to prefer that over trait fn and produce compile error.
Signatures in Type Definitions
🔴 Tightening bounds (e.g., to <T> <T: Clone> ).
🟡 Loosening bounds.
🟡 Adding defaulted type parameters.
🟡 Generalizing to generics.
Signatures in Functions
🔴 Adding / removing arguments.
🟡 Introducing a new type parameter.
🟡 Generalizing to generics.
Behavioral Changes
🔴 / 🟡 Changing semantics might not cause compiler errors, but might make clients do wrong thing.
Ralf Biedert, 2022 – cheats.rs