You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Contains clickable links to The BookBK, Rust by ExampleEX, Std DocsSTD, NomiconNOM, ReferenceREF.
Clickable symbols
BKThe Book EXRust by Example STDStd Docs NOMNomicon REFReference RFC Official RFC documents 🔗 The internet ↑ On this page, above ↓ On this page, below
Other symbols
🗑️ Largely deprecated '18 Has minimum edition requirement 🚧 Requires Rust nightly (or is incomplete) 🛑 Intentionally wrong example or pitfall 🝖 Slightly esoteric, rarely used or advanced 🔥 Something with outstanding utility ? Is missing good link or explanation 💬Opinionated
Get installer from rustup.rs (highly recommended for any platform)
IDEs
First Steps
Modular Beginner Resources
In addition, have a look at the ususal suspects. BKEXSTD
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.
Global variableBKEXREF with 'static lifetime, single memory location.
const X: T = T();
Defines constantBKEXREF. Copied into a temporary when used.
let x: T;
Allocate T bytes on stack1 bound as x. Assignable once, not mutable.
let mut x: T;
Like let, but allow for mutabilityBKEX and mutable borrow.2
x = y;
Moves y to x, invalidating y if T is not Copy, STD and copying y otherwise.
1Bound variablesBKEXREF live on stack for synchronous code. In async {} code they become part async's state machine, may reside on heap. 2 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.
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.
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.
Rust supports most operators you would expect (+, *, %, =, ==, …), including overloading. STD Since they behave no differently in Rust we do not list them here.
Why moves, references and lifetimes are how they are.
Types & Moves
Application Memory S(1) Application Memory
Application memory in itself is just array of bytes.
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).
Programming languages such as Rust give developers tools to:
define what data goes into what segment,
express a desire for bitcode with specific properties to be produced,
protect themselves from errors while performing these operations.
Most tricky part is tied to how stack evolves, which is our focus.
1 While for each part of the heap someone (the allocator) needs to perform bookkeeping at runtime, the stack is trivially managable: take a few bytes more while you need them, they will be discarded once you leave. The (for performance reasons desired) simplicity of this appraoch, along with the fact that you can tell others about such transient locations (which in turn might want to access them long after you left), form the very essence of why lifetimes exist; and are the subject of the rest of this chapter.
Variables S(1) S(1) at 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 that the term variable has some linguistic ambiguity,2 it can mean:
the name of the location ("rename that variable"),
the location itself, 0x7 ("tell me the address of that variable"),
the value contained within, S(1) ("increment that variable").
Specifically towards the compiler t can mean location oft, here 0x7, and value withint, here S(1).
1 Compare above,↑ true for fully synchronous code, but async stack frame might placed it on heap via runtime.
2 It is the author's opinion💬 that this ambiguity related to variables (and lifetimes and scope later) are some of the biggest contributors to the confusion around learning the basics of lifetimes. Whenever you hear one of these terms ask yourself "what exactly is meant here?"
Move Semantics S(1) at 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.
Type Safety S(1) M { ... } ⛔ ac Type Safety
let c: S = M::new();
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, …).
As an excercise to the reader, any time you see a value of type A being assignable to a location of some type not-exactly-A you should ask yourself: through what mechanism is this possible?
Scope & Drop S(1)▼ C(2) S(2)▼ S(3) t Scope & Drop
{
let mut c = S(2);
c = S(3); // <- Drop called on `c` before assignment.
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
Stack Frame S(1) ax Function Boundaries
fn f(x: S) { ... }
let a = S(1); // <- We are here
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) axx 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.
Validity of Variables S(1) M { } axm Repurposing Memory
fn f(x: S) {
if once() { f(x) }
let m = M::new() // <- We are here (after recursion)
}
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.
References & Pointers
Reference Types ▼ S(1) 0x3 ar References as Pointers
let mut a = S(1);
let r = &mut a;
let d = r.clone(); // Valid to clone (or copy) from r-target.
*r = S(2); // Valid to set new S value to r-target.
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 withinr, 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).
▼ S(2) 0x3 M { x } ⛔ ⛔ ard References Guard Referents
let mut a = ...;
let r = &mut a;
let d = *r; // Invalid to move out value, `a` would be empty.
*r = M::new(); // invalid to store non S value, doesn't make sense.
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).
▼ C(2) 0x3 cp Raw Pointers
let p: *const S = questionable_origin();
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. ↓
Lifetime Basics
C(2) 0x3 "Lifetime" of Things
Every entity in a program has some time it is 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.
2Live lines might have been a more appropriate term ...
▼ S(0) S(1) S(2) 0xa abcr 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.
▼ S(0) S(3) S(2) 0x6 ⛔ abcr 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
{ // variables in a function body, but very same principle applies
let a = S(0); // to functions next page.
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.
}
}
}
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 assiging &b (0x6) to r is valid, but &a (0x3) would not, as only &b lives equal or longer than &c.
▼ S(0) S(2) 0x6 S(4) ⛔ abc Borrowed State
let mut b = S(0);
let r = &mut b;
b = S(4); // Will fail since b in borrowed state.
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 addess 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(0) S(1) S(2) ? 0x6 0xa abcrxy Function Parameters
fn f(x: &S, y:&S) -> &u8 { ... }
let b = S(1);
let c = S(2);
let r = f(&b, &c);
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(0) S(1) S(2) ? 0x6 0xa abcrxy Problem of 'Borrowed' Propagation
let b = S(1);
let c = S(2);
let r = f(&b, &c);
let a = b; // Are we allowed to do this?
let a = c; // Which one is really borrowed?
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(1) S(2) y + _ 0x6 0xa abcrxy Lifetimes Propagate Borrowed State
let r = f(&b, &c); // We know returned reference is c-based, which must stay locked,
// while b is free to move.
let a = b;
print_byte(r);
Liftime 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.
S(2) S(1) S(2) y + 1 0x6 0xa abcrxy Unlocking
let mut c = S(2);
let r = f(&c);
let s = r;
// <- Not here, s prolongs locking of c.
print_byte(s);
let a = c; // <- But here, no more use of r or s.
A variable location is unlocked again once the last use of any reference that may point to it ends.
If something works that "shouldn't work now that you think about it", it might be due to one of these.
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.
u8u16f32boolchar Primitive Types FileStringBuilder Composite Types Vec<T>Vec<T>Vec<T>&'a T&'a T&'a T&mut 'a T&mut 'a T&mut 'a T[T; n][T; n][T; n] Type Constructors Vec<T>Vec<T>f<T>() {}drop() {} Functions PIdbg! Other ⌾ Copy ⌾ Dereftype Tgt; ⌾ From<T> ⌾ From<T> ⌾ From<T> Traits Items defined in upstream crates. ⌾ Serialize ⌾ Transport ⌾ ShowHexDevice ⌾ From<u8> Foreign trait impl. for local type. String ⌾ Serialize Local trait impl. for foreign type. String ⌾ From<u8>🛑 Illegal, foreign trait for f. type. String ⌾ From<Port> Exception: Legal if used type local. Port ⌾ From<u8> ⌾ From<u16> Mult. impl. of trait with differing IN params. Container ⌾ DerefTgt = u8; ⌾ DerefTgt = f32;🛑 Illegal impl. of trait with differing OUT params. TTT ⌾ ShowHex Blanket impl. of trait for any type. Your crate.
A walk through the jungle of types, traits, and implementations that (might possibly) exist in your application.
Allowing users to bring their own types and avoid code duplication.
Types & Traits
u8StringDevice
Set of values with given semantics, layout, …
Type
Values
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
May be obvious but u8, &u8, &mut u8, 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 }
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_u8does 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'.
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:
// 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
Type Constructors — Vec<>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<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.
Vec<T>[T; 128]&T&mut TS<T>
Parameter for Vec<> often named T therefore Vec<T>.
T "variable name for type" for user to plug in something specfic, Vec<f32>, S<u8>, …
Type Constructor
Produces Family
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);
}
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>.
Type Constructor
Produces Family
[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 ⌾ MulPort ⌾ 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":
// Type can only be constructed for some `T` if that
// T is part of `Absolute` membership list.
struct Num<T> where T: Absolute {
...
}
Absolute Trait
u8
u16
...
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 + Yu8 ⌾ Absolute ⌾ Dim ⌾ Mulf32 ⌾ Absolute ⌾ MulcharCmplx ⌾ Absolute ⌾ Dim ⌾ Mul ⌾ DirName ⌾ TwoDCar ⌾ 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.
Implementing Families — impl<>
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 S<T>,
containing the methods ...
You can think of such impl<T> ... {} code as abstractly implementing a family of behaviors. 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) →
Serialize Trait
u8
Port
...
They can be neat way to give foreign types functionality in a modular way if they just implement another interface.
Advanced Concepts🝖
Trait Parameters — Trait<In> { type Out; }
Notice how some traits can be "attached" multiple times, but others just once?
Port ⌾ From<u8> ⌾ From<u16>Port ⌾ Dereftype 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:
impl From<u8> for u16 {}
impl From<u16> for u32 {}
impl Deref for Port { type O = u8; }
impl Deref for String { type O = str; }
Deref
Port
u8
String
str
...
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 relation of types named Complex,
with 3 inputs (Self is always one) and 2 outputs, and it holds (Self, I1, I2) => (O1, O2)
Complex
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.
Trait Authoring Considerations (Abstract)
👩🦰 ⌾ A<I> 🧔 Car 👩🦰 / 🧔 Car ⌾ A<I> 🎅 car.a(0_u8)car.a(0_f32)
👩🦰 ⌾ Btype O; 🧔 Car 👩🦰 / 🧔 Car ⌾ BT = 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).
trait A<I> { }
trait B { type O; }
// Implementor adds (X, u32) to A.
impl A<u32> for X { }
// Implementor adds family impl. (X, ...) to A, user can materialze.
impl<T> A<T> for Y { }
// Implementor must decide specific entry (X, O) added to B.
impl B for X { type O = u32; }
Santa may add more members by providing his own type for T.
For given set of inputs (here Self), implementor must pre-select O.
developers 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!
impl Audio<u8> for MP3 { type O = DigitalDevice; }
impl Audio<f32> for MP3 { type O = AnalogDevice; }
impl<T> Audio<T> for Ogg { type O = GenericDevice; }
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, developer should determine resulting output type.
S<T>→S<u8>S<char>S<str>
struct S<T> { ... }
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:
S<T>→S<u8>S<char>S<str>
struct S<T> where T: ?Sized { ... }
Generics and Lifetimes — <'a>S<'a>&'a f32&'a mut u8
Lifetimes act* like type parameters:
user must provide specific 'a to instantiate type (compiler will help within methods),
as Vec<f32> and Vec<u8> are different types, so are S<'p> and S<'q>,
meaning you can't just assign value of type S<'a> to variable expecting S<'b> (exception: "subtype" relationship for lifetimes, e.g. 'a outliving 'b).
S<'a>→S<'auto>S<'static>
'static is only nameable instance of the typespace lifetimes.
// `'a is free parameter here (user can pass any specific lifetime)
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
// the right value for 'a automatically.
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. 🔗
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?
char Any UTF-8 scalar. str ... UTF-8 ... unspecified times Rarely seen alone, but as &str instead.
Basics
Type
Description
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.
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 twochar (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.
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 toe2 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.
Basic types definable by users. Actual layoutREF is subject to representation; REF padding can be present.
T x T Sized ↓T: ?SizedT Maybe DST ↓[T; n]TTT ... n times Fixed array of n elements. [T] ... TTT ... unspecified times Slice type of unknown-many elements. Neither Sized (nor carries len information), and most
often lives behind reference as &[T]. ↓struct S;; Zero-Sized ↓(A, B, C)ABC or maybe BAC Unless a representation is forced
(e.g., via #[repr(C)]), type layout
unspecified. struct S { b: B, c: C }BC 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() without representation guarantees.
These sum types hold a value of one of their sub types:
enum E { A, B, C }TagA exclusive or TagB exclusive or TagC Safely holds A or B or C, also
called 'tagged union', though
compiler may omit tag. union { ... }A unsafe or B unsafe or C Can unsafely reinterpret
memory. Result might
be undefined.
References give safe access to other memory, raw pointers unsafe access. The respective mut types are identical.
&'a Tptr2/4/8meta2/4/8 | T Must target some valid t of T,
and any such target must exist for
at least 'a. *const Tptr2/4/8meta2/4/8 No guarantees.
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 Tptr2/4/8 | T No meta for
sized target.
(pointer is thin). &'a Tptr2/4/8len2/4/8 | T If T is a DST struct such as S { x: [u8] } meta field len is
length of dyn. sized content. &'a [T]ptr2/4/8len2/4/8 | ... TT ... Regular slice reference (i.e., the
reference type of a slice type [T]) ↑
often seen as &[T] if 'a elided. &'a strptr2/4/8len2/4/8 | ... UTF-8 ... String slice reference (i.e., the
reference type of string type str),
with meta len being byte length. &'a dyn Traitptr2/4/8ptr2/4/8 | T |
*Drop::drop(&mut T)
size
align
*Trait::f(&T, ...)
*Trait::g(&T, ...)
Meta points to vtable, where *Drop::drop(), *Trait::f(), ... are pointers to their respective impl for T.
Ad-hoc functions with an automatically managed data block capturingREF environment where closure was defined. For example:
move |x| x + y.f() + zYZ Anonymous closure type C1 |x| x + y.f() + zptr2/4/8ptr2/4/8 Anonymous closure type C2 | Y | Z
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.
Rust's standard library combines the above primitive types into useful types with special semantics, e.g.:
UnsafeCell<T>T Magic type allowing
aliased mutability. Cell<T>T Allows T's
to move in
and out. RefCell<T>borrowedT Also support dynamic
borrowing of T. Like Cell this
is Send, but not Sync. AtomicUsizeusize2/4/8 Other atomic similarly. Result<T, E>TagE or TagTOption<T>Tag or TagT Tag may be omitted for
certain T, e.g., NonNull.
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>ptr2/4/8meta2/4/8
| strng2/4/8weak2/4/8T
Share ownership of T in same thread. Needs nested Cell
or RefCellto allow mutation. Is neither Send nor Sync. Arc<T>ptr2/4/8meta2/4/8
| strng2/4/8weak2/4/8T
Same, but allow sharing between threads IF contained T itself is Send and Sync. Mutex<T> / RwLock<T>ptr2/4/8poison2/4/8T | lock Needs to be held in Arc to be shared between
threads, always Send and Sync. Consider using parking_lot instead (faster, no heap usage).
* An instance t where T: Send can be moved to another thread, a T: Sync means &t can be moved to another thread. 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.
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 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;
let bytes: &[u8] = my_os_str.as_bytes();
CString::new(bytes)?
3 The c_charmust 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.
Minimal examples for various entry points might look like:
Applications
// src/main.rs (default application entry point)
fn main() {
println!("Hello, world!");
}
Libraries
// src/lib.rs (default library entry point)
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
} // is not accessible from the outside of the crate.
Unit Tests
// src/my_module.rs (any file of your project)
fn f() -> u32 { 0 }
#[cfg(test)]
mod test {
use super::f; // Need to import items from parent module. Has
// access to non-public members.
#[test]
fn ff() {
assert_eq!(f(), 0);
}
}
Integration Tests
// tests/sample.rs (sample integration test)
#[test]
fn my_sample() {
assert_eq!(my_crate::f(), 123); // Integration tests (and benchmarks) 'depend' to the crate like
} // a 3rd party would. Hence, they only see public items.
Benchmarks
// benches/sample.rs (sample benchmark)
#![feature(test)] // #[bench] is still experimental
extern crate test; // Even in '18 this is needed ... for reasons.
// Normally you don't need this in '18 code.
use test::{black_box, Bencher};
#[bench]
fn my_algo(b: &mut Bencher) {
b.iter(|| black_box(my_crate::f())); // black_box prevents f from being optimized away.
}
Build Scripts
// build.rs (sample pre-build script)
fn main() {
// You need to rely on env. vars for target; #[cfg(...)] are for host.
let target_os = env::var("CARGO_CFG_TARGET_OS");
}
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! 🛑
Namespaces🝖
Rust has three kinds of namespaces:
Namespace Types
Namespace Functions
Namespace Macros
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();1
1 Counts in Types and in Functions.
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.
Commands and tools that are good to know.
A command like cargo build means you can either type cargo build or just cargo b.
These are optional rustup components. Install them with rustup component add [tool].
🔘 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 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=X
Special tokens embedded in source code used by tooling or preprocessing.
For 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.
There is a subtrait relationship Fn : FnMut : FnOnce. That means a closure that implements FnSTD also implements FnMut and FnOnce. Likewise a closure that implements FnMutSTD 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 leads to unsound. Unsound leads to undefined. Undefined leads to the dark side of the force.
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.
// `x` must always point to race-free, valid, aligned, initialized u8 memory.
unsafe fn unsafe_f(x: *mut u8) {
my_native_lib(x);
}
Undefined Behavior
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.
if should_be_true() {
let r: &u8 = unsafe { &*ptr::null() }; // Once this runs, ENTIRE app is undefined. Even if
} else { // line seemingly didn't do anything, app might now run
println!("the spanish inquisition"); // both paths, corrupt database, or anything else.
}
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.
fn unsound_ref<T>(x: &T) -> &u128 { // Signature looks safe to users. Happens to be
unsafe { mem::transmute(x) } // ok if invoked with an &u128, UB for practically
} // everything else.
Want this Rust cheat sheet as a PDF? Download the latest PDF here. Alternatively, generate it yourself via File > Print and then "Save as PDF" (works great in Chrome, has some issues in Firefox).
via Rust Language Cheat Sheet
April 26, 2021 at 10:34AM
The text was updated successfully, but these errors were encountered:
Rust Language Cheat Sheet
https://cheats.rs/
Rust Language Cheat Sheet 25.04.2021
Fira Code Ligatures (..=, =>
) Expand all the things? Night Mode 💡Language Constructs
Behind the Scenes
Data Layout
Standard Library
Tooling
Coding Guides
Misc
Hello, Rust!url
If you are new to Rust, or if you want to try the things below:
Hello WorldThings Rust does measurably really well
Points you might run into
unsafe
in) libraries can secretly break safety guarantees.1 Compare Rust Survey.
Download
IDEs
Modular Beginner Resources
In addition, have a look at the ususal suspects. BK EX STD
Data Structuresurl
Data types and memory locations defined via keywords.
struct S {}
struct S { x: T }
x
of typeT
.struct S
(T);
.0
of typeT
.struct S;
enum E {}
enum E { A, B
(), C {} }
A
, tuple-B
()
and struct-likeC{}
.enum E { A = 1 }
union U {}
static X: T = T();
'static
lifetime, single memory location.const X: T = T();
let x: T;
T
bytes on stack1 bound asx
. Assignable once, not mutable.let mut x: T;
let
, but allow for mutability BK EX and mutable borrow.2x = y;
y
tox
, invalidatingy
ifT
is notCopy
, STD and copyingy
otherwise.1Bound variables BK EX REF live on stack for synchronous code. In
async {}
code they become part async's state machine, may reside on heap.2 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 }
struct S {}
oruse
'edenum E::S {}
with fieldx
set toy
.S { x }
x
for fieldx
.S { ..s }
s
, esp. useful with Default.S { 0: x }
S
(x)
below, but set field.0
with struct syntax.S
(x)
struct S
(T)
oruse
'edenum E::S
()
with field.0
set tox
.S
S
is unitstruct S;
oruse
'edenum E::S
create value ofS
.E::C { x: y }
C
. Other methods above also work.()
(x)
(x,)
(S,)
[S]
[S; n]
n
holding elements of typeS
.[x; n]
n
copies ofx
. REF[x, y]
x
andy
.x[0]
usize
. Implementable with Index, IndexMut.x[..]
x[a..b]
,x[a..=b]
, ... c. below.a..b
1..3
means1, 2
...b
a..=b
1..=3
means1, 2, 3
...=b
..
s.x
x
not part of typeS
.s.0
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
&s
).&[S]
address
,length
).&str
address
,length
).&mut S
&mut [S]
,&mut dyn S
, …)&dyn T
address
,vtable
).&s
s
, like0x1234
).&mut s
*const S
*mut S
&raw const s
ptr:addr_of!()
STD 🚧🝖&raw mut s
ref s
let ref r = s;
let r = &s
.let S { ref mut x } = s;
let x = &mut s.x
), shorthand destructuring ↓ version.*r
r
to access what it points to.*r = s;
r
is a mutable reference, move or copys
to target memory.s = *r;
s
a copy of whateverr
references, if that isCopy
.s = *r;
*r
is notCopy
, as that would move and leave empty place.s = *my_box;
Box
that can also move out Box'ed content if it isn'tCopy
.'a
&'a S
s
; addr. existing'a
or longer.&'a mut S
struct S<'a> {}
S
will contain address with lifetime'a
. Creator ofS
decides'a
.trait T<'a> {}
S
whichimpl T for S
might contain address.fn f<'a>(t: &'a T)
'a
.'static
Functions & Behaviorurl
Define units of code and their abstractions.
trait T {}
trait T : R {}
T
is subtrait of supertrait REFR
. AnyS
mustimpl R
before it canimpl T
.impl S {}
S
, e.g., methods.impl T for S {}
T
for typeS
.impl !T for S {}
fn f() {}
impl
.fn f() -> S {}
fn f(&self) {}
impl S {}
.const fn f() {}
fn
usable at compile time, e.g.,const X: u32 = f(Y)
. '18async fn f() {}
f
return animpl
Future
. STDasync fn f() -> S {}
f
return animpl Future<Output=S>
.async { x }
{ x }
animpl Future<Output=X>
.fn() -> S
Fn() -> S
FnMut
,FnOnce
), implemented by closures, fn's …|| {}
|x| {}
x
.|x| x + x
move |x| x + y
return || true
unsafe
unsafe f() {}
unsafe {}
Control Flowurl
Control execution within a function.
while x {}
x
is true.loop {}
break
. Can yield value withbreak x
.for x in iter {}
if x {} else {}
'label: loop {}
break
break x
x
value of the loop expression (only in actualloop
).break 'label
'label
.break 'label x
x
the value of the enclosing loop marked with'label
.continue
continue 'label
x?
x
is Err or None, return and propagate. BK EX STD REFx.await
async
. Yield flow untilFuture
STD or Streamx
ready. REF '18return x
f()
f
(e.g., a function, closure, function pointer,Fn
, …).x.f()
f
takesself
,&self
, … as first argument.X::f(x)
x.f()
. Unlessimpl Copy for X {}
,f
can only be called once.X::f(&x)
x.f()
.X::f(&mut x)
x.f()
.S::f(&x)
x.f()
ifX
derefs toS
, i.e.,x.f()
finds methods ofS
.T::f(&x)
x.f()
ifX impl T
, i.e.,x.f()
finds methods ofT
if in scope.X::f()
X::new()
.<X as T>::f()
T::f()
implemented forX
.Organizing Codeurl
Segment projects into smaller units and minimize dependencies.
mod m {}
{}
. ↓mod m;
m.rs
orm/mod.rs
. ↓a::b
b
withina
(mod
,enum
, …).::b
b
relative to crate root. 🗑️crate::b
b
relative to crate root. '18self::b
b
relative to current module.super::b
b
relative to parent module.use a::b;
b
directly in this scope without requiringa
anymore.use a::{b, c};
b
andc
into scope.use a::b as x;
b
into scope but namex
, likeuse std::error::Error as E
.use a::b as _;
b
anonymously into scope, useful for traits with conflicting names.use a::*;
a
into scope.pub use a::b;
a::b
into scope and reexport from here.pub T
T
.pub(crate) T
pub(self) T
pub(super) T
pub(in a::b) T
a::b
.extern crate a;
use a::b
in '18.extern "C" {}
"C"
) from FFI. BK EX NOM REFextern "C" fn f() {}
"C"
) to FFI.Type Aliases and Castsurl
Short-hand names of types, and methods to convert one type to another.
type T = S;
S
.Self
fn new() -> Self
.self
fn f(self) {}
, same asfn f(self: Self) {}
.&self
f(self: &Self)
&mut self
f(self: &mut Self)
self: Box<Self>
my_box.f_of_self()
).S as T
S
as traitT
, e.g.,<S as T>::f()
.S as R
use
of symbol, importS
asR
, e.g.,use a::S as R
.x as u32
Macros & Attributesurl
Code generation constructs expanded before the actual compilation happens.
m!()
m!{}
,m![]
(depending on macro).#[attr]
#![attr]
Pattern Matchingurl
Constructs found in
match
orlet
expressions, or function parameters.match m {}
let S(x) = get();
let
also destructures EX similar to the table below.let S { x } = s;
x
will be bound to values.x
.let (_, b, _) = abc;
b
will be bound to valueabc.1
.let (a, ..) = abc;
let (.., a, b) = (1, 2);
a
is1
,b
is2
.let Some(x) = get();
if let
instead.if let Some(x) = get() {}
enum
variant), syntactic sugar. *while let Some(x) = get() {}
get()
, run{}
as long as pattern can be assigned.fn f(S { x }: S)
let
, herex
bound tos.x
off(s)
. 🝖* Desugars to
match get() { Some(x) => {}, _ => () }
.Pattern matching arms in
match
expressions. Left side of these arms can also be found inlet
expressions.Generics & Constraintsurl
Generics combine with type constructors, traits and functions to give your users more flexibility.
S<T>
T
is placeholder name here).S<T: R>
R
must be actual trait).T: R, P: S
T
and one forP
).T: R, S
R + S
below.T: R + S
T
must fulfillR
andS
.T: R + 'a
T
must fulfillR
, ifT
has lifetimes, must outlive'a
.T: ?Sized
Sized
. ?T: 'a
'a
.T: 'static
t
will 🛑 live'static
, only that it could.'b: 'a
'b
must live at least as long as (i.e., outlive)'a
bound.S<const N: usize>
S
can provide constant valueN
. 🚧S<10>
S<{5+5}>
S<T> where T: R
S<T: R>
but more pleasant to read for longer bounds.S<T> where u8: R<T>
S<T = R>
S<'_>
S<_>
let x: Vec<_> = iter.collect()
S::<T>
f::<u32>()
.trait T<X> {}
X
. Can have multipleimpl T for S
(one perX
).trait T { type X; }
X
. Only oneimpl T for S
possible.type X = R;
impl T for S { type X = R; }
.impl<T> S<T> {}
T
inS<T>
, hereT
type parameter.impl S<T> {}
S<T>
, hereT
specific type (e.g.,S<u32>
).fn f() -> impl T
S
thatimpl T
.fn f(x: &impl T)
fn f<S:T>(x: &S)
.fn f(x: &dyn T)
f
will not be monomorphized.fn f() where Self: R;
trait T {}
, makef
accessible only on types known to alsoimpl R
.fn f() where Self: R {}
for<'a>
trait T: for<'a> R<'a> {}
S
thatimpl T
would also have to fulfillR
for any lifetime.Strings & Charsurl
Rust has several ways to create textual values.
"..."
\n
as line break0xA
, …r"..."
\n
, …r#"..."#
"
. Number of#
can vary.b"..."
[u8]
, not a string.br"..."
,br#"..."#
[u8]
, combination of the above.'🦀'
b'x'
1 Supports multiple lines out of the box. Just keep in mind
Debug
↓ (e.g.,dbg!(x)
andprintln!("{:?}", x)
) might render them as\n
, whileDisplay
↓ (e.g.,println!("{}", x)
) renders them proper.Documentationurl
Debuggers hate him. Avoid bugs with this one weird trick.
///
//!
//
/*...*/
/**...*/
/*!...*/
Tooling directives ↓ outlines what you can do inside doc comments.
Miscellaneousurl
These sigils did not fit any other category but are good to know nonetheless.
!
_
|x, _| {}
.let _ = x;
x
or preserve scope!_x
1_234_567
1_u8
i8
,u16
, …).0xBEEF
,0o777
,0b1001
0x
), octal (0o
) and binary (0b
) integer literals.r#foo
x;
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.Behind the Scenesurl
Arcane knowledge that may do terrible things to your mind, highly recommended.
The Abstract Machineurl
Like
C
andC++
, Rust is based on an abstract machine.Rust
→
CPU🛑 Less correctish. Rust
→
Abstract Machine→
CPUMore correctish.
The abstract machine
* Things people may incorrectly assume they should get away with if Rust targeted CPU directly, and more correct counterparts.
Memory & Lifetimesurl
Why moves, references and lifetimes are how they are.
Types & MovesBox<T>
),str
part of&str
),1 While for each part of the heap someone (the allocator) needs to perform bookkeeping at runtime, the stack is trivially managable: take a few bytes more while you need them, they will be discarded once you leave. The (for performance reasons desired) simplicity of this appraoch, along with the fact that you can tell others about such transient locations (which in turn might want to access them long after you left), 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
Variablest
of typeS
and the valueS(1)
stored inside.let
that location lives on stack. 10x7
("tell me the address of that variable"),S(1)
("increment that variable").t
can mean location oft
, here0x7
, and value withint
, hereS(1)
.1 Compare above,↑ true for fully synchronous code, but
Move Semantics S(1)async
stack frame might placed it on heap via runtime.a
t
Movest
to location ofa
, or copy it, ifS
isCopy
.t
is invalid and cannot be read anymore.t
(viaunsafe
) they might still look like validS
, but any attempt to use them as validS
is undefined behavior. ↓Copy
types explicitly here. They change the rules a bit, but not much:a
c
Type SafetyM::new()
cannot be converted to form of typeS
.t
Scope & Drop{}
-block it was defined inDrop::drop()
is called on the location of that value.drop()
is called ona
, twice onc
, but not ont
.Copy
values get dropped most of the time; exceptions includemem::forget()
,Rc
cycles,abort()
.a
x
Function Boundariesf
is invoked value ina
is moved to 'agreed upon' location on stack, and duringf
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 Functionsa
x
m
Repurposing Memoryf
produced secondx
, which after recursion was partially reused form
.a
r
References as Pointersa
r
d
Access to Non-Owned Memory&S
) and also write to (&mut S
) location they point to.*r
means to neither use the location of or value withinr
, but the locationr
points to.d
is created from*r
, andS(2)
written to*r
.Clone::clone(&T)
expects a reference itself, which is why we can user
, not*r
.*r = ...
old value in location also dropped (not shown above).a
r
d
References Guard Referents&mut T
must provide same guarantees as variables, and some more as they can't dissolve the target:c
p
Raw Pointersunsafe
, and treating an invalid*p
as if it were valid is undefined behavior. ↓r: &'a S
, arer
itself (well, it needs to exist shorter, that's it).&'static S
means address must be valid during all lines of code.a
b
c
r
Meaning ofr: &'c S
r: &'c S
from somewhere it means:r
holds an address of someS
,r
points to must and will exist for at least'c
,r
itself cannot live longer than'c
.a
b
c
r
Typelikeness of Lifetimesmut r: &mut 'c S
from somewhere.'c
part, like a type, also guards what is allowed intor
.&b
(0x6
) tor
is valid, but&a
(0x3
) would not, as only&b
lives equal or longer than&c
.a
b
c
Borrowed State&b
or&mut b
the variable is marked as borrowed.b
.&b
or&mut b
stops being used (in terms of LOC) original bindingb
works again.a
b
c
r
x
y
Function Parametersa
b
c
r
x
y
Problem of 'Borrowed' Propagationf
can return only one address, not in all casesb
andc
need to stay locked.a
b
c
r
x
y
Lifetimes Propagate Borrowed State'c
above, solve that problem.'c
are assigned.'b
,'c
are transparently picked by the compiler at call site, based on the borrowed variables the developer gave.b
orc
, but only a minimal subset of their scope called lifetime, that is, a minmal set of LOC based on how longb
andc
need to be borrowed to perform this call and use the obtained result.f
had'c: 'b
instead, we still couldn't distinguish and both needed to stay locked.a
b
c
r
x
y
UnlockingLanguage Sugarurl
If something works that "shouldn't work now that you think about it", it might be due to one of these.
Types, Traits, Genericsurl
The building blocks of compile-time safety.
u8
u16
f32
bool
char
Primitive TypesFile
String
Builder
Composite TypesVec<T>
Vec<T>
Vec<T>
&'a T
&'a T
&'a T
&mut 'a T
&mut 'a T
&mut 'a T
[T; n]
[T; n]
[T; n]
Type ConstructorsVec<T>
Vec<T>
f<T>() {}
drop() {}
FunctionsPI
dbg!
Other ⌾Copy
⌾Deref
type Tgt;
⌾From<T>
⌾From<T>
⌾From<T>
Traits Items defined in upstream crates. ⌾Serialize
⌾Transport
⌾ShowHex
Device
⌾From<u8>
Foreign trait impl. for local type.String
⌾Serialize
Local trait impl. for foreign type.String
⌾From<u8>
🛑 Illegal, foreign trait for f. type.String
⌾From<Port>
Exception: Legal if used type local.Port
⌾From<u8>
⌾From<u16>
Mult. impl. of trait with differing IN params.Container
⌾Deref
Tgt = u8;
⌾Deref
Tgt = f32;
🛑 Illegal impl. of trait with differing OUT params.T
T
T
⌾ShowHex
Blanket impl. of trait for any type. Your crate.A walk through the jungle of types, traits, and implementations that (might possibly) exist in your application.
Type Paraphernaliaurl
Allowing users to bring their own types and avoid code duplication.
Types & Traitsu8
String
Device
u8
{ 0u8, 1u8, ..., 255u8 }
char
{ 'a', 'b', ... '🦀' }
struct S(u8, char)
{ (0u8, 'a'), ... (255u8, '🦀') }
Sample types and sample values.
Type Equivalence and Conversionsu8
&u8
&mut u8
[u8; 1]
String
u8
,&u8
,&mut u8
, entirely different from each othert: T
only accepts values from exactlyT
, e.g.,f(0_u8)
can't be called withf(&0_u8)
,f(&mut my_u8)
can't be called withf(&my_u8)
,f(0_u8)
can't be called withf(0_i8)
.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.
0_i8 as u8
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 ofu16
and0_u8
being the same as0_u16
) where such a conversion would be purely a compile time check. Rust does not use subtyping for regular types (and0_u8
does differ from0_u16
) but sort-of for lifetimes. 🔗2 Safety here is not just physical concept (e.g.,
Implementations —&u8
can't be coerced to&u128
), but also whether 'history has shown that such a conversion would lead to programming errors'.impl S { }
u8
impl { ... }
String
impl { ... }
Port
impl { ... }
impl Port {}
, behavior related to type:Port::new(80)
port.close()
Copy
⌾Clone
⌾Sized
⌾ShowHex
u8
String
...
char
Port
...
Traits as membership tables,
Self
refers to the type included.⌾
Copy
Copy
is example marker trait, meaning memory may be copied bitwise.Sized
Sized
provided by compiler for types with known size; either this is, or isn'timpl T for S { }
impl A for B
add typeB
to the trait memebership list:u8
impl { ... }
⌾Sized
⌾Clone
⌾Copy
Device
impl { ... }
⌾Transport
Port
impl { ... }
⌾Sized
⌾Clone
⌾ShowHex
👩🦰 ⌾Eat
🧔Venison
⌾Eat
🎅venison.eat()
Interfaces
Eat
.Venison
, he must decide ifVenison
implementsEat
or not.Venison
, Santa can make use of behavior provided byEat
:Eat
🧔Venison
👩🦰 / 🧔Venison
+
⌾Eat
🎅venison.eat()
Traits
Eat
.Venison
and decides not to implementEat
(he might not even know aboutEat
).Eat
toVenison
would be a really good idea.Venison
Santa must importEat
separately:* To prevent two persons from implementing
Eat
differently Rust limits that choice to either Alice or Bob; that is, animpl Eat for Venison
may only happen in the crate ofVenison
or in the crate ofEat
. For details see coherence. ?Vec<>
Vec<u8>
Vec<char>
Vec<u8>
is type "vector of bytes";Vec<char>
is type "vector of chars", but what isVec<>
?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"Vec<UserType>
become real type itself.Vec<T>
[T; 128]
&T
&mut T
S<T>
Vec<>
often namedT
thereforeVec<T>
.T
"variable name for type" for user to plug in something specfic,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.
Const Generics —[T; N]
andS<const N: usize>
[T; n]
S<const N>
[T; n]
constructs array type holdingT
typen
times.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.
Bounds (Simple) —where T: X
🧔Num<T>
→
🎅Num<u8>
Num<f32>
Num<Cmplx>
u8
⌾Absolute
⌾Dim
⌾Mul
Port
⌾Clone
⌾ShowHex
T
can be any type, how can we reason about (write code) for such aNum<T>
?u8
u16
...
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
+ X
addition to a bound merely cuts down space of eligible types.impl<>
When we write:
It can be read as:
T
(theimpl <T>
part),Absolute + Dim + Mul
traits,S<T>
,You can think of such
impl<T> ... {}
code as abstractly implementing a family of behaviors. Most notably, they allow 3rd parties to transparently materialize implementations similarly to how type constructors materialize types:impl<T> X for T { ... }
Can also write "family implementations" so they apply trait to many types:
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.
Trait<In> { type Out; }
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?
trait From<I> {}
trait Deref { type O; }
Self
?I
(for input) andO
(for output) are just more columns to that trait's list:Port
u8
String
str
...
Input and output parameters.
Now here's the twist,
O
parameters must be uniquely determined by input parametersI
,X Y
would represent a function),Self
counts as an input.A more complex example:
Complex
,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
Trait Authoring Considerations (Abstract) 👩🦰 ⌾(NiceMonster, u16, String)
hasalready uniquely determined the outputs.
A<I>
🧔Car
👩🦰 / 🧔Car
⌾A<I>
🎅car.a(0_u8)
car.a(0_f32)
👩🦰 ⌾
B
type O;
🧔Car
👩🦰 / 🧔Car
⌾B
T = u8;
🎅car.b(0_u8)
car.b(0_f32)
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
Trait Authoring Considerations (Example) ⌾Self
), implementor must pre-selectO
.Audio
→
⌾Audio<I>
⌾Audio
type O;
⌾Audio<I>
type O;
Choice of parameters goes along with purpose trait has to fill:
No Additional Parameters
👩🦰 ⌾
Audio
→
🧔MP3
⌾Audio
Ogg
⌾Audio
Trait author assumes:
Input Parameters
👩🦰 ⌾
Audio<I>
→
🧔MP3
⌾Audio<f32>
⌾Audio<u8>
⌾Audio<Mix>
Ogg
⌾Audio<T>
... whereT
isHeadsetCtrl
.Trait author assumes:
Self
type,I
-types ability should be possible.Output Parameters
👩🦰 ⌾
Audio
type O;
→
🧔MP3
⌾Audio
O = f32;
Ogg
⌾Audio
O = Mixer;
Trait author assumes:
Self
type (but in only one way),Self
.Multiple In- and Output Parameters
👩🦰 ⌾
Audio<I>
type O;
→
🧔MP3
⌾Audio<u8>
O = DD;
⌾Audio<f32>
O = AD;
Ogg
⌾Audio<T>
O = GD;
Like examples above, in particular trait author assumes:
I
-types ability should be possible,S<T>
→
S<u8>
S<char>
S<str>
T
can be any concrete type.T: Sized
, soS<str>
is not possible out of box.T : ?Sized
to opt-out of that bound:S<T>
→
S<u8>
S<char>
S<str>
<'a>
S<'a>
&'a f32
&'a mut u8
'a
to instantiate type (compiler will help within methods),Vec<f32>
andVec<u8>
are different types, so areS<'p>
andS<'q>
,S<'a>
to variable expectingS<'b>
(exception: "subtype" relationship for lifetimes, e.g.'a
outliving'b
).S<'a>
→
S<'auto>
S<'static>
'static
is only nameable instance of the typespace lifetimes.* There are subtle differences, for example you can create an explicit instance
0
of a typeu32
, 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.
Data Layouturl
Memory representations of common data types.
Basic Typesurl
Essential types built into the core of the language.
Numeric Types REFurl
u8
,i8
u16
,i16
u32
,i32
u64
,i64
u128
,i128
f32
f64
usize
,isize
Same asptr
on platform.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
u16
,u32
, oru64
.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
i16
,i32
, ori64
.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
i16
,i32
, ori64
.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
F
Explanation:
Similarly, for
f64
types this would look like:Textual Types REFurl
char
Any UTF-8 scalar.str
...U
T
F
-
8
... unspecified times Rarely seen alone, but as&str
instead.char
str
u8
-array of unknown length guaranteed to hold UTF-8 encoded code points.let c = 'a';
char
(unicode scalar) can coincide with your intuition of character.let c = '❤';
let c = '❤️';
char
(see Encoding) and can't 🛑 be held byc
.1c = 0xffff_ffff;
let s = "a";
str
is usually never held directly, but as&str
, likes
here.let s = "❤❤️";
let s = "I ❤ Rust";
let t = "I ❤️ Rust";
s.as_bytes()
49
20
e2 9d a4
20 52 75 73 74
3s.chars()
149 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
4t.chars()
149 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 thechar
, but got UTF-8 encoded to e2 9d a4 in thestr
.4 Also observe how the emoji Red Heart
❤️
, is a combination of❤
and the U+FE0F Variation Selector, thust
has a higher char count thans
.Custom Typesurl
Basic types definable by users. Actual layout REF is subject to representation; REF padding can be present.
T
xT
Sized ↓T: ?Sized
T
Maybe DST ↓[T; n]
T
T
T
... n times Fixed array ofn
elements.[T]
...T
T
T
... unspecified times Slice type of unknown-many elements. NeitherSized
(nor carrieslen
information), and mostoften lives behind reference as
&[T]
. ↓struct S;
;
Zero-Sized ↓(A, B, C)
A
B
C
or maybeB
A
C
Unless a representation is forced(e.g., via
#[repr(C)]
), type layoutunspecified.
struct S { b: B, c: C }
B
C
or maybeC
↦
B
Compiler may also add padding.These sum types hold a value of one of their sub types:
enum E { A, B, C }
Tag
A
exclusive orTag
B
exclusive orTag
C
Safely holds A or B or C, alsocalled 'tagged union', though
compiler may omit tag.
union { ... }
A
unsafe orB
unsafe orC
Can unsafely reinterpretmemory. Result might
be undefined.
References & Pointersurl
References give safe access to other memory, raw pointers
unsafe
access. The respectivemut
types are identical.&'a T
ptr
2/4/8meta
2/4/8 |T
Must target some validt
ofT
,and any such target must exist for
at least
'a
.*const T
ptr
2/4/8meta
2/4/8 No guarantees.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
ptr
2/4/8 |T
No meta forsized target.
(pointer is thin).
&'a T
ptr
2/4/8len
2/4/8 |T
IfT
is a DSTstruct
such asS { x: [u8] }
meta fieldlen
islength of dyn. sized content.
&'a [T]
ptr
2/4/8len
2/4/8 | ...T
T
... Regular slice reference (i.e., thereference type of a slice type
[T]
) ↑often seen as
&[T]
if'a
elided.&'a str
ptr
2/4/8len
2/4/8 | ...U
T
F
-
8
... String slice reference (i.e., thereference type of string type
str
),with meta
len
being byte length.&'a dyn Trait
ptr
2/4/8ptr
2/4/8 |T
|*Drop::drop(&mut T)
size
align
*Trait::f(&T, ...)
*Trait::g(&T, ...)
*Drop::drop()
,*Trait::f()
, ... are pointers to their respectiveimpl
forT
.Closuresurl
Ad-hoc functions with an automatically managed data block capturing REF environment where closure was defined. For example:
move |x| x + y.f() + z
Y
Z
Anonymous closure type C1|x| x + y.f() + z
ptr
2/4/8ptr
2/4/8 Anonymous closure type C2 |Y
|Z
Standard Library Typesurl
Rust's standard library combines the above primitive types into useful types with special semantics, e.g.:
UnsafeCell<T>
T
Magic type allowingaliased mutability.
Cell<T>
T
AllowsT
'sto move in
and out.
RefCell<T>
borrowed
T
Also support dynamicborrowing of
T
. LikeCell
thisis
Send
, but notSync
.AtomicUsize
usize
2/4/8 Other atomic similarly.Result<T, E>
Tag
E
orTag
T
Option<T>
Tag
orTag
T
Tag may be omitted forcertain T, e.g.,
NonNull
.General Purpose Heap Storageurl
Box<T>
ptr
2/4/8meta
2/4/8 |T
For someT
stack proxy may carrymeta↑ (e.g.,
Box[T]>
).Vec<T>
ptr
2/4/8capacity
2/4/8len
2/4/8 |
← capacity →T
T
... lenOwned Stringsurl
String
ptr
2/4/8capacity
2/4/8len
2/4/8 |
← capacity → Observe howU
T
F
-
8
... lenString
differs from&str
and&[char]
.CString
ptr
2/4/8len
2/4/8 |
Nul-terminated but w/o nul in middle.A
B
C
... len ...␀
OsString
? Platform Defined |
Encapsulates how operating system?
?
/?
?
represents strings (e.g., UTF-16 on
Windows).
PathBuf
?OsString
|
Encapsulates how operating system?
?
/?
?
represents paths.
Shared Ownershipurl
If the type does not contain a
Cell
forT
, these are often combined with one of theCell
types above to allow shared de-facto mutability.Rc<T>
ptr
2/4/8meta
2/4/8|
Share ownership ofstrng
2/4/8weak
2/4/8T
T
in same thread. Needs nestedCell
or
RefCell
to allow mutation. Is neitherSend
norSync
.Arc<T>
ptr
2/4/8meta
2/4/8|
Same, but allow sharing between threads IF containedstrng
2/4/8weak
2/4/8T
T
itself isSend
andSync
.Mutex<T>
/RwLock<T>
ptr
2/4/8poison
2/4/8T
|lock
Needs to be held inArc
to be shared betweenthreads, always
Send
andSync
. Consider usingparking_lot instead (faster, no heap usage).
Standard Libraryurl
One-Linersurl
Snippets that are common, but still easy to forget. See Rust Cookbook 🔗 for more.
Thread Safetyurl
Send
*!Send
Sync
*Mutex<T>
,Arc<T>
1,2MutexGuard<T>
1,RwLockReadGuard<T>
1!Sync
Cell<T>
2,RefCell<T>
2Rc<T>
,&dyn Trait
,*const T
3,*mut T
3* An instance
t
whereT: Send
can be moved to another thread, aT: Sync
means&t
can be moved to another thread.1 If
T
isSync
.2 If
T
isSend
.3 If you need to send a raw pointer, create newtype
struct Ptr(*const u8)
andunsafe impl Send for Ptr {}
. Just ensure you may send it.(Dynamically / Zero) Sized Typesurl
MostTypes
⌾Sized
Normal types.vs.
Z
⌾Sized
Zero sized.vs.
str
⌾Sized
Dynamically sized.[u8]
⌾Sized
dyn Trait
⌾Sized
...
⌾Sized
T
isSized
STD if at compile time it is known how many bytes it occupies,u8
and&[u8]
are,[u8]
isn't.Sized
meansimpl Sized for T {}
holds. Happens automatically and cannot be user impl'ed.Sized
are called dynamically sized types BK NOM REF (DSTs), sometimes unsized.Iteratorsurl
Collection<T>
⌾IntoIter
Item = T;
To = IntoIter<T>
Iterate overT
.IntoIter<T>
⌾Iterator
Item = T;
&Collection<T>
⌾IntoIter
Item = &T;
To = Iter<T>
Iterate over&T
.Iter<T>
⌾Iterator
Item = &T;
&mut Collectn<T>
⌾IntoIter
Item = &mut T;
To = IterMut<T>
Iterate over&mut T
.IterMut<T>
⌾Iterator
Item = &mut T;
String Conversionsurl
If you want a string of type …
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 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 aString
).2 Only on some platforms
std::os::<your_os>::ffi::OsStrExt
exists with helper methods to get a raw&[u8]
representation of the underlyingOsStr
. Use the rest of the table to go from there, e.g.:3 The
c_char
must have come from a previousCString
. If it comes from FFI see&CStr
instead.4 No known shorthand as
x
will lack terminating0x0
. Best way to probably go viaCString
.5 Must ensure vector actually ends with
0x0
.String Outputurl
How to convert types into a
APIs Printable Types FormattingString
, or output them.Each argument designator in format macro is either empty
{}
,{argument}
, or follows a basic syntax: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:
lib.rs
).Actual module definitions work as follows:
mod m {}
defines module in-file, whilemod m;
will readm.rs
orm/mod.rs
..rs
based on nesting, e.g.,mod a { mod b { mod c; }}}
is eithera/b/c.rs
ora/b/c/mod.rs
.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;
1struct X();
11 Counts in Types and in Functions.
enum X {}
andfn X() {}
can coexiststruct X;
andconst X
cannot coexistuse my_mod::X;
all items calledX
will be imported.Commands and tools that are good to know.
A command like
cargo build
means you can either typecargo build
or justcargo b
.These are optional
rustup
components. Install them withrustup 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 X
.🔘 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:or
🔘 Set environment variables (optional, wait until compiler complains before setting):
Whether you set them depends on how compiler complains, not necessarily all are needed.
✔️ Compile with
cargo build --target=X
Special tokens embedded in source code used by tooling or preprocessing.
Macros Documentation#![globals]
#[code]
#[quality]
#[macros]
#[cfg]
build.rs
For 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.Coding Guidesurl
Idiomatic Rusturl
If you are used to programming Java or C, consider these.
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 implementsFn
STD also implementsFnMut
andFnOnce
. Likewise a closure that implementsFnMut
STD also implementsFnOnce
. STDFrom a call site perspective that means:
Notice how asking for a
Fn
closure as a function is most restrictive for the caller; but having aFn
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.
Unsafe CodeUnsafe Code
unsafe
has special permissions, e.g., to deref raw pointers, or invoke otherunsafe
functions.unsafe
code is not bad, but dangerous, and needed for FFI or exotic data structures.Undefined Behavior (UB)
unsafe
code implies special promises to the compiler (it wouldn't need beunsafe
otherwise).Unsound Code
unsafe
code that may invoke UB on its own accord by violating above-mentioned promises.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
These are other great guides and tables.
All major Rust books developed by the community.
For more inofficial books see Little Book of Rust Books.
Comprehensive lookup tables for common components.
Online services which provide information or tooling.
Printing & PDFurl
via Rust Language Cheat Sheet
April 26, 2021 at 10:34AM
The text was updated successfully, but these errors were encountered: