Rust语言在2006年作为 Mozilla 员工 Graydon Hoare 的私人项目出现,而 Mozilla 于 2009 年开始赞助这个项目。第一个有版本号的 Rust 编译器于2012 年 1 月发布。Rust 1.0 是第一个稳定版本,于 2015年5月15日发布。
“Rust”最初是Mozilla公司的 Graydon Hoare的私人项目。2009年Mozilla开始赞助此项目,并有若干 Mozilla 员工参与 Rust 语言的设计和研发。2013年8月,Graydon Hoare卸任 Rust 技术负责人职位,由Brian Anderson接任。 2015年5月15日,Rust 1.0版本正式发布。2017年9月,Brian Anderson离开Mozilla,项目核心团队由 Aaron Turon和 Niko Matsakis接管共同领导,另外,Aaron Turon是 Mozilla的Rust团队的负责人。
创建这个新语言的目的是为了解决一个顽疾:软件的演进速度大大低于硬件的演进,软件在语言级别上无法真正利用多核计算带来的性能提升。Rust是针对多核体系提出的语言,并且吸收一些其他动态语言的重要特性,比如不需要管理内存,比如不会出现Null指针等等。
资源下载
BK 书籍.
EX Rust例子.
STD 官方文档.
NOM Nomicon.
REF 参考资料.
RFC 官方RFC 文档.
↑ 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.
..=, =>
) 展开所有? 暗色模式 💡
语言构成
幕后
数据类型
杂项
标准库
工具
类型
编码风格指导
如果你的第一次接触Rust,或你想试一下下面的代码:
你也许会遇到下面情况:
unsafe
特征)程序库可能导致安全性缺失。1 对比Rust Survey.
下载
集成开发环境(IDE)
初学者资料
建议 💬 — 如果你是第一次听说Rust,之前从未使用过,建议你还是看看上面提供的资料,因为下面的内容介绍的会很简练。
数据类型,内存寻址定义关键字
Example | Explanation |
---|---|
struct S {} | Define a struct BK EX STD REF with named fields. |
struct S { x: T } | Define struct with named field x of type T . |
struct S (T); | Define "tupled" struct with numbered field .0 of type T . |
struct S; | Define zero sized NOM unit struct. Occupies no space, optimized away. |
enum E {} | Define an enum, BK EX REF c. algebraic data types, tagged unions. |
enum E { A, B (), C {} } | Define variants of enum; can be unit- A , tuple- B () and struct-like C{} . |
enum E { A = 1 } | If variants are only unit-like, allow discriminant values, e.g., for FFI. |
union U {} | Unsafe C-like union REF for FFI compatibility. 🝖 |
static X: T = T(); | Global variable BK EX REF with 'static lifetime, single memory location. |
const X: T = T(); | Defines constant, BK EX REF 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 mutability BK EX and mutable borrow.2 |
x = y; | Moves y to x , invalidating y if T is not Copy , STD and copying y otherwise. |
1 Bound variables BK EX REF live on stack for synchronous code. In async {}
they become part of async's state machine, may reside on heap.
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. |
() | Empty tuple, both literal and type, aka unit. STD |
(x) | Parenthesized expression. |
(x,) | Single-element tuple expression. EX STD REF |
(S,) | Single-element tuple type. |
[S] | Array type of unspecified length, i.e., slice. EX STD REF Can't live on stack. * |
[S; n] | Array type EX STD of fixed length n holding elements of type S . |
[x; n] | Array instance with n copies of x . REF |
[x, y] | Array instance with given elements x and y . |
x[0] | Collection indexing, here w. usize . Implementable with Index, IndexMut. |
x[..] | Same, via range (here full range), also x[a..b] , x[a..=b] , ... c. below. |
a..b | Right-exclusive range STD REF creation, e.g., 1..3 means 1, 2 . |
..b | Right-exclusive range to STD without starting point. |
a..=b | Inclusive range, STD 1..=3 means 1, 2, 3 . |
..=b | Inclusive range from STD without starting point. |
.. | Full range, STD usually means the whole collection. |
s.x | Named field access, REF might try to Deref if x not part of type S . |
s.0 | Numbered field access, used for tuple types S (T) . |
* For now,RFC pending completion of tracking issue.
Granting access to un-owned memory. Also see section on Generics & Constraints.
Example | Explanation |
---|---|
&S | Shared reference BK STD NOM REF (space for holding any &s ). |
&[S] | Special slice reference that contains (address , length ). |
&str | Special string slice reference that contains (address , length ). |
&mut S | Exclusive reference to allow mutability (also &mut [S] , &mut dyn S , …). |
&dyn T | Special trait object BK reference that contains (address , vtable ). |
&s | Shared borrow BK EX STD (e.g., address, len, vtable, … of this s , like 0x1234 ). |
&mut s | Exclusive borrow that allows mutability. EX |
*const S | Immutable raw pointer type BK STD REF w/o memory safety. |
*mut S | Mutable raw pointer type w/o memory safety. |
&raw const s | Create raw pointer w/o going through reference; c. ptr:addr_of!() STD 🚧 🝖 |
&raw mut s | Same, but mutable. 🚧 Raw ptrs. are needed for unaligned, packed fields. 🝖 |
ref s | Bind by reference, EX makes binding reference type. 🗑️ |
let ref r = s; | Equivalent to let r = &s . |
let S { ref mut x } = s; | Mutable ref binding (let x = &mut s.x ), shorthand destructuring ↓ version. |
*r | Dereference BK STD NOM a reference r to access what it points to. |
*r = s; | If r is a mutable reference, move or copy s to target memory. |
s = *r; | Make s a copy of whatever r references, if that is Copy . |
s = *r; | Won't work 🛑 if *r is not Copy , as that would move and leave empty place. |
s = *my_box; | Special case🔗 for Box that can also move out Box'ed content if it isn't Copy . |
'a | A lifetime parameter, BK EX NOM REF duration of a flow in static analysis. |
&'a S | Only accepts an address holding an s ; addr. existing 'a or longer. |
&'a mut S | Same, but allow content of address to be changed. |
struct S<'a> {} | Signals S will contain address with lifetime 'a . Creator of S decides 'a . |
trait T<'a> {} | Signals a S which impl T for S might contain address. |
fn f<'a>(t: &'a T) | Same, for function. Caller decides 'a . |
'static | Special lifetime lasting the entire program execution. |
Define units of code and their abstractions.
Example | Explanation |
---|---|
trait T {} | Define a trait; BK EX REF common behavior others can implement. |
trait T : R {} | T is subtrait of supertrait BK EX REF R . Any S must impl R before it can impl T . |
impl S {} | Implementation REF of functionality for a type S , e.g., methods. |
impl T for S {} | Implement trait T for type S . |
impl !T for S {} | Disable an automatically derived auto trait. NOM REF 🚧 🝖 |
fn f() {} | Definition of a function; BK EX REF or associated function if inside impl . |
fn f() -> S {} | Same, returning a value of type S. |
fn f(&self) {} | Define a method, BK EX e.g., within an impl S {} . |
struct S (T); | More arcanely, also↑ defines fn S(x: T) -> S constructor function. RFC 🝖 |
const fn f() {} | Constant fn usable at compile time, e.g., const X: u32 = f(Y) . '18 |
async fn f() {} | Async REF '18 function transformation, ↓ makes f return an impl Future . STD |
async fn f() -> S {} | Same, but make f return an impl Future<Output=S> . |
async { x } | Used within a function, make { x } an impl Future<Output=X> . |
fn() -> S | Function pointers, BK STD REF memory holding address of a callable. |
Fn() -> S | Callable Trait BK STD (also FnMut , FnOnce ), implemented by closures, fn's … |
|| {} | A closure BK EX REF that borrows its captures, ↓ REF (e.g., a local variable). |
|x| {} | Closure accepting one argument named x , body is block expression. |
|x| x + x | Same, without block expression; may only consist of single expression. |
move |x| x + y | Closure taking ownership of its captures; i.e., y transferred to closure. |
return || true | Closures sometimes look like logical ORs (here: return a closure). |
unsafe | If you enjoy debugging segfaults Friday night; unsafe code. ↓ BK EX NOM REF |
unsafe fn f() {} | Means "calling can cause UB, ↓ YOU must check requirements". |
unsafe trait T {} | Means "careless impl. of T can cause UB; implementor must check". |
unsafe { f(); } | Guarantees to compiler "I have checked requirements, trust me". |
unsafe impl T for S {} | Guarantees S is well-behaved w.r.t T ; people may use T on S safely. |
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 iter {} | Syntactic sugar to loop over iterators. BK STD REF |
if x {} else {} | Conditional branch REF if expression is true. |
'label: loop {} | Loop label, EX REF useful for flow control in nested loops. |
break | Break expression REF to exit a loop. |
break x | Same, but make x value of the loop expression (only in actual loop ). |
break 'label | Exit not only this loop, but the enclosing one marked with 'label . |
break 'label x | Same, but make x the value of the enclosing loop marked with 'label . |
continue | Continue expression REF to the next loop iteration of this loop. |
continue 'label | Same but instead of this loop, enclosing loop marked with 'label. |
x? | If x is Err or None, return and propagate. BK EX STD REF |
x.await | Only works inside async . Yield flow until Future STD or Stream x 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. |
X::f(x) | Same as x.f() . Unless impl Copy for X {} , f can only be called once. |
X::f(&x) | Same as x.f() . |
X::f(&mut x) | Same as x.f() . |
S::f(&x) | Same as x.f() if X derefs to S , i.e., x.f() finds methods of S . |
T::f(&x) | Same as x.f() if X impl T , i.e., x.f() finds methods of T if in scope. |
X::f() | Call associated function, e.g., X::new() . |
<X as T>::f() | Call trait method T::f() implemented for X . |
Segment projects into smaller units and minimize dependencies.
Example | Explanation |
---|---|
mod m {} | Define a module, BK EX REF get definition from inside {} . ↓ |
mod m; | Define a module, get definition from m.rs or m/mod.rs . ↓ |
a::b | Namespace path EX REF to element b within a (mod , enum , …). |
::b | Search b relative to crate root. 🗑️ |
crate::b | Search b relative to crate root. '18 |
self::b | Search b relative to current module. |
super::b | Search b relative to parent module. |
use a::b; | Use EX REF b directly in this scope without requiring a anymore. |
use a::{b, c}; | Same, but bring b and c into scope. |
use a::b as x; | Bring b into scope but name x , like use std::error::Error as E . |
use a::b as _; | Bring b anonymously into scope, useful for traits with conflicting names. |
use a::*; | Bring everything from a in, only recommended if a is some prelude. STD 🔗 |
pub use a::b; | Bring a::b into scope and reexport from here. |
pub T | "Public if parent path is public" visibility BK REF for T . |
pub(crate) T | Visible at most1 in current crate. |
pub(super) T | Visible at most1 in parent. |
pub(self) T | Visible at most1 in current module (default, same as no pub ). |
pub(in a::b) T | Visible at most1 in ancestor a::b . |
extern crate a; | Declare dependency on external crate; BK REF 🗑️ just use a::b in '18. |
extern "C" {} | Declare external dependencies and ABI (e.g., "C" ) from FFI. BK EX NOM REF |
extern "C" fn f() {} | Define function to be exported with ABI (e.g., "C" ) to FFI. |
1 Items in child modules always have access to any item, regardless if pub
or not.
Short-hand names of types, and methods to convert one type to another.
Example | Explanation |
---|---|
type T = S; | Create a type alias, BK REF i.e., another name for S . |
Self | Type alias for implementing type, REF e.g. fn new() -> Self . |
self | Method subject in fn f(self) {} , same as fn f(self: Self) {} . |
&self | Same, but refers to self as borrowed, same as f(self: &Self) |
&mut self | Same, but mutably borrowed, same as f(self: &mut Self) |
self: Box<Self> | Arbitrary self type, add methods to smart pointers (my_box.f_of_self() ). |
S as T | Disambiguate BK 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 . |
x as u32 | Primitive cast, EX REF may truncate and be a bit surprising. 1 NOM |
1 See Type Conversions below for all the ways to convert between types.
Code generation constructs expanded before the actual compilation happens.
Example | Explanation |
---|---|
m!() | Macro BK STD REF invocation, also m!{} , m![] (depending on macro). |
#[attr] | Outer attribute, EX REF annotating the following item. |
#![attr] | Inner attribute, annotating the upper, surrounding item. |
Inside Macros | Explanation |
---|---|
$x:ty | Macro capture (here a type); see tooling directives ↓ for details. |
$x | Macro substitution, e.g., use the captured $x:ty from above. |
$(x),* | Macro repetition "zero or more times" in macros by example. |
$(x),? | Same, but "zero or one time". |
$(x),+ | Same, but "one or more times". |
$(x)<<+ | In fact separators other than , are also accepted. Here: << . |
Constructs found in match
or let
expressions, or function parameters.
Example | Explanation |
---|---|
match m {} | Initiate pattern matching, BK EX REF then use match arms, c. next table. |
let S(x) = get(); | Notably, let also destructures EX similar to the table below. |
let S { x } = s; | Only x will be bound to value s.x . |
let (_, b, _) = abc; | Only b will be bound to value abc.1 . |
let (a, ..) = abc; | Ignoring 'the rest' also works. |
let (.., a, b) = (1, 2); | Specific bindings take precedence over 'the rest', here a is 1 , b is 2 . |
let s @ S { x } = get(); | Bind s to S while x is bound to s.x , pattern binding, BK EX REF c. below 🝖 |
let w @ t @ f = get(); | Stores 3 copies of get() result in each w , t , f . 🝖 |
let Some(x) = get(); | Won't work 🛑 if pattern can be refuted, REF use if let instead. |
if let Some(x) = get() {} | Branch if pattern can be assigned (e.g., enum variant), syntactic sugar. * |
while let Some(x) = get() {} | Equiv.; here keep calling get() , run {} as long as pattern can be assigned. |
fn f(S { x }: S) | Function parameters also work like let , here x bound to s.x of f(s) . 🝖 |
* Desugars to match get() { Some(x) => {}, _ => () }
.
Pattern matching arms in match
expressions. Left side of these arms can also be found in let
expressions.
Within Match Arm | Explanation |
---|---|
E::A => {} | Match enum variant A , c. pattern matching. BK EX REF |
E::B ( .. ) => {} | Match enum tuple variant B , wildcard any index. |
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. |
D => {} | Match enum variant E::D if D in use . |
D => {} | Match anything, bind D ; possibly false friend 🛑 of E::D if D not in use . |
_ => {} | Proper wildcard that matches anything / "all the rest". |
0 | 1 => {} | Pattern alternatives, or-patterns. RFC |
E::A | E::Z | Same, but on enum variants. |
E::C {x} | E::D {x} | Same, but bind x if all variants have it. |
Some(A | B) | Same, can also match alternatives deeply nested. |
(a, 0) => {} | Match tuple with any value for a and 0 for second. |
[a, 0] => {} | Slice pattern, REF 🔗 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 . |
[1, x @ .., 5] => {} | Same, but also bind x to slice representing middle (c. pattern binding). |
[a, x @ .., b] => {} | Same, but match any first, last, bound as a , b respectively. |
1 .. 3 => {} | Range pattern, BK REF here matches 1 and 2 ; partially unstable. 🚧 |
1 ..= 3 => {} | Inclusive range pattern, matches 1 , 2 and 3 . |
1 .. => {} | Open range pattern, matches 1 and any larger number. |
x @ 1..=5 => {} | Bind matched to x ; pattern binding, BK EX REF here x would be 1 , 2 , … or 5 . |
Err(x @ Error {..}) => {} | Also works nested, here x binds to Error , esp. useful with if below. |
S { x } if x > 10 => {} | Pattern match guards, BK EX REF condition must be true as well to match. |
Generics combine with type constructors, traits and functions to give your users more flexibility.
Example | Explanation |
---|---|
S<T> | A generic BK EX type with a type parameter (T is placeholder name here). |
S<T: R> | Type short hand trait bound BK EX specification (R must be actual trait). |
T: R, P: S | Independent trait bounds (here one for T and one for P ). |
T: R, S | Compile error, 🛑 you probably want compound bound R + S below. |
T: R + S | Compound trait bound, BK EX T must fulfill R and S . |
T: R + 'a | Same, but w. lifetime. T must fulfill R , if T has lifetimes, must outlive 'a . |
T: ?Sized | Opt out of a pre-defined trait bound, here Sized . ? |
T: 'a | Type lifetime bound; EX if T has references, they must outlive 'a . |
T: 'static | Same; does esp. not mean value t will 🛑 live 'static , only that it could. |
'b: 'a | Lifetime 'b must live at least as long as (i.e., outlive) 'a bound. |
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. |
S<{5+5}> | Expressions must be put in curly brackets. |
S<T> where T: R | Almost same as S<T: R> but more pleasant to read for longer bounds. |
S<T> where u8: R<T> | Also allows you to make conditional statements involving other types. |
S<T = R> | Default parameters; BK bit easier to use, but still flexible. |
S<const N: u8 = 0> | Default parameter for constants; e.g., in f(x: S) {} param N is 0 . |
S<T = u8> | Default parameter for types, e.g., in f(x: S) {} param T is u8 . |
S<'_> | Inferred anonymous lifetime; asks compiler to 'figure it out' if obvious. |
S<_> | Inferred anonymous type, e.g., as let x: Vec<_> = iter.collect() |
S::<T> | Turbofish STD call site type disambiguation, e.g. f::<u32>() . |
trait T<X> {} | A trait generic over X . Can have multiple impl T for S (one per X ). |
trait T { type X; } | Defines associated type BK REF RFC X . Only one impl T for S possible. |
type X = R; | Set associated type within impl T for S { type X = 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() -> impl T | Existential types, BK returns an unknown-to-caller S that impl T . |
fn f(x: &impl T) | Trait bound,"impl traits", BK somewhat similar to fn f<S:T>(x: &S) . |
fn f(x: &dyn T) | Marker for dynamic dispatch, BK REF f will not be monomorphized. |
fn f() where Self: R; | In trait T {} , make f accessible only on types known to also impl R . |
fn f() where Self: Sized; | Using Sized can opt f out of dyn T trait object vtable, enabling trait obj. |
fn f() where Self: R {} | Other R useful w. dflt. methods (non dflt. would need be impl'ed anyway). |
Actual types and traits, abstract over something, usually lifetimes.
Example | Explanation |
---|---|
for<'a> | Marker for higher-ranked bounds. NOM REF 🝖 |
trait T: for<'a> R<'a> {} | Any S that impl T would also have to fulfill R for any lifetime. |
fn(&'a u8) | Fn. ptr. type holding fn callable with specific lifetime 'a . |
for<'a> fn(&'a u8) | Higher-ranked type1 🔗 holding fn callable with any lt.; subtype of above. |
fn(&'_ u8) | Same; automatically expanded to type for<'a> fn(&'a u8) . |
fn(&u8) | Same; automatically expanded to type for<'a> fn(&'a u8) . |
dyn for<'a> Fn(&'a u8) | Higher-ranked (trait-object) type, works like fn above. |
dyn Fn(&'_ u8) | Same; automatically expanded to type dyn for<'a> Fn(&'a u8) . |
dyn Fn(&u8) | Same; automatically expanded to type dyn for<'a> Fn(&'a u8) . |
1 Yes, the for<>
is part of the type, which is why you write impl T for for<'a> fn(&'a u8)
below.
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 . |
impl T for fn(&u8) {} | Same, short version. |
Rust has several ways to create textual values.
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 |
b'x' | ASCII byte literal. REF |
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.
Debuggers hate him. Avoid bugs with this one weird trick.
Example | Explanation |
---|---|
/// | Outer line doc comment, BK EX 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. |
/**...*/ | Outer block doc comment. |
/*!...*/ | Inner block doc comment. |
Tooling directives ↓ outlines what you can do inside doc comments.
These sigils did not fit any other category but are good to know nonetheless.
Example | Explanation |
---|---|
! | Always empty never type. 🚧 BK EX STD REF |
_ | Unnamed variable binding, e.g., |x, _| {} . |
let _ = x; | Unnamed assignment is no-op, does not 🛑 move out x or preserve scope! |
_x | Variable binding explicitly marked as unused. |
1_234_567 | Numeric separator for visual clarity. |
1_u8 | Type specifier for numeric literals EX REF (also i8 , u16 , …). |
0xBEEF , 0o777 , 0b1001 | Hexadecimal (0x ), octal (0o ) and binary (0b ) integer literals. |
r#foo | A raw identifier BK EX for edition compatibility. 🝖 |
x; | Statement REF terminator, c. expressions EX REF |
Rust supports most operators you would expect (+
, *
, %
, =
, ==
, …), including overloading. STD Since they behave no differently in Rust we do not list them here.
Arcane knowledge that may do terrible things to your mind, highly recommended.
Like C
and C++
, Rust is based on an abstract machine.
→
→
→
The abstract machine
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. |
If something works that "shouldn't work now that you think about it", it might be due to one of these.
Name | Description |
---|---|
Coercions NOM | Weakens types to match signature, e.g., &mut T to &T ; c. type conversions. ↓ |
Deref NOM 🔗 | Derefs x: T until *x , **x , … compatible with some target S . |
Prelude STD | Automatic import of basic items, e.g., Option , drop , ... |
Reborrow | Since x: &mut T can't be copied; moves new &mut *x instead. |
Lifetime Elision BK NOM REF | Automatically annotates f(x: &T) to f<'a>(x: &'a T) . |
Method Resolution REF | Derefs or borrow x until x.f() works. |
Match Ergonomics RFC | Repeatedly dereferences scrutinee and adds ref and ref mut to bindings. |
Rvalue Static Promotion RFC | Makes references to constants 'static , e.g., &42 , &None , &mut [] . |
Dual Definitions RFC | Defining one thing (e.g., struct S(u8) ) implicitly def. another (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.
Why moves, references and lifetimes are how they are.
Box<T>
),str
part of &str
),1 For fixed-size values stack is trivially managable: take a few bytes more while you need them, discarded once you leave. However, giving out pointers to these transient locations form the very essence of why lifetimes exist; and are the subject of the rest of this chapter.
let t = S(1);
t
of type S
and the value S(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 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.
let a = t;
t
to location of a
, or copy it, if S
is Copy
.t
is invalid and cannot be read anymore.
t
(via unsafe
) they might still look like valid S
, but
any attempt to use them as valid S
is undefined behavior. ↓Copy
types explicitly here. They change the rules a bit, but not much:
let c: S = M::new();
M::new()
cannot be converted to form of type S
.{
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`.
{}
-block it was defined inDrop::drop()
is called on the location of that value.
drop()
is called on a
, twice on c
, but not on t
.Copy
values get dropped most of the time; exceptions include mem::forget()
, Rc
cycles, abort()
.fn f(x: S) { ... }
let a = S(1); // <- We are here
f(a);
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.
fn f(x: S) {
if once() { f(x) } // <- We are here (before recursion)
}
let a = S(1);
f(a);
fn f(x: S) {
if once() { f(x) }
let m = M::new() // <- We are here (after recursion)
}
let a = S(1);
f(a);
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.
let a = S(1);
let r: &S = &a;
&S
or &mut S
can hold the location of some s
.&S
, bound as name r
, holds location of variable a
(0x3
), that must be type S
, obtained via &a
.c
as specific location, reference r
is a switchboard for locations.let r: &S = &a;
let r = &a;
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.
&S
) and also write to (&mut S
) location they point to.*r
means to neither use the location of or value within r
, but the location r
points to.d
is created from *r
, and S(2)
written to *r
.
Clone::clone(&T)
expects a reference itself, which is why we can use r
, not *r
.*r = ...
old value in location also dropped (not shown above).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.
&mut T
must provide same guarantees as variables, and some more as they can't dissolve the target:
let p: *const S = questionable_origin();
unsafe
, and treating an invalid *p
as if it were valid is undefined behavior. ↓r: &'a S
, are
r
itself (well, it needs to exist shorter, that's it).&'static S
means address must be valid during all lines of code.1 There is sometimes ambiguity in the docs differentiating the various scopes and lifetimes. We try to be pragmatic here, but suggestions are welcome.
2 Live lines might have been a more appropriate term ...
r: &'c S
r: &'c S
from somewhere it means:
r
holds an address of some S
,r
points to must and will exist for at least 'c
,r
itself cannot live longer than 'c
.{
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.
}
}
}
mut r: &mut 'c S
from somewhere.
'c
part, like a type, also guards what is allowed into r
.&b
(0x6
) to r
is valid, but &a
(0x3
) would not, as only &b
lives equal or longer than &c
.let mut b = S(0);
let r = &mut b;
b = S(4); // Will fail since `b` in borrowed state.
print_byte(r);
&b
or &mut b
the variable is marked as borrowed.b
.&b
or &mut b
stops being used (in terms of LOC) original binding b
works again.fn f(x: &S, y:&S) -> &u8 { ... }
let b = S(1);
let c = S(2);
let r = f(&b, &c);
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);
f
can return only one address, not in all cases b
and c
need to stay locked.fn f<'b, 'c>(x: &'b S, y: &'c S) -> &'c u8 { ... }
let b = S(1);
let c = S(2);
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);
'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
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.f
had 'c: 'b
instead, we still couldn't distinguish and both needed to stay locked.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`.
↕️ Examples expand by clicking.
Memory representations of common data types.
Essential types built into the core of the language.
u8
, i8
u16
, i16
u32
, i32
u64
, i64
u128
, i128
f32
f64
usize
, isize
ptr
on platform. Type | Max Value |
---|---|
u8 | 255 |
u16 | 65_535 |
u32 | 4_294_967_295 |
u64 | 18_446_744_073_709_551_615 |
u128 | 340_282_366_920_938_463_463_374_607_431_768_211_455 |
usize | Depending on platform pointer size, same as u16 , u32 , or u64 . |
Type | Max Value |
---|---|
i8 | 127 |
i16 | 32_767 |
i32 | 2_147_483_647 |
i64 | 9_223_372_036_854_775_807 |
i128 | 170_141_183_460_469_231_731_687_303_715_884_105_727 |
isize | Depending on platform pointer size, same as i16 , i32 , or i64 . |
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 |
isize | Depending on platform pointer size, same as i16 , i32 , or i64 . |
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:
f32 | S (1) | E (8) | F (23) | Value |
---|---|---|---|---|
Normalized number | ± | 1 to 254 | any | ±(1.F)2 * 2E-127 |
Denormalized number | ± | 0 | non-zero | ±(0.F)2 * 2-126 |
Zero | ± | 0 | 0 | ±0 |
Infinity | ± | 255 | 0 | ±∞ |
NaN | ± | 255 | non-zero | NaN |
Similarly, for f64
types this would look like:
f64 | S (1) | E (11) | F (52) | Value |
---|---|---|---|---|
Normalized number | ± | 1 to 2046 | any | ±(1.F)2 * 2E-1023 |
Denormalized number | ± | 0 | non-zero | ±(0.F)2 * 2-1022 |
Zero | ± | 0 | 0 | ±0 |
Infinity | ± | 2047 | 0 | ±∞ |
NaN | ± | 2047 | non-zero | NaN |
Cast1 | Gives | Note |
---|---|---|
3.9_f32 as u8 | 3 | Truncates, consider x.round() first. |
314_f32 as u8 | 255 | Takes closest available number. |
f32::INFINITY as u8 | 255 | Same, treats INFINITY as really large number. |
f32::NAN as u8 | 0 | - |
_314 as u8 | 58 | Truncates excess bits. |
_200 as i8 | 56 | - |
_257 as i8 | -1 | - |
Operation1 | Gives | Note |
---|---|---|
200_u8 / 0_u8 | Compile error. | - |
200_u8 / _0 d | Panic. | Regular math may panic; here: division by zero. |
200_u8 / _0 r | Panic. | Same. |
200_u8 + 200_u8 | Compile error. | - |
200_u8 + _200 d | Panic. | Consider checked_ , wrapping_ , ... instead. STD |
200_u8 + _200 r | 144 | In release mode this will overflow. |
1_u8 / 2_u8 | 0 | Other integer division truncates. |
0.8_f32 + 0.1_f32 | 0.90000004 | - |
1.0_f32 / 0.0_f32 | f32::INFINITY | - |
0.0_f32 / 0.0_f32 | f32::NAN | - |
x < f32::NAN | false | NAN comparisons always return false. |
x > f32::NAN | false | - |
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
&str
instead.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. |
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. |
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. |
let s = "I ❤ Rust";
let t = "I ❤️ Rust";
Variant | Memory Representation2 |
---|---|
s.as_bytes() | 49 20 e2 9d a4 20 52 75 73 74 3 |
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 … |
❤
, 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
.❤️
, 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
andt
above.
Basic types definable by users. Actual layout REF is subject to representation; REF padding can be present.
T
T
T: ?Sized
T
[T; n]
T
T
T
n
elements.[T]
T
T
T
Sized
(nor carries len
information), and most&[T]
. ↓struct S;
;
(A, B, C)
A
B
C
B
A
C
#[repr(C)]
), type layoutstruct S { b: B, c: C }
B
C
C
↦
B
Also note, two types
A(X, Y)
andB(X, Y)
with exactly the same fields can still have differing layout; nevertransmute()
without representation guarantees.
These sum types hold a value of one of their sub types:
enum E { A, B, C }
Tag
A
Tag
B
Tag
C
union { ... }
A
B
C
References give safe access to 3rd party memory, raw pointers unsafe
access.
The corresponding mut
types have an identical data layout to their immutable counterparts.
&'a T
ptr
2/4/8 meta
2/4/8 T
t
of T
, 'a
.*const T
ptr
2/4/8 meta
2/4/8 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
&'a T
ptr
2/4/8 len
2/4/8 T
T
is a DST struct
such asS { x: [u8] }
meta field len
is &'a [T]
ptr
2/4/8 len
2/4/8 T
T
[T]
) ↑ &[T]
if 'a
elided.&'a str
ptr
2/4/8 len
2/4/8 U
T
F
-
8
str
),len
being byte length.&'a dyn Trait
ptr
2/4/8 ptr
2/4/8 T
*Drop::drop(&mut T) |
size |
align |
*Trait::f(&T, ...) |
*Trait::g(&T, ...) |
*Drop::drop()
, *Trait::f()
, ... are pointers to their respective impl
for T
.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
|x| x + y.f() + z
ptr
2/4/8 ptr
2/4/8 Y
Z
Also produces anonymous
fn
such asfc1(C1, X)
orfc2(&C2, X)
. Details depend whichFnOnce
,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
Cell<T>
T
T
'sRefCell<T>
borrowed
T
T
. Like Cell
thisSend
, but not Sync
.AtomicUsize
usize
2/4/8 Result<T, E>
Tag
E
Tag
T
Option<T>
Tag
Tag
T
NonNull
.Box<T>
ptr
2/4/8 meta
2/4/8 T
T
stack proxy may carry Box<[T]>[T]>
).Vec<T>
ptr
2/4/8 capacity
2/4/8 len
2/4/8 T
T
String
ptr
2/4/8 capacity
2/4/8 len
2/4/8 U
T
F
-
8
String
differs from &str
and &[char]
.CString
ptr
2/4/8 len
2/4/8 A
B
C
␀
OsString
??
?
?
?
PathBuf
?OsString
?
?
?
?
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>
ptr
2/4/8 meta
2/4/8 strng
2/4/8weak
2/4/8T
T
in same thread. Needs nested Cell
RefCell
to allow mutation. Is neither Send
nor Sync
.Arc<T>
ptr
2/4/8 meta
2/4/8 strng
2/4/8weak
2/4/8T
T
itself is Send
and Sync
.Mutex<T>
/ RwLock<T>
ptr
2/4/8poison
2/4/8T
lock
Arc
to be shared betweenSend
and Sync
. Consider using Snippets that are common, but still easy to forget. See Rust Cookbook 🔗 for more.
Intent | Snippet |
---|---|
Concatenate strings (any Display ↓ that is). 1 '21 | format!("{x}{y}") |
Split by separator pattern. STD 🔗 | s.split(pattern) |
... with &str | s.split("abc") |
... with char | s.split('/') |
... with closure | s.split(char::is_numeric) |
Split by whitespace. | s.split_whitespace() |
Split by newlines. | s.lines() |
Split by regular expression.2 | Regex::new(r"\s")?.split("one two three") |
1 Allocates; might not be fastest solution if x
is String
already.
2 Requires regex crate.
Intent | Snippet |
---|---|
Create a new file | File::create(PATH)? |
Same, via OpenOptions | OpenOptions::new().create(true).write(true).truncate(true).open(PATH)? |
Intent | Snippet |
---|---|
Macro w. variable arguments | macro_rules! var_args { ($($args:expr),*) => {{ }} } |
Using args , e.g., calling f multiple times. | $( f($args); )* |
Intent | Snippet |
---|---|
Cleaner closure captures | wants_closure({ let c = outer.clone(); move || use_clone(c) }) |
Fix inference in 'try ' closures | iter.try_for_each(|x| { Ok::<(), Error>(()) })?; |
Iterate and edit &mut [T] if T Copy. | Cell::from_mut(mut_slice).as_slice_of_cells() |
Get subslice with length. | &original_slice[offset..][..length] |
Canary to ensure trait T is object safe. | const _: Option<&dyn T> = None; |
Examples | Send * | !Send |
---|---|---|
Sync * | Most types ... Mutex<T> , Arc<T> 1,2 | MutexGuard<T> 1, RwLockReadGuard<T> 1 |
!Sync | Cell<T> 2, RefCell<T> 2 | Rc<T> , &dyn Trait , *const T 3, *mut T 3 |
* 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.
Basics
Assume you have a collection c
of type C
:
c.into_iter()
— Turns collection c
into an Iterator
STD i
and consumes* c
. Requires IntoIterator
STD for C
to be implemented. Type of item depends on what C
was. 'Standardized' way to get Iterators.c.iter()
— Courtesy method some collections provide, returns borrowing Iterator, doesn't consume c
.c.iter_mut()
— Same, but mutably borrowing Iterator that allow collection to be changed.The Iterator
Once you have an i
:
i.next()
— Returns Some(x)
next element c
provides, or None
if we're done.For Loops
for x in c {}
— Syntactic sugar, calls c.into_iter()
and loops i
until None
.* If it looks as if it doesn't consume c
that's because type was Copy
. For example, if you call (&c).into_iter()
it will invoke .into_iter()
on &c
(which will consume the reference and turn it into an Iterator), but c
remains untouched.
Basics
Let's assume you have a struct Collection<T> {}
.
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;
Shared & Mutable Iterators
struct Iter<T> {}
— Create struct holding &Collection<T>
for shared iteration.struct IterMut<T> {}
— Similar, but holding &mut Collection<T>
for mutable iteration.impl Iterator for Iter<T> {}
— Implement shared iteration.impl Iterator for IterMut<T> {}
— Implement mutable iteration.In addition, you might want to add convenience methods:
Collection::iter(&self) -> Iter
,Collection::iter_mut(&mut self) -> IterMut
.Iter<T>
Iterator
Item = &T;
IterMut<T>
Iterator
Item = &mut T;
Making Loops Work
impl IntoIterator for Collection {}
— Now for x in c {}
works.impl IntoIterator for &Collection {}
— Now for x in &c {}
works.impl IntoIterator for &mut Collection {}
— Now for x in &mut c {}
works.Collection<T>
IntoIterator
Item = T;
To = IntoIter<T>
T
.&Collection<T>
IntoIterator
Item = &T;
To = Iter<T>
&T
.&mut Collectn<T>
IntoIterator
Item = &mut T;
To = IterMut<T>
&mut T
.As-correct-as-it-currently-gets number conversions.
↓ Have / Want → | u8 … i128 | f32 / f64 | String |
---|---|---|---|
u8 … i128 | u8::try_from(x)? 1 | x as f32 3 | x.to_string() |
f32 / f64 | x as u8 2 | x as f32 | x.to_string() |
String | x.parse::<u8>()? | x.parse::<f32>()? | x |
1 If type true subset from()
works directly, e.g., u32::from(my_u8)
.
2 Truncating (11.9_f32 as u8
gives 11
) and saturating (1024_f32 as u8
gives 255
); c. below.
3 Might misrepresent number (u64::MAX as f32
) or produce Inf
(u128::MAX as f32
).
If you want a string of type …
If you have x of type … | Use this … |
---|---|
String | x |
CString | x.into_string()? |
OsString | x.to_str()?.to_string() |
PathBuf | x.to_str()?.to_string() |
Vec<u8> 1 | String::from_utf8(x)? |
&str | x.to_string() i |
&CStr | x.to_str()?.to_string() |
&OsStr | x.to_str()?.to_string() |
&Path | x.to_str()?.to_string() |
&[u8] 1 | String::from_utf8_lossy(x).to_string() |
If you have x of type … | Use this … |
---|---|
String | CString::new(x)? |
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)? |
&CStr | x.to_owned() i |
&OsStr 2 | CString::new(x.to_os_string().into_string()?)? |
&Path | CString::new(x.to_str()?)? |
&[u8] 1 | CString::new(Vec::from(x))? |
*mut c_char 3 | unsafe { CString::from_raw(x) } |
If you have x of type … | Use this … |
---|---|
String | OsString::from(x) i |
CString | OsString::from(x.to_str()?) |
OsString | x |
PathBuf | x.into_os_string() |
Vec<u8> 1 | ? |
&str | OsString::from(x) i |
&CStr | OsString::from(x.to_str()?) |
&OsStr | OsString::from(x) i |
&Path | x.as_os_str().to_owned() |
&[u8] 1 | ? |
If you have x of type … | Use this … |
---|---|
String | PathBuf::from(x) i |
CString | PathBuf::from(x.to_str()?) |
OsString | PathBuf::from(x) i |
PathBuf | x |
Vec<u8> 1 | ? |
&str | PathBuf::from(x) i |
&CStr | PathBuf::from(x.to_str()?) |
&OsStr | PathBuf::from(x) i |
&Path | PathBuf::from(x) i |
&[u8] 1 | ? |
If you have x of type … | Use this … |
---|---|
String | x.into_bytes() |
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() |
If you have x of type … | Use this … |
---|---|
String | x.as_str() |
CString | x.to_str()? |
OsString | x.to_str()? |
PathBuf | x.to_str()? |
Vec<u8> 1 | std::str::from_utf8(&x)? |
&str | x |
&CStr | x.to_str()? |
&OsStr | x.to_str()? |
&Path | x.to_str()? |
&[u8] 1 | std::str::from_utf8(x)? |
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 |
Vec<u8> 1,5 | CStr::from_bytes_with_nul(&x)? |
&str | ?,4 |
&CStr | x |
&OsStr 2 | ? |
&Path | ? |
&[u8] 1,5 | CStr::from_bytes_with_nul(x)? |
*const c_char 1 | unsafe { CStr::from_ptr(x) } |
If you have x of type … | Use this … |
---|---|
String | OsStr::new(&x) |
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 | ? |
If you have x of type … | Use this … |
---|---|
String | Path::new(x) r |
CString | Path::new(x.to_str()?) |
OsString | Path::new(x.to_str()?) r |
PathBuf | Path::new(x.to_str()?) r |
Vec<u8> 1 | ? |
&str | Path::new(x) r |
&CStr | Path::new(x.to_str()?) |
&OsStr | Path::new(x) r |
&Path | x |
&[u8] 1 | ? |
If you have x of type … | Use this … |
---|---|
String | x.as_bytes() |
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 |
You want | And have x | Use this … |
---|---|---|
*const c_char | CString | x.as_ptr() |
i Short form x.into()
possible if type can be inferred.
r Short form x.as_ref()
possible if type can be inferred.
1 You 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_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
.
5 Must ensure vector actually ends with 0x0
.
How to convert types into a String
, or output them.
Rust has, among others, these APIs to convert types to stringified output, collectively called format macros:
Macro | Output | Notes |
---|---|---|
format!(fmt) | String | Bread-and-butter "to String " converter. |
print!(fmt) | Console | Writes to standard output. |
println!(fmt) | Console | Writes to standard output. |
eprint!(fmt) | Console | Writes to standard error. |
eprintln!(fmt) | Console | Writes to standard error. |
write!(dst, fmt) | Buffer | Don't forget to also use std::io::Write; |
writeln!(dst, fmt) | Buffer | Don't forget to also use std::io::Write; |
Method | Notes |
---|---|
x.to_string() STD | Produces String , implemented for any Display type. |
Here fmt
is string literal such as "hello {}"
, that specifies output (compare "Formatting" tab) and additional parameters.
In format!
and friends, types convert via trait Display
"{}"
STD or Debug
"{:?}"
STD , non exhaustive list:
Type | Implements | |
---|---|---|
String | Debug, Display | |
CString | Debug | |
OsString | Debug | |
PathBuf | Debug | |
Vec<u8> | Debug | |
&str | Debug, Display | |
&CStr | Debug | |
&OsStr | Debug | |
&Path | Debug | |
&[u8] | Debug | |
bool | Debug, Display | |
char | Debug, Display | |
u8 … i128 | Debug, Display | |
f32 , f64 | Debug, Display | |
! | Debug, Display | |
() | Debug |
In short, pretty much everything is Debug
; more special types might need special handling or conversion ↑ to Display
.
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 , ...), argument '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 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. |
type | Debug STD (? ) formatting, hex (x ), binary (b ), octal (o ), pointer (p ), exp (e ) ... see more. |
Format Example | Explanation |
---|---|
{} | Print the next argument using Display .STD |
{x} | Same, but use variable x from scope. '21 |
{:?} | Print the next argument using Debug .STD |
{2:#?} | Pretty-print the 3rd argument with Debug STD formatting. |
{val:^2$} | Center the val named argument, width specified by the 3rd argument. |
{:<10.3} | Left align with width 10 and a precision of 3. |
{val:#x} | Format val argument as hex, with a leading 0x (alternate format for x ). |
Basic project layout, and common files and folders, as used by cargo
. ↓
Entry | Code |
---|---|
📁 .cargo/ | Project-local cargo configuration, may contain config.toml . 🔗 🝖 |
📁 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 . |
📁 src/ | Actual source code for your project. |
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. |
📁 src/bin/ | Place for additional binaries, even in library projects. |
x.rs | Additional binary, run with cargo run --bin x . |
📁 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 🝖 |
build.rs | Pre-build script, 🔗 useful when compiling C / FFI, ... |
Cargo.toml | Main project manifest, 🔗 Defines dependencies, artifacts ... |
Cargo.lock | Dependency details for reproducible builds; add to git for apps, not for libs. |
rust-toolchain.toml | Define toolchain override🔗 (channel, components, targets) for this project. |
* On stable consider Criterion.
Minimal examples for various entry points might look like:
// src/main.rs (default application entry point)
fn main() {
println!("Hello, world!");
}
// 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.
// 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);
}
}
// 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.
// 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.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");
}
*See here for list of environment variables set.
// src/lib.rs (default entry point for proc macros)
extern crate proc_macro; // Apparently needed to be imported like this.
use proc_macro::TokenStream;
#[proc_macro_attribute] // Can now be used as `#[my_attribute]`
pub fn my_attribute(_attr: TokenStream, item: TokenStream) -> TokenStream {
item
}
// Cargo.toml
[package]
name = "my_crate"
version = "0.1.0"
[lib]
proc-macro = true
Module trees and imports:
Modules BK EX REF and source files work as follows:
lib.rs
).Actual module definitions work as follows:
mod m {}
defines module in-file, while mod m;
will read m.rs
or m/mod.rs
..rs
based on nesting, e.g., mod a { mod b { mod c; }}}
is either a/b/c.rs
or a/b/c/mod.rs
.mod m;
won't be touched by compiler! 🛑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.
enum X {}
and fn X() {}
can coexiststruct X;
and const X
cannot coexistuse my_mod::X;
all items called X
will be imported.Due to naming conventions (e.g.,
fn
andmod
are lowercase by convention) and common sense (most developers just don't name all thingsX
) 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.
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 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) |
build -Z timings | Show what crates caused your build to take so long, highly useful. 🚧 🔥 |
rustc -- -Zunpretty=expanded | Show expanded macros. 🚧 |
rustup doc | Open offline Rust documentation (incl. the books), good on a plane! |
Here cargo build
means you can either type cargo build
or just cargo b
; and --release
means it can be replaced with -r
.
These are optional rustup
components.
Install them with rustup component add [tool]
.
Tool | Description |
---|---|
cargo clippy | Additional (lints) catching common API misuses and unidiomatic code. 🔗 |
cargo fmt | Automatic code formatter (rustup component add rustfmt ). 🔗 |
A large number of additional cargo plugins can be found here.
🔘 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:
[target.aarch64-linux-android]
linker = "[PATH_TO_TOOLCHAIN]/aarch64-linux-android/bin/aarch64-linux-android-clang"
or
[target.aarch64-linux-android]
linker = "C:/[PATH_TO_TOOLCHAIN]/prebuilt/windows-x86_64/bin/aarch64-linux-android21-clang.cmd"
🔘 Set environment variables (optional, wait until compiler complains before setting):
set CC=C:\[PATH_TO_TOOLCHAIN]\prebuilt\windows-x86_64\bin\aarch64-linux-android21-clang.cmd
set CXX=C:\[PATH_TO_TOOLCHAIN]\prebuilt\windows-x86_64\bin\aarch64-linux-android21-clang.cmd
set AR=C:\[PATH_TO_TOOLCHAIN]\prebuilt\windows-x86_64\bin\aarch64-linux-android-ar.exe
...
Whether you set them depends on how compiler complains, not necessarily all are needed.
Some platforms / configurations can be extremely sensitive how paths are specified (e.g.,
\
vs/
) and quoted.
✔️ Compile with cargo build --target=X
Special tokens embedded in source code used by tooling or preprocessing.
Inside a declarative BK macro by example BK EX REF macro_rules!
implementation these work:
Within Macros | Explanation |
---|---|
$x:ty | Macro capture (here a type). |
$x:item | An item, like a function, struct, module, etc. |
$x:block | A block {} of statements or expressions, e.g., { let x = 5; } |
$x:stmt | A statement, e.g., let x = 1 + 1; , String::new(); or vec![]; |
$x:expr | An expression, e.g., x , 1 + 1 , String::new() or vec![] |
$x:pat | A pattern, e.g., Some(t) , (17, 'a') or _ . |
$x:ty | A type, e.g., String , usize or Vec<u8> . |
$x:ident | An identifier, for example in let x = 0; the identifier is x . |
$x:path | A path (e.g. foo , ::std::mem::replace , transmute::<_, int> ). |
$x:literal | A literal (e.g. 3 , "foo" , b"bar" , etc.). |
$x:lifetime | A lifetime (e.g. 'a , 'static , etc.). |
$x:meta | A meta item; the things that go inside #[...] and #![...] attributes. |
$x:vis | A visibility modifier; pub , pub(crate) , etc. |
$x:tt | A single token tree, see here for more details. |
$crate | Special hygiene variable, crate where macros is defined. ? |
Inside a doc comment BK EX REF these work:
Within Doc Comments | Explanation |
---|---|
```...``` | Include a doc test (doc code running on cargo test ). |
```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 . |
[`S`](crate::S) | Paths can also be used, in the form of markdown links. |
Attributes affecting the whole crate or app:
Opt-Out's | On | Explanation |
---|---|---|
#![no_std] | C | Don't (automatically) import std STD ; use core STD instead. REF |
#![no_implicit_prelude] | CM | Don't add prelude STD, need to manually import None , Vec , ... REF |
#![no_main] | C | Don't emit main() in apps if you do that yourself. REF |
Opt-In's | On | Explanation |
---|---|---|
#![feature(a, b, c)] | C | Rely on features that may never get stabilized, c. Unstable Book. 🚧 |
Builds | On | Explanation |
---|---|---|
#![windows_subsystem = "x"] | C | On Windows, make a console or windows app. REF 🝖 |
#![crate_name = "x"] | C | Specifiy current crate name, e.g., when not using cargo . ? 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 |
#[global_allocator] | S | Make static item impl. GlobalAlloc STD global allocator. REF |
Attributes primarily governing emitted 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 |
#[inline(never)] | F | Instruct compiler to feel disappointed if it still inlines the function. REF |
#[cold] | F | Hint that function probably isn't going to be called. REF |
#[target_feature(enable="x")] | F | Enable CPU feature (e.g., avx2 ) for code of unsafe fn . REF |
#[track_caller] | F | Allows fn to find caller STD for better panic messages. REF |
#[repr(X)] 1 | T | Use another representation instead of the default rust REF one: |
#[repr(C)] | T | Use a C-compatible (f. FFI), predictable (f. transmute ) layout. 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 |
#[repr(packed(1))] | T | Lower alignment of struct and contained fields, mildly UB prone. REF |
#[repr(align(8))] | T | Raise alignment of struct to given value, e.g., for SIMD types. 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 |
Attributes used by Rust tools to improve code quality:
Code Patterns | On | Explanation |
---|---|---|
#[allow(X)] | * | Instruct rustc / clippy to ... ignore class X of possible issues. REF |
#[warn(X)] 1 | * | ... emit a warning, mixes well with clippy lints. 🔥 REF |
#[deny(X)] 1 | * | ... fail compilation. REF |
#[forbid(X)] 1 | * | ... fail compilation and prevent subsequent allow overrides. REF |
#[deprecated = "msg"] | * | Let your users know you made a design mistake. REF |
#[must_use = "msg"] | FTX | Makes compiler check return value is processed by caller. 🔥 REF |
1 There is some debate which one is the best to ensure high quality crates. Actively maintained multi-dev crates probably benefit from more aggressive deny
or forbid
lints; less-regularly updated ones probably more from conservative use of warn
(as future compiler or clippy
updates may suddenly break otherwise working code with minor issues).
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 |
---|---|---|
#[rustfmt::skip] | * | Prevent cargo fmt from cleaning up item. 🔗 |
#![rustfmt::skip::macros(x)] | CM | ... from cleaning up macro x . 🔗 |
#![rustfmt::skip::attributes(x)] | CM | ... from cleaning up attribute x . 🔗 |
Documentation | On | Explanation |
---|---|---|
#[doc = "Explanation"] | * | Same as adding a /// doc comment. 🔗 |
#[doc(alias = "other")] | * | Provide another name users can search for in the docs. 🔗 |
#[doc(hidden)] | * | Prevent item from showing up in docs. 🔗 |
#![doc(html_favicon_url = "")] | C | Sets the favicon for the docs. 🔗 |
#![doc(html_logo_url = "")] | C | The logo used in the docs. 🔗 |
#![doc(html_playground_url = "")] | C | Generates Run buttons and uses given service. 🔗 |
#![doc(html_root_url = "")] | C | Base URL for links to external crates. 🔗 |
#![doc(html_no_source)] | C | Prevents source from being included in docs. 🔗 |
Attributes related to the creation and use of macros:
Macros By Example | On | Explanation |
---|---|---|
#[macro_export] | ! | Export macro_rules! as pub on crate level REF |
#[macro_use] | MX | Let macros persist past modules; or import from extern crate . REF |
Proc Macros | On | Explanation |
---|---|---|
#[proc_macro] | F | Mark fn as function-like procedural macro callable as m!() . REF |
#[proc_macro_derive(Foo)] | F | Mark fn as derive macro which can #[derive(Foo)] . 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 |
Attributes governing conditional compilation:
Config Attributes | On | Explanation |
---|---|---|
#[cfg(X)] | * | Include item if configuration X holds. REF |
#[cfg(all(X, Y, Z))] | * | Include item if all options hold. REF |
#[cfg(any(X, Y, Z))] | * | Include item if at least one option holds. REF |
#[cfg(not(X))] | * | Include item if X does not hold. REF |
#[cfg_attr(X, foo = "msg")] | * | Apply #[foo = "msg"] if configuration X holds. REF |
⚠️ 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(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 |
Environment variables and outputs related to the pre-build script.
Input Environment | Explanation 🔗 |
---|---|
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 _ . |
CARGO_CFG_X | Exposes cfg's; joins mult. opts. by , and converts - to _ . |
CARGO_CFG_TARGET_OS=macos | If target_os were set to macos . |
CARGO_CFG_TARGET_FEATURE=avx,avx2 | If target_feature were set to avx and avx2 . |
OUT_DIR | Where output should be placed. |
TARGET | Target triple being compiled for. |
HOST | Host triple (running this build script). |
PROFILE | Can be debug or release . |
Available in build.rs
via env::var()?
. List not exhaustive.
Output String | Explanation 🔗 |
---|---|
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.
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.
Allowing users to bring their own types and avoid code duplication.
u8
String
Device
Type | Values |
---|---|
u8 | { 0u8, 1u8, ..., 255u8 } |
char | { 'a', 'b', ... '🦀' } |
struct S(u8, char) | { (0u8, 'a'), ... (255u8, '🦀') } |
u8
&u8
&mut u8
[u8; 1]
String
u8
, &u8
, &mut u8
, are entirely different from each othert: 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, ... } |
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 of u16
and 0_u8
being the same as 0_u16
) where such a conversion would be purely a compile time check. Rust does not use subtyping for regular types (and 0_u8
does differ from 0_u16
) but sort-of for lifetimes. 🔗
2 Safety here is not just physical concept (e.g., &u8
can't be coerced to &u128
), but also whether 'history has shown that such a conversion would lead to programming errors'.
impl S { }
u8
impl { ... }
String
impl { ... }
Port
impl { ... }
impl Port {
fn f() { ... }
}
impl Port {}
, behavior related to type:
Port::new(80)
port.close()
What's considered related is more philosophical than technical, nothing (except good taste) would prevent a
u8::play_sound()
from happening.
trait T { }
Copy
Clone
Sized
ShowHex
Copy Trait |
---|
Self |
u8 |
u16 |
... |
Clone Trait |
---|
Self |
u8 |
String |
... |
Sized Trait |
---|
Self |
char |
Port |
... |
Self
refers to the type included.
trait ShowHex {
// Must be implemented according to documentation.
fn as_hex() -> String;
// Provided by trait author.
fn print_hex() {}
}
Copy
trait 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 ShowHex for Port { ... }
impl A for B
add type B
to the trait membership list:ShowHex Trait |
---|
Self |
Port |
u8
impl { ... }
Sized
Clone
Copy
Device
impl { ... }
Transport
Port
impl { ... }
Sized
Clone
ShowHex
Eat
Venison
Eat
venison.eat()
Interfaces
Eat
.Venison
, he must decide if Venison
implements Eat
or not.Venison
, Santa can make use of behavior provided by Eat
:// Santa imports `Venison` to create it, can `eat()` if he wants.
import food.Venison;
new Venison("rudolph").eat();
Eat
Venison
Venison
+
Eat
venison.eat()
Traits
Eat
.Venison
and decides not to implement Eat
(he might not even know about Eat
).Eat
to Venison
would be a really good idea.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. ?
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<> | - |
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.<T>
Vec<T>
[T; 128]
&T
&mut T
S<T>
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 , ... |
// 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);
}
[T; N]
and S<const N: usize>
[T; n]
S<const N>
[T; n]
constructs array type holding T
type n
times.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> , ... |
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],
}
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 a Num<T>
?// 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 |
---|
Self |
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.
where T: X + Y
u8
Absolute
Dim
Mul
f32
Absolute
Mul
char
Cmplx
Absolute
Dim
Mul
DirName
TwoD
Car
DirName
struct S<T>
where
T: Absolute + Dim + Mul + DirName + TwoD
{ ... }
+ X
addition to a bound merely cuts down space of eligible types.impl<>
When we write:
impl<T> S<T> where T: Absolute + Dim + Mul {
fn f(&self, x: T) { ... };
}
It can be read as:
T
(the impl <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:
// 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');
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.
ToHex |
---|
Self |
Port |
Device |
... |
→ Whatever was in left table, may be added to right table, based on the following recipe (impl
) →
Serialize Trait |
---|
Self |
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) 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; }
From | |
---|---|
Self | I |
u16 | u8 |
u32 | u16 |
... |
Deref | |
---|---|
Self | O |
Port | u8 |
String | str |
... |
Now here's the twist,
O
parameters must be uniquely determined by input parameters I
,X Y
would represent a function),Self
counts as an input.A more complex example:
trait Complex<I1, I2> {
type O1;
type O2;
}
Complex
,Self
is always one) and 2 outputs, and it holds (Self, I1, I2) => (O1, O2)
Complex | ||||
---|---|---|---|---|
Self [I] | 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 |
(NiceMonster, u16, String)
has
already 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).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; }
A | |
---|---|
Self | I |
X | u32 |
Y | ... |
T
.
B | |
---|---|
Self | O |
Player | String |
X | u32 |
Self
), implementor must pre-select O
.
Query
vs.
Query<I>
vs.
Query
type O;
vs.
Query<I>
type O;
Choice of parameters goes along with purpose trait has to fill.
No Additional Parameters
trait Query {
fn search(&self, needle: &str);
}
impl Query for PostgreSQL { ... }
impl Query for Sled { ... }
postgres.search("SELECT ...");
Query
→
PostgreSQL
Query
Sled
Query
Trait author assumes:
Input Parameters
trait Query<I> {
fn search(&self, needle: I);
}
impl Query<&str> for PostgreSQL { ... }
impl Query<String> for PostgreSQL { ... }
impl<T> Query<T> for Sled where T: ToU8Slice { ... }
postgres.search("SELECT ...");
postgres.search(input.to_string());
sled.search(file);
Query<I>
→
PostgreSQL
Query<&str>
Query<String>
Sled
Query<T>
T
is ToU8Slice
.Trait author assumes:
Self
type,I
-types behavior should be possible.Output Parameters
trait Query {
type O;
fn search(&self, needle: Self::O);
}
impl Query for PostgreSQL { type O = String; ...}
impl Query for Sled { type O = Vec<u8>; ... }
postgres.search("SELECT ...".to_string());
sled.search(vec![0, 1, 2, 4]);
Query
type O;
→
PostgreSQL
Query
O = String;
Sled
Query
O = Vec<u8>;
Trait author assumes:
Self
type (but in only one way),Self
.As you can see here, the term input or output does not (necessarily) have anything to do with whether
I
orO
are inputs or outputs to an actual function!
Multiple In- and Output Parameters
trait Query<I> {
type O;
fn search(&self, needle: I) -> Self::O;
}
impl Query<&str> for PostgreSQL { type O = String; ... }
impl Query<CString> for PostgreSQL { type O = CString; ... }
impl<T> Query<T> for Sled where T: ToU8Slice { type O = Vec<u8>; ... }
postgres.search("SELECT ...").to_uppercase();
sled.search(&[1, 2, 3, 4]).pop();
Query<I>
type O;
→
PostgreSQL
Query<&str>
O = String;
Query<CString>
O = CString;
Sled
Query<T>
O = Vec<u8>;
T
is ToU8Slice
.Like examples above, in particular trait author assumes:
I
-types ability should be possible,MostTypes
Sized
vs.
Z
Sized
vs.
str
Sized
[u8]
Sized
dyn Trait
Sized
...
Sized
T
is Sized
STD if at compile time it is known how many bytes it occupies, u8
and &[u8]
are, [u8]
isn't.Sized
means impl Sized for T {}
holds. Happens automatically and cannot be user impl'ed.Sized
are called dynamically sized types BK NOM REF (DSTs), sometimes unsized.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 . |
struct C<T> { x: T } | Type params have implicit T: Sized bound, e.g., C<A> is valid, C<B> is 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>
struct S<T> { ... }
T
can be any concrete type.T: Sized
, so S<str>
is not possible out of box.T : ?Sized
to opt-out of that bound:S<T>
→
S<u8>
S<char>
S<str>
struct S<T> where T: ?Sized { ... }
<'a>
S<'a>
&'a f32
&'a mut u8
'a
to instantiate type (compiler will help within methods),Vec<f32>
and Vec<u8>
are different types, so are S<'p>
and S<'q>
,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 toS<'static>
likeS<T>
is toS<u32>
, then'static
would be a type; but then what's the value of that type?
Examples expand by clicking.
A visual overview of types and traits in crates.
u8
u16
f32
bool
char
File
String
Builder
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]
Vec<T>
Vec<T>
f<T>() {}
drop() {}
PI
dbg!
Copy
Deref
type Tgt;
From<T>
From<T>
From<T>
Serialize
Transport
ShowHex
Device
From<u8>
String
Serialize
String
From<u8>
String
From<Port>
Port
From<u8>
From<u16>
Container
Deref
Tgt = u8;
Deref
Tgt = f32;
T
T
T
ShowHex
A walk through the jungle of types, traits, and implementations that (might possibly) exist in your application.
How to get B
when you have A
?
fn f(x: A) -> B {
// How can you obtain B from A?
}
Method | Explanation |
---|---|
Identity | Trivial case, B is exactly A . |
Computation | Create and manipulate instance of B by writing code transforming data. |
Casts | On-demand conversion between types where caution is advised. |
Coercions | Automatic conversion within 'weakening ruleset'.1 |
Subtyping | Automatic conversion within 'same-layout-different-lifetimes ruleset'.1 |
1 While both convert A
to B
, coercions generally link to an unrelated B
(a type "one could reasonably expect to have different methods"),
while subtyping links to a B
differing only in lifetimes.
fn f(x: A) -> B {
x.into()
}
Bread and butter way to get B
from A
. Some traits provide canonical, user-computable type relations:
Trait | Example | Trait implies ... |
---|---|---|
impl From<A> for B {} | a.into() | Obvious, always-valid relation. |
impl TryFrom<A> for B {} | a.try_into()? | Obvious, sometimes-valid relation. |
impl Deref for A {} | *a | A is smart pointer carrying B ; also enables coercions. |
impl AsRef<B> for A {} | a.as_ref() | A can be viewed as B . |
impl AsMut<B> for A {} | a.as_mut() | A can be mutably viewed as B . |
impl Borrow<B> for A {} | a.borrow() | A has borrowed analog B (behaving same under Eq , ...). |
impl ToOwned for A { ... } | a.to_owned() | A has owned analog B . |
fn f(x: A) -> B {
x as B
}
Convert types with keyword as
if conversion relatively obvious but might cause issues. NOM
A | B | Example | Explanation |
---|---|---|---|
Ptr | Ptr | device_ptr as *const u8 | If *A , *B are Sized . |
Ptr | Integer | device_ptr as usize | |
Integer | Ptr | my_usize as *const Device | |
Number | Number | my_u8 as u16 | Often surprising behavior. ↑ |
enum w/o fields | Integer | E::A as u8 | |
bool | Integer | true as u8 | |
char | Integer | 'A' as u8 | |
&[T; N] | *const T | my_ref as *const u8 | |
fn(...) | Ptr | f as *const u8 | If Ptr is Sized . |
fn(...) | Integer | f as usize |
Where Ptr
, Integer
, Number
are just used for brevity and actually mean:
Ptr
any *const T
or *mut T
;Integer
any countable u8
... i128
;Number
any Integer
, f32
, f64
.Opinion 💬 — Casts, esp.
Number
-Number
, can easily go wrong. If you are concerned with correctness, consider more explicit methods instead.
fn f(x: A) -> B {
x
}
Automatically weaken type A
to B
; types can be substantially1 different. NOM
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 . |
|x| x + x | fn(u8) -> u8 | Non-capturing closure, to equivalent fn pointer. |
1 Substantially meaning one can regularly expect a coercion result B
to be an entirely different type (i.e., have entirely different methods) than the original type A
.
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 🔗 circumstances.fn f(x: A) -> B {
x
}
Automatically converts A
to B
for types only differing in lifetimes NOM - subtyping examples:
A(subtype) | B(supertype) | Explanation |
---|---|---|
&'static u8 | &'a u8 | Valid, forever-pointer is also transient-pointer. |
&'a u8 | &'static u8 | 🛑 Invalid, transient should not be forever. |
&'a &'b u8 | &'a &'b u8 | Valid, same thing. But now things get interesting. Read on. |
&'a &'static u8 | &'a &'b u8 | Valid, &'static u8 is also &'b u8 ; covariant inside & . |
&'a mut &'static u8 | &'a mut &'b u8 | 🛑 Invalid and surprising; invariant inside &mut . |
Box<&'static u8> | Box<&'a u8> | Valid, Box with forever is also box with transient; covariant. |
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). |
fn f(x: A) -> B {
x
}
Automatically converts A
to B
for types only differing in lifetimes NOM - subtyping variance rules:
'a
that outlives a shorter 'b
is a subtype of 'b
.'static
is subtype of all other lifetimes 'a
.&'a T
) are subtypes of each other the following variance table is used:Construct1 | 'a | T | U |
---|---|---|---|
&'a T | covariant | covariant | |
&'a mut T | covariant | invariant | |
Box<T> | covariant | ||
Cell<T> | invariant | ||
fn(T) -> U | contravariant | covariant | |
*const T | covariant | ||
*mut T | invariant |
Covariant means if A
is subtype of B
, then T[A]
is subtype of T[B]
.
Contravariant means if A
is subtype of B
, then T[B]
is subtype of T[A]
.
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 ofu16
), and aBox<u32>
would never be sub- or supertype of anything. However, generally aBox<A>
, can be subtype ofBox<B>
(via covariance) ifA
is a subtype ofB
, which can only happen ifA
andB
are 'sort of the same type that only differed in lifetimes', e.g.,A
being&'static u32
andB
being&'a u32
.
If you are used to programming Java or C, consider these.
Idiom | Code |
---|---|
Think in Expressions | x = if x { a } else { b }; |
x = loop { break 5 }; | |
fn f() -> u32 { 0 } | |
Think in Iterators | (1..10).map(f).collect() |
names.iter().filter(|x| x.starts_with("A")) | |
Handle Absence with ? | x = try_something()?; |
get_option()?.run()? | |
Use Strong Types | enum E { Invalid, Valid { ... } } over ERROR_INVALID = -1 |
enum E { Visible, Hidden } over visible: bool | |
struct Charge(f32) over f32 | |
Provide Builders | Car::new("Model T").hp(20).build(); |
Split Implementations | Generic types S<T> can have a separate impl per T . |
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. |
Tooling | With clippy you can improve your code quality. |
Formatting with rustfmt helps others to read your code. | |
Add unit tests BK (#[test] ) to ensure your code works. | |
Add doc tests BK (``` my_api::f() ``` ) to ensure docs match code. | |
Documentation | Annotate your APIs with doc comments that can show up on docs.rs. |
Don't forget to include a summary sentence and the Examples heading. | |
If applicable: Panics, Errors, Safety, Abort and Undefined Behavior. |
🔥 We highly recommend you also follow the API Guidelines (Checklist) for any shared project! 🔥
If you are familiar with async / await in C# or TypeScript, here are some things to keep in mind:
Construct | Explanation |
---|---|
async | Anything declared async always returns an impl Future<Output=_> . STD |
async fn f() {} | Function f returns an impl Future<Output=()> . |
async fn f() -> S {} | Function f returns an impl Future<Output=S> . |
async { x } | Transforms { x } into an impl Future<Output=X> . |
let sm = f(); | Calling f() that is async will not execute f , but produce state machine sm . 1 2 |
sm = async { g() }; | Likewise, does not execute the { g() } block; produces state machine. |
runtime.block_on(sm); | Outside an async {} , schedules sm to actually run. Would execute g() . 3 4 |
sm.await | Inside an async {} , run sm until complete. Yield to runtime if sm not ready. |
1 Technically async
transforms following code into anonymous, compiler-generated state machine type; f()
instantiates that machine.
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., async-std or tokio 0.2+. Also, more helpers in futures crate.
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:
sm
/ Future
dropped.Simplified diagram for code written inside an async
block :
consecutive_code(); consecutive_code(); consecutive_code();
START --------------------> x.await --------------------> y.await --------------------> READY
// ^ ^ ^ Future<Output=X> ready -^
// Invoked via runtime | |
// or an external .await | This might resume on another thread (next best available),
// | or NOT AT ALL if Future was dropped.
// |
// Execute `x`. If ready: just continue execution; if not, return
// this thread to runtime.
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.
There is a subtrait relationship Fn
: FnMut
: FnOnce
. That means a closure that
implements Fn
STD also implements FnMut
and FnOnce
. Likewise a closure
that implements FnMut
STD also implements FnOnce
. STD
From a call site perspective that means:
Signature | Function g can call … | Function g accepts … |
---|---|---|
g<F: FnOnce()>(f: F) | … f() once. | Fn , FnMut , FnOnce |
g<F: FnMut()>(mut f: F) | … f() multiple times. | Fn , FnMut |
g<F: Fn()>(f: F) | … f() multiple times. | Fn |
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:
Closure | Implements* | Comment |
---|---|---|
|| { moved_s; } | FnOnce | Caller must give up ownership of moved_s . |
|| { &mut s; } | FnOnce , FnMut | Allows g() to change caller's local state s . |
|| { &s; } | FnOnce , FnMut , Fn | May not mutate state; but can share and reuse s . |
* Rust prefers capturing by reference
(resulting in the most "compatible" Fn
closures from a caller perspective), but can be
forced to capture its environment by copy or move via the
move || {}
syntax.
That gives the following advantages and disadvantages:
Requiring | Advantage | Disadvantage |
---|---|---|
F: FnOnce | Easy to satisfy as caller. | Single use only, g() may call f() just once. |
F: FnMut | Allows g() to change caller state. | Caller may not reuse captures during g() . |
F: Fn | Many can exist at same time. | Hardest to produce for caller. |
Unsafe leads to unsound. Unsound leads to undefined. Undefined leads to the dark side of the force.
Unsafe Code
unsafe
has special permissions, e.g., to deref raw pointers, or invoke other unsafe
functions.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 (UB)
unsafe
code implies special promises to the compiler (it wouldn't need be unsafe
otherwise).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
unsafe
code that may invoke UB on its own accord by violating above-mentioned promises.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.
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.
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 |
🔴 Adding new fields to a variant. |
Traits |
---|
🔴 Adding a non-defaulted item, breaks all existing impl T for S {} . |
🔴 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., <T> to <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. |
These are other great guides and tables.
Cheat Sheets | Description |
---|---|
Rust Learning⭐ | Probably the best collection of links about learning Rust. |
Functional Jargon in Rust | A collection of functional programming jargon explained in Rust. |
Periodic Table of Types | How various types and references correlate. |
Futures | How to construct and work with futures. |
Rust Iterator Cheat Sheet | Summary of iterator-related methods from std::iter and itertools . |
Type-Based Rust Cheat Sheet | Lists common types and how they convert. |
All major Rust books developed by the community.
Books ️📚 | Description |
---|---|
The Rust Programming Language | Standard introduction to Rust, start here if you are new. |
API Guidelines | How to write idiomatic and re-usable Rust. |
Asynchronous Programming 🚧 | Explains async code, Futures , ... |
Design Patterns | Idioms, Patterns, Anti-Patterns. |
Edition Guide | Working with Rust 2015, Rust 2018, and beyond. |
Guide to Rustc Development | Explains how the compiler works internally. |
Little Book of Rust Macros | Community's collective knowledge of Rust macros. |
Reference 🚧 | Reference of the Rust language. |
RFC Book | Look up accepted RFCs and how they change the language. |
Performance Book | Techniques to improve the speed and memory usage. |
Rust Cookbook | Collection of simple examples that demonstrate good practices. |
Rust in Easy English | Explains concepts in simplified English, good alternative start. |
Rust for the Polyglot Programmer | A guide for the experienced programmer. |
Rustdoc Book | Tips how to customize cargo doc and rustdoc . |
Rustonomicon | Dark Arts of Advanced and Unsafe Rust Programming. |
Unsafe Code Guidelines 🚧 | Concise information about writing unsafe code. |
Unstable Book | Information about unstable items, e.g, #![feature(...)] . |
The Cargo Book | How to use cargo and write Cargo.toml . |
The CLI Book | Information about creating CLI tools. |
The Embedded Book | Working with embedded and #![no_std] devices. |
The Embedonomicon | First #![no_std] from scratch on a Cortex-M. |
The WebAssembly Book | Working with the web and producing .wasm files. |
The wasm-bindgen Guide | How to bind Rust and JavaScript APIs in particular. |
For more inofficial books see Little Book of Rust Books.
Comprehensive lookup tables for common components.
Tables 📋 | Description |
---|---|
Rust Changelog | See all the things that changed in a particular version. |
Rust Forge | Lists release train and links for people working on the compiler. |
Rust Platform Support | All supported platforms and their Tier. |
Rust Component History | Check nightly status of various Rust tools for a platform. |
ALL the Clippy Lints | All the clippy lints you might be interested in. |
Configuring Rustfmt | All rustfmt options you can use in .rustfmt.toml . |
Compiler Error Index | Ever wondered what E0404 means? |
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).