Fork me on GitHub

Rust 语言备忘清单 2022-03-02

参考书: Rust 程序设计语言 BK (中文), 通过例子学 Rust EX(中文), 标准库文档 STD, Rust 死灵书 NOM(中文), Rust 参考手册 REF(中文).

可点击的符号

BK Rust 程序设计语言
EX 通过例子学 Rust
STD 标准库文档
NOM Rust 死灵书
REF Rust 参考手册
RFC 官方 RFC 文档。
🔗 互联网。
在这个页面上,上面
在这个页面上,下面

其它符号

🗑️ 很大程度上已废弃
'18 最低版本要求。
🚧 需要Rust nightly(或不完整)。
🛑 故意的错误示例陷阱
🝖 略显深奥,很少使用或高级。
🔥 具有出色实用性的东西。
? 缺少好的链接或说明。
💬 固执己见的

字体连字 (..=, =>) 展开所有内容? 夜间模式 💡

语言结构

Behind the Scenes

数据布局

附录

标准库

工具

使用类型

编码指南

你好,Rust!url

如果你是 Rust 新手,或者你想试点什么,可以在下面尝试运行一下:

fn main() {
    println!("Hello, world!");
}
服务由 play.rust-lang.org 🔗 提供

Rust 做得非常好

你可能会遇到的点

  • 陡峭的学习曲1;编译器强制执行(特别是内存)规则,这在其他地方是“最佳实践”。
  • 在某些领域、目标平台(尤其是嵌入式)、IDE 功能中缺少 Rust 原生库1
  • 与其他语言中的“类似”代码相比,编译时间更长1
  • 没有正式的语言规范,可能会阻止在某些领域(航空、医疗等)合法使用。
  • Careless (use of unsafe in) libraries can secretly break safety guarantees.

1 Compare Rust Survey.

下载

  • rustup.rs 获取安装程序(强烈推荐用于任何平台)

IDE 编辑器

模块化初学者资源

此外,have a look at the usual suspects. BK EX STD

Opinion 💬 — If you have never seen or used any Rust it might be good to visit one of the links above before continuing; the next chapter might feel a bit terse otherwise.

数据结构url

数据类型和内存位置由关键字定义。

示例说明
struct S {}定义包含命名字段的结构体BK EX STD REF
     struct S { x: T }定义包含 T 类型命名字段 x 的结构体。
     struct S(T);定义 T 类型数字字段 .0 的“元组”结构体。
     struct S;定义一个零大小NOM的单元结构体。不占用任何空间,并进行了优化。
enum E {}定义枚举BK EX REF c. 数字数据类型, 标签联合.
     enum E { A, B(), C {} }定义变体枚举;可以是单元- A,元组- B() 或则结构体风格的 C{}
     enum E { A = 1 }如果所有变体都是单元值,则允许判别式值,例如用于 FFI。
union U {}不安全的 C 风格联合体REF,用于兼容 FFI。 🝖
static X: T = T();'static 生命周期的全局变量 BK EX REF,内存位置独立。
const X: T = T();定义常量BK EX REF,使用时会临时复制一份。
let x: T;在栈1上分配 T 大小的字节并命名为 x。 一旦分配不可修改。
let mut x: T;类似于 let,但允许修改BK EX以及可变借用2
     x = y;y 移动到 x,如果 T 不能 CopySTDy 将不再可用,否则 y 会复制一份。

1 绑定变量 BK EX REF生存在栈上的同步代码。在async{}中,这些变量将成为异步状态机的一部分,最终驻留在堆上。
2 严密地来说_可变_和_不可变_并不准确。不可变绑定或共享引用可能仍然包含 Cell STD,从而提供_内部可变性_。

 

下面列出了如何构建和访问数据结构;以及一些_神奇的_类型。

示例说明
S { x: y }构建 struct S {} 或者 use'ed enum E::S {} 字段 x 设置为 y
S { x }同上,但字段 x 会被设置为局部变量 x
S { ..s }s 填充剩余字段,常配合 Default 一起使用。
S { 0: x }类似于下面的 S(x),但是用结构体语法设置字段 .0
S(x)构建 struct S(T) 或者 use'ed enum E::S() 其中字段 .0 设置为 x
S如果 S 是单元 struct S;或以 S 为值构建 use'ed enum E::S
E::C { x: y }构建枚举变体 C。 上面的其他方法依然有效。
()空元组,既是字面量也是类型,又称单元STD
(x)括号表达式。
(x,)单元素元组表达式。EX STD REF
(S,)单元素元组类型。
[S]未指明长度的的数组类型,如切片EX STD REF 不能生存在栈上。*
[S; n]元素类型为 S,固定长度为 n数组类型 EX STD
[x; n]nx 副本构建的数组实例。REF
[x, y]由给定元素 xy构成的数据实例。
x[0]组合的索引,返回结果是 usize 类型。可重载 Index, IndexMut
     x[..]同样,通过范围(这里是_全部范围_),下面的 x[a..b], x[a..=b], ...c 也一样。
a..b构建左闭右开区间 STD REF,例如 1..3 表示 1, 2
..b无起点到右开区间 STD
a..=b全闭合区间, STD 1..=3 表示 1, 2, 3
..=b无起点到右闭合区间 STD
..全包含区间STD,通常表示_整个组合_。
s.x命名 字段访问REF,如果 x 不是 s 的一部分的话则会尝试 Deref
s.0数字字段访问,用于元组类型 S(T)

* 目前RFC,待处理的跟踪问题

引用 & 指针url

为非所有者内存赋予访问权限。另请参见 泛型 & 约束部分。

示例说明
&SShared reference BK STD NOM REF (space for holding any &s).
     &[S]Special slice reference that contains (address, length).
     &strSpecial string slice reference that contains (address, length).
     &mut SExclusive reference to allow mutability (also &mut [S], &mut dyn S, …).
     &dyn TSpecial trait object BK reference that contains (address, vtable).
&sShared borrow BK EX STD (e.g., address, len, vtable, … of this s, like 0x1234).
     &mut sExclusive borrow that allows mutability. EX
*const SImmutable raw pointer type BK STD REF w/o memory safety.
     *mut SMutable raw pointer type w/o memory safety.
     &raw const sCreate raw pointer w/o going through reference; c. ptr:addr_of!() STD 🚧 🝖
     &raw mut sSame, but mutable. 🚧 Raw ptrs. are needed for unaligned, packed fields. 🝖
ref sBind 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.
*rDereference 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.
'aA lifetime parameter, BK EX NOM REF duration of a flow in static analysis.
     &'a SOnly accepts an address holding an s; addr. existing 'a or longer.
     &'a mut SSame, 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.
'staticSpecial lifetime lasting the entire program execution.

函数 & 行为url

定义代码单元及其抽象。

示例说明
trait T {}Define a trait; BK EX REF common behavior others can implement.
trait T : R {}T is subtrait of supertrait 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 {}.
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() -> SFunction pointers, BK STD REF memory holding address of a callable.
Fn() -> SCallable 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 + xSame, 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).
unsafeIf 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.

控制流程url

在函数中控制执行。

示例说明
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.
breakBreak expression REF to exit a loop.
     break xSame, but make x value of the loop expression (only in actual loop).
     break 'labelExit not only this loop, but the enclosing one marked with 'label.
     break 'label xSame, 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 'labelSame 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.awaitOnly works inside async. Yield flow until Future STD or Stream x ready. REF '18
return xEarly 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.

代码组织url

将项目分割成更小的单元并最大限度地减少依赖关系。

示例说明
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::bNamespace path EX REF to element b within a (mod, enum, …).
     ::bSearch b relative to crate root. 🗑️
     crate::bSearch b relative to crate root. '18
     self::bSearch b relative to current module.
     super::bSearch 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. 🔗
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) TVisible at most1 in current crate.
     pub(super) TVisible at most1 in parent.
     pub(self) TVisible at most1 in current module (default, same as no pub).
     pub(in a::b) TVisible 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 子模块中的项目始终可以访问任何项目,无论是否 pub

类型别名和转换url

类型名称的简写,以及转为其他类型的方法。

示例说明
type T = S;Create a type alias, BK REF i.e., another name for S.
SelfType alias for implementing type, REF e.g. fn new() -> Self.
selfMethod subject in fn f(self) {}, same as fn f(self: Self) {}.
     &selfSame, but refers to self as borrowed, same as f(self: &Self)
     &mut selfSame, 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 TDisambiguate BK REF type S as trait T, e.g., <S as T>::f().
S as RIn use of symbol, import S as R, e.g., use a::S as R.
x as u32Primitive cast, EX REF may truncate and be a bit surprising. 1 NOM

1 关于在类型之间转换的所有方法,请参见下面的类型转换

宏 & 属性url

实际编译前的代码预展开。

示例说明
m!() BK STD REF 咒语,也作 m!{}m![](取决于宏本身)
#[attr]外部属性EX REF,注解接下来的内容。
#![attr]内部属性,注解_上部_,周边的内容。
 
内部宏说明
$x:tyMacro capture (here a type); see tooling directives for details.
$xMacro 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: <<.

模式匹配url

函数参数、matchlet 表达式中的构造。

示例说明
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). 🝖

* 展开后是 match get() { Some(x) => {}, _ => () }.

 

match 表达式的模式匹配分支。左列的分支也可用于 let 表达式。

Within Match Arm说明
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.

泛型 & 约束url

泛型与类型构造函数、特征和函数相结合,为用户提供了更大的灵活性。

示例说明
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: SIndependent trait bounds (here one for T and one for P).
     T: R, SCompile error, 🛑 you probably want compound bound R + S below.
     T: R + SCompound trait bound, BK EX T must fulfill R and S.
     T: R + 'aSame, but w. lifetime. T must fulfill R, if T has lifetimes, must outlive 'a.
     T: ?SizedOpt out of a pre-defined trait bound, here Sized. ?
     T: 'aType lifetime bound; EX if T has references, they must outlive 'a.
     T: 'staticSame; does esp. not mean value t will 🛑 live 'static, only that it could.
     'b: 'aLifetime '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: RAlmost 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 TExistential 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).

Higher-Ranked Items 🝖url

_真实的_类型和特征,抽象于某物之上,通常是生命周期。

示例说明
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说明
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.

字符串 & 字符url

Rust 提供了多种创建文字值的方法。

示例说明
"..."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.

注释url

调试人员讨厌他。用这个奇怪的技巧来避免错误。

示例说明
///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.

其他url

这些小技巧不属于其他分类但最好了解一下。

示例说明
!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!
_xVariable binding explicitly marked as unused.
1_234_567Numeric separator for visual clarity.
1_u8Type specifier for numeric literals EX REF (also i8, u16, …).
0xBEEF, 0o777, 0b1001Hexadecimal (0x), octal (0o) and binary (0b) integer literals.
r#fooA raw identifier BK EX for edition compatibility. 🝖
x;Statement REF terminator, c. expressions EX REF

通用运算符url

Rust 支持大部分其他语言也有的通用操作符(+, *, %, =, ==, …),包含运算符重载STD。因为这在 Rust 里没什么太大差别所以这里不列出来了。


Behind the Scenesurl

强烈推荐那些可能会对你的思想造成可怕影响的神秘知识。

机器抽象url

CC++一样,Rust 也是基于机器抽象的。

Rust CPU
🛑 不那么准确。
Rust 机器抽象 CPU
更正。
 

机器抽象

  • 不是运行时,也没有任何运行时的开销,而是_计算模型的抽象_,
  • 包含诸如内存区域(堆栈,...),执行语义等概念,
  • knows and sees things your CPU might not care about,
  • 在程序员和机器之间形成契约,
  • 并且利用上述所有内容进行优化

Things people may incorrectly assume they should get away with if Rust targeted CPU directly, and more correct counterparts: 如果 Rust 直接针对 CPU,人们可能会错误地认为他们可以_肆无忌惮_,而_更正确_的做法是:

 
Without AMWith 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.

语法糖url

如果有什么东西让你觉得,“不该能用的啊”,那可能就是这里的原因。

名称说明
Coercions NOMWeakens 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 STDAutomatic import of basic items, e.g., Option, drop, ...
ReborrowSince x: &mut T can't be copied; moves new &mut *x instead.
Lifetime Elision BK NOM REFAutomatically annotates f(x: &T) to f<'a>(x: &'a T).
Method Resolution REFDerefs or borrow x until x.f() works.
Match Ergonomics RFCRepeatedly dereferences scrutinee and adds ref and ref mut to bindings.
Rvalue Static Promotion RFCMakes references to constants 'static, e.g., &42, &None, &mut [].
 

意见 💬 — 尽管上面的特性将使简化了开发工作,但它们也会对理解当前发生了什么造成可能的妨碍。如果你对 Rust 还不太了解,想要搞明白到底发生了什么,你应该更详细地阅读相关资料。

内存 & 生命周期url

为什么移动、引用和生命周期是这样的。

应用程序内存 S(1) 应用程序内存
  • 应用程序内存只是低级别的字节数组。
  • 操作环境通常分为以下几个部分:
    • 栈(Stack)(小,低开销内存1,大多数_变量_都放在这里),
    • 堆(Heap)(大而灵活的内存,但总是通过栈代理处理,如 Box<T>),
    • 静态(static)(最常用作 &strstr 部分来使用),
    • 代码(code)(函数的位码所在的位置)。
  • 最棘手的部分与栈如何演变有关,这是我们的重点

1 对于固定大小的值,栈很容易管理:在需要时多占用几个字符,离开后丢弃。然而,给出指向这些_瞬态_位置的指针构成了_生命周期_存在的本质;并且是本章其余部分的主题。

变量 S(1) S(1) 变量
let t = S(1);
  • Reserves memory location with name t of type S and the value S(1) stored inside.
  • If declared with let that location lives on stack. 1
  • Note the linguistic ambiguity,2 in the term variable, it can mean the:
    1. name of the location in the source file ("rename that variable"),
    2. location in a compiled app, 0x7 ("tell me the address of that variable"),
    3. value contained within, S(1) ("increment that variable").
  • Specifically towards the compiler t can mean location of t, here 0x7, and value within t, here S(1).

1 Compare above, true for fully synchronous code, but async stack frame might placed it on heap via runtime.

Move Semantics S(1) Moves
let a = t;
  • This will move value within t to location of a, or copy it, if S is Copy.
  • After move location t is invalid and cannot be read anymore.
    • Technically the bits at that location are not really empty, but undefined.
    • If you still had access to t (via unsafe) they might still look like valid S, but any attempt to use them as valid S is undefined behavior.
  • We do not cover Copy types explicitly here. They change the rules a bit, but not much:
    • They won't be dropped.
    • They never leave behind an 'empty' variable location.
Type Safety M { ... } Type Safety
let c: S = M::new();
  • The type of a variable serves multiple important purposes, it:
    1. dictates how the underlying bits are to be interpreted,
    2. allows only well-defined operations on these bits
    3. prevents random other values or bits from being written to that location.
  • Here assignment fails to compile since the bytes of M::new() cannot be converted to form of type S.
  • Conversions between types will always fail in general, unless explicit rule allows it (coercion, cast, …).
Scope & Drop S(1) C(2) S(2) S(3) Scope & Drop
{
    let mut c = S(2);
    c = S(3);  // <- Drop called on `c` before assignment.
    let t = S(1);
    let a = t;
}   // <- Scope of `a`, `t`, `c` ends here, drop called on `a`, `c`.
  • Once the 'name' of a non-vacated variable goes out of (drop-)scope, the contained value is dropped.
    • Rule of thumb: execution reaches point where name of variable leaves {}-block it was defined in
    • In detail more tricky, esp. temporaries, …
  • Drop also invoked when new value assigned to existing variable location.
  • In that case Drop::drop() is called on the location of that value.
    • In the example above drop() is called on a, twice on c, but not on t.
  • Most non-Copy values get dropped most of the time; exceptions include mem::forget(), Rc cycles, abort().
Stack Frame S(1) Function Boundaries
fn f(x: S) { ... }

let a = S(1); // <- We are here
f(a);
  • When a function is called, memory for parameters (and return values) are reserved on stack.1
  • Here before f is invoked value in a is moved to 'agreed upon' location on stack, and during f works like 'local variable' x.

1 Actual location depends on calling convention, might practically not end up on stack at all, but that doesn't change mental model.

S(1) Nested Functions
fn f(x: S) {
    if once() { f(x) } // <- We are here (before recursion)
}

let a = S(1);
f(a);
  • Recursively calling functions, or calling other functions, likewise extends the stack frame.
  • Nesting too many invocations (esp. via unbounded recursion) will cause stack to grow, and eventually to overflow, terminating the app.
Validity of Variables S(1) M { } Repurposing Memory
fn f(x: S) {
    if once() { f(x) }
    let m = M::new() // <- We are here (after recursion)
}

let a = S(1);
f(a);
  • Stack that previously held a certain type will be repurposed across (even within) functions.
  • Here, recursing on f produced second x, which after recursion was partially reused for m.

Key take away so far, there are multiple ways how memory locations that previously held a valid value of a certain type stopped doing so in the meantime. As we will see shortly, this has implications for pointers.

Reference Types   S(1) 0x3 References as Pointers
let a = S(1);
let r: &S = &a;
  • A reference type such as &S or &mut S can hold the location of some s.
  • Here type &S, bound as name r, holds location of variable a (0x3), that must be type S, obtained via &a.
  • If you think of variable c as specific location, reference r is a switchboard for locations.
  • The type of the reference, like all other types, can often be inferred, so we might omit it from now on:
    let r: &S = &a;
    let r = &a;
    
(Mutable) References   S(2) 0x3 S(1) Access to Non-Owned Memory
let mut a = S(1);
let r = &mut a;
let d = r.clone();  // Valid to clone (or copy) from r-target.
*r = S(2);          // Valid to set new S value to r-target.
  • References can read from (&S) and also write to (&mut S) location they point to.
  • The dereference *r means to neither use the location of or value within r, but the location r points to.
  • In example above, clone d is created from *r, and S(2) written to *r.
    • Method Clone::clone(&T) expects a reference itself, which is why we can use r, not *r.
    • On assignment *r = ... old value in location also dropped (not shown above).
  S(2) 0x3 M { x } References Guard Referents
let mut a = ...;
let r = &mut a;
let d = *r;       // Invalid to move out value, `a` would be empty.
*r = M::new();    // invalid to store non S value, doesn't make sense.
  • While bindings guarantee to always hold valid data, references guarantee to always point to valid data.
  • Esp. &mut T must provide same guarantees as variables, and some more as they can't dissolve the target:
    • They do not allow writing invalid data.
    • They do not allow moving out data (would leave target empty w/o owner knowing).
  C(2) 0x3 Raw Pointers
let p: *const S = questionable_origin();
  • In contrast to references, pointers come with almost no guarantees.
  • They may point to invalid or non-existent data.
  • Dereferencing them is unsafe, and treating an invalid *p as if it were valid is undefined behavior.
C(2) 0x3 "Lifetime" of Things
  • Every entity in a program has some (temporal / spatial) room where it is relevant, i.e., alive.
  • Loosely speaking, this alive time can be1
    1. the LOC (lines of code) where an item is available (e.g., a module name).
    2. the LOC between when a location is initialized with a value, and when the location is abandoned.
    3. the LOC between when a location is first used in a certain way, and when that usage stops.
    4. the LOC (or actual time) between when a value is created, and when that value is dropped.
  • Within the rest of this section, we will refer to the items above as the:
    1. scope of that item, irrelevant here.
    2. scope of that variable or location.
    3. lifetime2 of that usage.
    4. lifetime of that value, might be useful when discussing open file descriptors, but also irrelevant here.
  • Likewise, lifetime parameters in code, e.g., r: &'a S, are
    • concerned with LOC any location r points to needs to be accessible or locked;
    • unrelated to the 'existence time' (as LOC) of r itself (well, it needs to exist shorter, that's it).
  • &'static S means address must be valid during all lines of code.

1 There is sometimes ambiguity in the docs differentiating the various scopes and lifetimes. We try to be pragmatic here, but suggestions are welcome.

2 Live lines might have been a more appropriate term ...

  S(0) S(1) S(2) 0xa Meaning of r: &'c S
  • Assume you got a r: &'c S from somewhere it means:
    • r holds an address of some S,
    • any address r points to must and will exist for at least 'c,
    • the variable r itself cannot live longer than 'c.
  S(0) S(3) S(2) 0x6 Typelikeness of Lifetimes
{
    let b = S(3);
    {
        let c = S(2);
        let r: &'c S = &c;      // Does not quite work since we can't name lifetimes of local
        {                       // variables in a function body, but very same principle applies
            let a = S(0);       // to functions next page.

            r = &a;             // Location of `a` does not live sufficient many lines -> not ok.
            r = &b;             // Location of `b` lives all lines of `c` and more -> ok.
        }
    }
}
  • Assume you got a mut r: &mut 'c S from somewhere.
    • That is, a mutable location that can hold a mutable reference.
  • As mentioned, that reference must guard the targeted memory.
  • However, the 'c part, like a type, also guards what is allowed into r.
  • Here assiging &b (0x6) to r is valid, but &a (0x3) would not, as only &b lives equal or longer than &c.
  S(0)   S(2) 0x6 S(4) Borrowed State
let mut b = S(0);
let r = &mut b;

b = S(4);   // Will fail since `b` in borrowed state.

print_byte(r);
  • Once the address of a variable is taken via &b or &mut b the variable is marked as borrowed.
  • While borrowed, the content of the address cannot be modified anymore via original binding b.
  • Once address taken via &b or &mut b stops being used (in terms of LOC) original binding b works again.
S(0) S(1) S(2) ? 0x6 0xa Function Parameters
fn f(x: &S, y:&S) -> &u8 { ... }

let b = S(1);
let c = S(2);

let r = f(&b, &c);
  • When calling functions that take and return references two interesting things happen:
    • The used local variables are placed in a borrowed state,
    • But it is during compilation unknown which address will be returned.
S(0) S(1) S(2) ? 0x6 0xa Problem of 'Borrowed' Propagation
let b = S(1);
let c = S(2);

let r = f(&b, &c);

let a = b;   // Are we allowed to do this?
let a = c;   // Which one is _really_ borrowed?

print_byte(r);
  • Since f can return only one address, not in all cases b and c need to stay locked.
  • In many cases we can get quality-of-life improvements.
    • Notably, when we know one parameter couldn't have been used in return value anymore.
  S(1) S(1) S(2) y + _ 0x6 0xa Lifetimes Propagate Borrowed State
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);
  • Lifetime parameters in signatures, like 'c above, solve that problem.
  • Their primary purpose is:
    • outside the function, to explain based on which input address an output address could be generated,
    • within the function, to guarantee only addresses that live at least 'c are assigned.
  • The actual lifetimes 'b, 'c are transparently picked by the compiler at call site, based on the borrowed variables the developer gave.
  • They are not equal to the scope (which would be LOC from initialization to destruction) of b or c, but only a minimal subset of their scope called lifetime, that is, a minmal set of LOC based on how long b and c need to be borrowed to perform this call and use the obtained result.
  • In some cases, like if f had 'c: 'b instead, we still couldn't distinguish and both needed to stay locked.
S(2) S(1) S(2) y + 1 0x6 0xa Unlocking
let mut c = S(2);

let r = f(&c);
let s = r;
                    // <- Not here, `s` prolongs locking of `c`.

print_byte(s);

let a = c;          // <- But here, no more use of `r` or `s`.


  • A variable location is unlocked again once the last use of any reference that may point to it ends.

↕️ Examples expand by clicking.

 

数据类型url

通用数据类型的内存表示。

基本类型url

语言核心内建的必要类型。

数字类型 REFurl

u8, i8 u16, i16 u32, i32 u64, i64 u128, i128 f32 f64 usize, isize 与平台的 ptr 一致。
 
类型最大值
u8255
u1665_535
u324_294_967_295
u6418_446_744_073_709_551_615
u128340_282_366_920_938_463_463_374_607_431_768_211_455
usizeDepending on platform pointer size, same as u16, u32, or u64.
类型最大值
i8127
i1632_767
i322_147_483_647
i649_223_372_036_854_775_807
i128170_141_183_460_469_231_731_687_303_715_884_105_727
isizeDepending on platform pointer size, same as i16, i32, or i64.
 
类型最小值
i8-128
i16-32_768
i32-2_147_483_648
i64-9_223_372_036_854_775_808
i128-170_141_183_460_469_231_731_687_303_715_884_105_728
isizeDepending on platform pointer size, same as i16, i32, or i64.

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
 

说明:

f32S (1)E (8)F (23)
规格化数±1 to 254任意±(1.F)2 * 2E-127
非规格化数±0非零±(0.F)2 * 2-126
±00±0
无穷大±2550±∞
NaN±255非零NaN
 

同样,对于 f64 类型,这将类似于:

f64S (1)E (11)F (52)
规格化数±1 to 2046任意±(1.F)2 * 2E-1023
非规格化数±0非零±(0.F)2 * 2-1022
±00±0
无穷大±20470±∞
NaN±2047非零NaN
* 浮点类型遵循 IEEE 754-2008 规范,并取决于平台大小端序。
Cast1GivesNote
3.9_f32 as u83Truncates, consider x.round() first.
314_f32 as u8255采用最接近的可用数字。
f32::INFINITY as u8255Same, treats INFINITY as really large number.
f32::NAN as u80-
_314 as u858截断多余的位。
_200 as i856-
_257 as i8-1-
Operation1GivesNote
200_u8 / 0_u8Compile error.-
200_u8 / _0 dPanic.Regular math may panic; here: division by zero.
200_u8 / _0 rPanic.Same.
200_u8 + 200_u8Compile error.-
200_u8 + _200 dPanic.Consider checked_, wrapping_, ... instead. STD
200_u8 + _200 r144In release mode this will overflow.
1_u8 / 2_u80Other integer division truncates.
0.8_f32 + 0.1_f320.90000004-
1.0_f32 / 0.0_f32f32::INFINITY-
0.0_f32 / 0.0_f32f32::NAN-
x < f32::NANfalseNAN comparisons always return false.
x > f32::NANfalse-
f32::NAN == f32::NANfalse-

1表达式_100表示可能包含100值的任何内容,例如100_i32,但对编译器是不透明的。
d 调试版本。
r 发布版本。

 

文字类型 REFurl

char 任意 UTF-8 标量。 str ... U T F - 8 ...未指明条目 很少单独见到,常用 &str 代替。
 
类型描述
char总是为 4 字节,且仅包含一个 Unicode 标量值🔗
str未知长度的 u8 数组保证保存 UTF-8 编码的码位
字符描述
let c = 'a';Often a char (unicode scalar) can coincide with your intuition of character.
let c = '❤';It can also hold many Unicode symbols.
let c = '❤️';But not always. Given emoji is two char (see Encoding) and can't 🛑 be held by c.1
c = 0xffff_ffff;Also, chars are not allowed 🛑 to hold arbitrary bit patterns.
1 Fun fact, due to the Zero-width joiner (⨝) what the user perceives as a character can get even more unpredictable: 👨‍👩‍👧 is in fact 5 chars 👨⨝👩⨝👧, and rendering engines are free to either show them fused as one, or separately as three, depending on their abilities.
 
字符串描述
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";

VariantMemory Representation2
s.as_bytes()49 20 e2 9d a4 20 52 75 73 74 3
s.chars()149 00 00 00 20 00 00 00 64 27 00 00 20 00 00 00 52 00 00 00 75 00 00 00 73 00
t.as_bytes()49 20 e2 9d a4 ef b8 8f 20 52 75 73 74 4
t.chars()149 00 00 00 20 00 00 00 64 27 00 00 0f fe 01 00 20 00 00 00 52 00 00 00 75 00
 
1 Result then collected into array and transmuted to bytes.
2 Values given in hex, on x86.
3 Notice how , having Unicode Code Point (U+2764), is represented as 64 27 00 00 inside the char, but got UTF-8 encoded to e2 9d a4 in the str.
4 Also observe how the emoji Red Heart ❤️, is a combination of and the U+FE0F Variation Selector, thus t has a higher char count than s.
 

💬 For what seem to be browser bugs Safari and Edge render the hearts in Footnote 3 and 4 wrong, despite being able to differentiate them correctly in s and t above.

 

自定义类型url

用户定义的基本类型。它实际的内存布局REF取决于表示法REF,还有对齐。

T T Sized T: ?Sized T 也许是 DST [T; n] T T T ... n 次 固定的 n 个元素的数组。 [T] ... T T T ...未指定条目 未知多元素的切片类型。Neither
Sized (不携带 len 信息),
而且大多数情况下都是以&[T]为参照。
struct S; Zero-Sized (A, B, C) A B C or maybe B A C 除非强制表示(例如通过 #[repr(C)]
否则类型布局未指定。
struct S { b: B, c: C } B C 或者也许 C B 编译器还可以添加填充。

还需注意,具有完全相同字段的两种类型 A(X, Y)B(X, Y) 仍然可以具有不同的布局;如果没有表示保证,则决不能使用 transmute()

 

这些合并类型存有其一种子类型的值:

enum E { A, B, C } 标签 A 排他性或 标签 B 排他性或 标签 C 安全地保存 A、B 或 C。
又名“标签联合”,尽管编译器会忽略标签。
union { ... } A 不安全或 B 不安全或 C 不安全地以多种方式解释同一块内存。
结果可能是未定义的。

引用 & 指针url

引用授权了对其他内存空间的安全访问。裸指针则是不安全 unsafe 的访问。 各自的 mut 类型是相同的。

&'a T ptr2/4/8 meta2/4/8 | T 必须定位一些有效 tT
并且任何这样的目标必须 至少存在'a
*const T ptr2/4/8 meta2/4/8 没有任何保证。

元指针url

许多引用和指针类型可以携带一个额外的字段,元数据指针STD。 它可以是目标的元素长度或字节长度,也可以是指向vtable的指针。带有 meta 的指针称为胖指针(fat),否则称为细指针(thin)

&'a T ptr2/4/8 | T 没有大小目标的元(细指针)。 &'a T ptr2/4/8 len2/4/8 | T If T is a DST struct such as
S { x: [u8] } meta field len is
length of dyn. sized content.
&'a [T] ptr2/4/8 len2/4/8 | ... T T ... Regular slice reference (i.e., the
reference type of a slice type [T])
often seen as &[T] if 'a elided.
&'a str ptr2/4/8 len2/4/8 | ... U T F - 8 ... String slice reference (i.e., the
reference type of string type str),
with meta len being byte length.

&'a dyn Trait ptr2/4/8 ptr2/4/8 | T |
*Drop::drop(&mut T)
size
align
*Trait::f(&T, ...)
*Trait::g(&T, ...)
Meta points to vtable, where *Drop::drop(), *Trait::f(), ... are pointers to their respective impl for T.

闭包url

闭包是一个临时函数,定义闭包时,它会自动管理数据捕获REF环境中访问的内容。例如:

move |x| x + y.f() + z Y Z 匿名闭包类型 C1 |x| x + y.f() + z ptr2/4/8 ptr2/4/8 匿名闭包类型 C2 | Y | Z

Also produces anonymous fn such as fc1(C1, X) or fc2(&C2, X). Details depend which FnOnce, FnMut, Fn ... is supported, based on properties of captured types.

Standard Library Typesurl

Rust's standard library combines the above primitive types into useful types with special semantics, e.g.:

UnsafeCell<T> T Magic type allowing
aliased mutability.
Cell<T> T Allows T's
to move in
and out.
RefCell<T> borrowed T Also support dynamic
borrowing of T. Like Cell this
is Send, but not Sync.
AtomicUsize usize2/4/8 Other atomic similarly. Result<T, E> Tag E or Tag T Option<T> Tag or Tag T Tag may be omitted for
certain T, e.g., NonNull.
 

General Purpose Heap Storageurl

Box<T> ptr2/4/8 meta2/4/8 | T For some T stack proxy may carry
meta (e.g., Box<[T]>).
Vec<T> ptr2/4/8 capacity2/4/8 len2/4/8 |
T T ... len
capacity
 

Owned Stringsurl

String ptr2/4/8 capacity2/4/8 len2/4/8 |
U T F - 8 ... len
capacity
Observe how String differs from &str and &[char].
CString ptr2/4/8 len2/4/8 |
A B C ... len ...
NUL-terminated but w/o NUL in middle.
OsString ? Platform Defined |
? ? / ? ?
Encapsulates how operating system
represents strings (e.g., UTF-16 on
Windows).
PathBuf ? OsString |
? ? / ? ?
Encapsulates how operating system
represents paths.
 

Shared Ownershipurl

If the type does not contain a Cell for T, these are often combined with one of the Cell types above to allow shared de-facto mutability.

Rc<T> ptr2/4/8 meta2/4/8
| strng2/4/8 weak2/4/8 T
Share ownership of T in same thread. Needs nested Cell
or RefCellto allow mutation. Is neither Send nor Sync.
Arc<T> ptr2/4/8 meta2/4/8
| strng2/4/8 weak2/4/8 T
Same, but allow sharing between threads IF contained
T itself is Send and Sync.

Mutex<T> / RwLock<T> ptr2/4/8 poison2/4/8 T | lock Needs to be held in Arc to be shared between
threads, always Send and Sync. Consider using
parking_lot instead (faster, no heap usage).

Standard Libraryurl

One-Linersurl

Snippets that are common, but still easy to forget. See Rust Cookbook 🔗 for more.

IntentSnippet
Concatenate strings (any Display that is). 1 '21format!("{x}{y}")
Split by separator pattern. STD 🔗s.split(pattern)
     ... with &strs.split("abc")
     ... with chars.split('/')
     ... with closures.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.

IntentSnippet
Create a new fileFile::create(PATH)?
     Same, via OpenOptionsOpenOptions::new().create(true).write(true).truncate(true).open(PATH)?
IntentSnippet
Macro w. variable argumentsmacro_rules! var_args { ($($args:expr),*) => {{ }} }
     Using args, e.g., calling f multiple times.     $( f($args); )*
IntentSnippet
Cleaner closure captureswants_closure({ let c = outer.clone(); move || use_clone(c) })
Fix inference in 'try' closuresiter.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;

线程安全url

ExamplesSend*!Send
Sync*Most types ... Mutex<T>, Arc<T>1,2MutexGuard<T>1, RwLockReadGuard<T>1
!SyncCell<T>2, RefCell<T>2Rc<T>, &dyn Trait, *const T3, *mut T3

* 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.

迭代器url

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> Iterate over T. &Collection<T> IntoIterator Item = &T; To = Iter<T> Iterate over &T. &mut Collectn<T> IntoIterator Item = &mut T; To = IterMut<T> Iterate over &mut T.

数字转换url

As-correct-as-it-currently-gets number conversions.

↓ Have / Want →u8i128f32 / f64String
u8i128u8::try_from(x)? 1x as f32 3x.to_string()
f32 / f64x as u8 2x as f32x.to_string()
Stringx.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).

字符串转换url

If you want a string of type …

If you have x of type …Use this …
Stringx
CStringx.into_string()?
OsStringx.to_str()?.to_string()
PathBufx.to_str()?.to_string()
Vec<u8> 1String::from_utf8(x)?
&strx.to_string() i
&CStrx.to_str()?.to_string()
&OsStrx.to_str()?.to_string()
&Pathx.to_str()?.to_string()
&[u8] 1String::from_utf8_lossy(x).to_string()
If you have x of type …Use this …
StringCString::new(x)?
CStringx
OsString 2CString::new(x.to_str()?)?
PathBufCString::new(x.to_str()?)?
Vec<u8> 1CString::new(x)?
&strCString::new(x)?
&CStrx.to_owned() i
&OsStr 2CString::new(x.to_os_string().into_string()?)?
&PathCString::new(x.to_str()?)?
&[u8] 1CString::new(Vec::from(x))?
*mut c_char 3unsafe { CString::from_raw(x) }
If you have x of type …Use this …
StringOsString::from(x) i
CStringOsString::from(x.to_str()?)
OsStringx
PathBufx.into_os_string()
Vec<u8> 1?
&strOsString::from(x) i
&CStrOsString::from(x.to_str()?)
&OsStrOsString::from(x) i
&Pathx.as_os_str().to_owned()
&[u8] 1?
If you have x of type …Use this …
StringPathBuf::from(x) i
CStringPathBuf::from(x.to_str()?)
OsStringPathBuf::from(x) i
PathBufx
Vec<u8> 1?
&strPathBuf::from(x) i
&CStrPathBuf::from(x.to_str()?)
&OsStrPathBuf::from(x) i
&PathPathBuf::from(x) i
&[u8] 1?
If you have x of type …Use this …
Stringx.into_bytes()
CStringx.into_bytes()
OsString?
PathBuf?
Vec<u8> 1x
&strVec::from(x.as_bytes())
&CStrVec::from(x.to_bytes_with_nul())
&OsStr?
&Path?
&[u8] 1x.to_vec()
If you have x of type …Use this …
Stringx.as_str()
CStringx.to_str()?
OsStringx.to_str()?
PathBufx.to_str()?
Vec<u8> 1std::str::from_utf8(&x)?
&strx
&CStrx.to_str()?
&OsStrx.to_str()?
&Pathx.to_str()?
&[u8] 1std::str::from_utf8(x)?
If you have x of type …Use this …
StringCString::new(x)?.as_c_str()
CStringx.as_c_str()
OsString 2x.to_str()?
PathBuf?,4
Vec<u8> 1,5CStr::from_bytes_with_nul(&x)?
&str?,4
&CStrx
&OsStr 2?
&Path?
&[u8] 1,5CStr::from_bytes_with_nul(x)?
*const c_char 1unsafe { CStr::from_ptr(x) }
If you have x of type …Use this …
StringOsStr::new(&x)
CString?
OsStringx.as_os_str()
PathBufx.as_os_str()
Vec<u8> 1?
&strOsStr::new(x)
&CStr?
&OsStrx
&Pathx.as_os_str()
&[u8] 1?
If you have x of type …Use this …
StringPath::new(x) r
CStringPath::new(x.to_str()?)
OsStringPath::new(x.to_str()?) r
PathBufPath::new(x.to_str()?) r
Vec<u8> 1?
&strPath::new(x) r
&CStrPath::new(x.to_str()?)
&OsStrPath::new(x) r
&Pathx
&[u8] 1?
If you have x of type …Use this …
Stringx.as_bytes()
CStringx.as_bytes()
OsString?
PathBuf?
Vec<u8> 1&x
&strx.as_bytes()
&CStrx.to_bytes_with_nul()
&OsStrx.as_bytes() 2
&Path?
&[u8] 1x
You wantAnd have xUse this …
*const c_charCStringx.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.

字符串输出url

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:

MacroOutputNotes
format!(fmt)StringBread-and-butter "to String" converter.
print!(fmt)ConsoleWrites to standard output.
println!(fmt)ConsoleWrites to standard output.
eprint!(fmt)ConsoleWrites to standard error.
eprintln!(fmt)ConsoleWrites to standard error.
write!(dst, fmt)BufferDon't forget to also use std::io::Write;
writeln!(dst, fmt)BufferDon't forget to also use std::io::Write;
 
MethodNotes
x.to_string() STDProduces 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:

TypeImplements
StringDebug, Display
CStringDebug
OsStringDebug
PathBufDebug
Vec<u8>Debug
&strDebug, Display
&CStrDebug
&OsStrDebug
&PathDebug
&[u8]Debug
boolDebug, Display
charDebug, Display
u8i128Debug, Display
f32, f64Debug, 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] }
ElementMeaning
argumentNumber (0, 1, ...), argument '21 or name,'18 e.g., print!("{x}").
fillThe character to fill empty spaces with (e.g., 0), if width is specified.
alignLeft (<), center (^), or right (>), if width is specified.
signCan be + for sign to always be printed.
#Alternate formatting, e.g. prettify DebugSTD formatter ? or prefix hex with 0x.
widthMinimum width (≥ 0), padding with fill (default to space). If starts with 0, zero-padded.
precisionDecimal digits (≥ 0) for numerics, or max width for non-numerics.
$Interpret width or precision as argument identifier instead to allow for dynamic formatting.
typeDebugSTD (?) formatting, hex (x), binary (b), octal (o), pointer (p), exp (e) ... see more.
 
Format ExampleExplanation
{}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 DebugSTD 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).
 
Full ExampleExplanation
println!("{}", x)Print x using DisplaySTD on std. out and append new line. '15
println!("{x}")Same, but use variable x from scope. '21
format!("{a:.3} {b:?}")Convert PI with 3 digits, add space, b with Debug STD, return String. '21
 

Toolingurl

Project Anatomyurl

Basic project layout, and common files and folders, as used by cargo.

EntryCode
📁 .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.rsIndividual examples are run like cargo run --example my_example.
📁 src/Actual source code for your project.
     main.rsDefault entry point for applications, this is what cargo run uses.
     lib.rsDefault entry point for libraries. This is where lookup for my_crate::f() starts.
📁 src/bin/Place for additional binaries, even in library projects.
     x.rsAdditional binary, run with cargo run --bin x.
📁 tests/Integration tests go here, invoked via cargo test. Unit tests often stay in src/ file.
.rustfmt.tomlIn case you want to customize how cargo fmt works.
.clippy.tomlSpecial configuration for certain clippy lints, utilized via cargo clippy 🝖
build.rsPre-build script, 🔗 useful when compiling C / FFI, ...
Cargo.tomlMain project manifest, 🔗 Defines dependencies, artifacts ...
Cargo.lockDependency details for reproducible builds; add to git for apps, not for libs.
rust-toolchain.tomlDefine 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:

  • Module tree needs to be explicitly defined, is not implicitly built from file system tree. 🔗
  • Module tree root equals library, app, … entry point (e.g., lib.rs).

Actual module definitions work as follows:

  • A mod m {} defines module in-file, while mod m; will read m.rs or m/mod.rs.
  • Path of .rs based on nesting, e.g., mod a { mod b { mod c; }}} is either a/b/c.rs or a/b/c/mod.rs.
  • Files not pathed from module tree root via some mod m; won't be touched by compiler! 🛑

Rust has three kinds of namespaces:

Namespace Types Namespace Functions Namespace Macros
mod X {} fn X() {} macro_rules! X { ... }
X (crate) const X: u8 = 1;
trait X {} static X: u8 = 1;
enum X {}
union X {}
struct X {}
struct X;1
struct X();1

1 Counts in Types and in Functions.

  • In any given scope, for example within a module, only one item item per namespace can exist, e.g.,
    • enum X {} and fn X() {} can coexist
    • struct X; and const X cannot coexist
  • With a use my_mod::X; all items called X will be imported.

Due to naming conventions (e.g., fn and mod are lowercase by convention) and common sense (most developers just don't name all things X) you won't have to worry about these kinds in most cases. They can, however, be a factor when designing macros.

 

Cargourl

Commands and tools that are good to know.

CommandDescription
cargo initCreate a new project for the latest edition.
cargo buildBuild the project in debug mode (--release for all optimization).
cargo checkCheck if project would compile (much faster).
cargo testRun tests for the project.
cargo doc --openLocally generate documentation for your code and dependencies.
cargo runRun your project, if a binary is produced (main.rs).
     cargo run --bin bRun binary b. Unifies features with other dependents (can be confusing).
     cargo run -p wRun main of sub-workspace w. Treats features more as you would expect.
cargo treeShow 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 timingsShow what crates caused your build to take so long, highly useful. 🚧 🔥
     rustc -- -Zunpretty=expandedShow expanded macros. 🚧
rustup docOpen 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].

ToolDescription
cargo clippyAdditional (lints) catching common API misuses and unidiomatic code. 🔗
cargo fmtAutomatic code formatter (rustup component add rustfmt). 🔗
 

A large number of additional cargo plugins can be found here.

 

Cross Compilationurl

🔘 Check target is supported.

🔘 Install target via rustup target install X.

🔘 Install native toolchain (required to link, depends on target).

Get from target vendor (Google, Apple, …), might not be available on all hosts (e.g., no iOS toolchain on Windows).

Some toolchains require additional build steps (e.g., Android's make-standalone-toolchain.sh).

🔘 Update ~/.cargo/config.toml like this:

[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

 

Tooling Directivesurl

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 MacrosExplanation
$x:tyMacro capture (here a type).
     $x:itemAn item, like a function, struct, module, etc.
     $x:blockA block {} of statements or expressions, e.g., { let x = 5; }
     $x:stmtA statement, e.g., let x = 1 + 1;, String::new(); or vec![];
     $x:exprAn expression, e.g., x, 1 + 1, String::new() or vec![]
     $x:patA pattern, e.g., Some(t), (17, 'a') or _.
     $x:tyA type, e.g., String, usize or Vec<u8>.
     $x:identAn identifier, for example in let x = 0; the identifier is x.
     $x:pathA path (e.g. foo, ::std::mem::replace, transmute::<_, int>).
     $x:literalA literal (e.g. 3, "foo", b"bar", etc.).
     $x:lifetimeA lifetime (e.g. 'a, 'static, etc.).
     $x:metaA meta item; the things that go inside #[...] and #![...] attributes.
     $x:visA visibility modifier; pub, pub(crate), etc.
     $x:ttA single token tree, see here for more details.
$crateSpecial hygiene variable, crate where macros is defined. ?

Inside a doc comment BK EX REF these work:

Within Doc CommentsExplanation
```...```Include a doc test (doc code running on cargo test).
```X,Y ...```Same, and include optional configurations; with X, Y being ...
     rustMake it explicit test is written in Rust; implied by Rust tooling.
     -Compile test. Run test. Fail if panic. Default behavior.
     should_panicCompile test. Run test. Execution should panic. If not, fail test.
     no_runCompile test. Fail test if code can't be compiled, Don't run test.
     compile_failCompile test but fail test if code can be compiled.
     ignoreDo not compile. Do not run. Prefer option above instead.
     edition2018Execute 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'sOnExplanation
#![no_std]CDon't (automatically) import stdSTD ; use coreSTD instead. REF
#![no_implicit_prelude]CMDon't add preludeSTD, need to manually import None, Vec, ... REF
#![no_main]CDon't emit main() in apps if you do that yourself. REF
 
Opt-In'sOnExplanation
#![feature(a, b, c)]CRely on features that may never get stabilized, c. Unstable Book. 🚧
 
BuildsOnExplanation
#![windows_subsystem = "x"]COn Windows, make a console or windows app. REF 🝖
#![crate_name = "x"]CSpecifiy current crate name, e.g., when not using cargo. ? REF 🝖
#![crate_type = "bin"]CSpecifiy current crate type (bin, lib, dylib, cdylib, ...). REF 🝖
#![recursion_limit = "123"]CSet compile-time recursion limit for deref, macros, ... REF 🝖
#![type_length_limit = "456"]CLimits maximum number of type substitutions. REF 🝖
 
HandlersOnExplanation
#[panic_handler]FMake some fn f(&PanicInfo) -> ! app's panic handler. REF
#[global_allocator]SMake static item impl. GlobalAlloc STD global allocator. REF

Attributes primarily governing emitted code:

Developer UXOnExplanation
#[non_exhaustive]TFuture-proof struct or enum; hint it may grow in future. REF
#[path = "x.rs"]MGet module from non-standard file. REF
 
CodegenOnExplanation
#[inline]FNicely suggest compiler should inline function at call sites. REF
#[inline(always)]FEmphatically threaten compiler to inline call, or else. REF
#[inline(never)]FInstruct compiler to feel disappointed if it still inlines the function. REF
#[cold]FHint that function probably isn't going to be called. REF
#[target_feature(enable="x")]FEnable CPU feature (e.g., avx2) for code of unsafe fn. REF
#[track_caller]FAllows fn to find callerSTD for better panic messages. REF
#[repr(X)]1TUse another representation instead of the default rust REF one:
     #[repr(C)]TUse a C-compatible (f. FFI), predictable (f. transmute) layout. REF
     #[repr(C, u8)]enumGive enum discriminant the specified type. REF
     #[repr(transparent)]TGive single-element type same layout as contained field. REF
     #[repr(packed(1))]TLower alignment of struct and contained fields, mildly UB prone. REF
     #[repr(align(8))]TRaise 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))].

 
LinkingOnExplanation
#[no_mangle]*Use item name directly as symbol name, instead of mangling. REF
#[no_link]XDon't link extern crate when only wanting macros. REF
#[link(name="x", kind="y")]XNative lib to link against when looking up symbol. REF
#[link_name = "foo"]FName of symbol to search for resolving extern fn. REF
#[link_section = ".sample"]FSSection name of object file where item should be placed. REF
#[export_name = "foo"]FSExport a fn or static under a different name. REF
#[used]SDon't optimize away static variable despite it looking unused. REF

Attributes used by Rust tools to improve code quality:

Code PatternsOnExplanation
#[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"]FTXMakes 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).

 
TestsOnExplanation
#[test]FMarks the function as a test, run with cargo test. 🔥 REF
#[ignore = "msg"]FCompiles but does not execute some #[test] for now. REF
#[should_panic]FTest must panic!() to actually succeed. REF
#[bench]FMark function in bench/ as benchmark for cargo bench. 🚧 REF
 
FormattingOnExplanation
#[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. 🔗
 
DocumentationOnExplanation
#[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 = "")]CSets the favicon for the docs. 🔗
#![doc(html_logo_url = "")]CThe logo used in the docs. 🔗
#![doc(html_playground_url = "")]CGenerates Run buttons and uses given service. 🔗
#![doc(html_root_url = "")]CBase URL for links to external crates. 🔗
#![doc(html_no_source)]CPrevents source from being included in docs. 🔗

Attributes related to the creation and use of macros:

Macros By ExampleOnExplanation
#[macro_export]!Export macro_rules! as pub on crate level REF
#[macro_use]MXLet macros persist past modules; or import from extern crate. REF
 
Proc MacrosOnExplanation
#[proc_macro]FMark fn as function-like procedural macro callable as m!(). REF
#[proc_macro_derive(Foo)]FMark fn as derive macro which can #[derive(Foo)]. REF
#[proc_macro_attribute]FMark fn as attribute macro which can understand new #[x]. REF
 
DerivesOnExplanation
#[derive(X)]TLet some proc macro provide a goodish impl of trait X. 🔥 REF

Attributes governing conditional compilation:

Config AttributesOnExplanation
#[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 OptionsOnExplanation
#[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 EnvironmentExplanation 🔗
CARGO_FEATURE_XEnvironment variable set for each feature x activated.
     CARGO_FEATURE_SERDEIf feature serde were enabled.
     CARGO_FEATURE_SOME_FEATUREIf feature some-feature were enabled; dash - converted to _.
CARGO_CFG_XExposes cfg's; joins mult. opts. by , and converts - to _.
     CARGO_CFG_TARGET_OS=macosIf target_os were set to macos.
     CARGO_CFG_TARGET_FEATURE=avx,avx2If target_feature were set to avx and avx2.
OUT_DIRWhere output should be placed.
TARGETTarget triple being compiled for.
HOSTHost triple (running this build script).
PROFILECan be debug or release.

Available in build.rs via env::var()?. List not exhaustive.

 
Output StringExplanation 🔗
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=]NAMELink native library as if via -l option.
cargo:rustc-link-search=[KIND=]PATHSearch path for native library as if via -L option.
cargo:rustc-flags=FLAGSAdd 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=MESSAGEEmit 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.


Working with Typesurl

Types, Traits, Genericsurl

Allowing users to bring their own types and avoid code duplication.

Types
u8 String Device
  • Set of values with given semantics, layout, …
TypeValues
u8{ 0u8, 1u8, ..., 255u8 }
char{ 'a', 'b', ... '🦀' }
struct S(u8, char){ (0u8, 'a'), ... (255u8, '🦀') }

Sample types and sample values.

Type Equivalence and Conversions
u8 &u8 &mut u8 [u8; 1] String
  • It may be obvious but u8, &u8, &mut u8, are entirely different from each other
  • Any t: T only accepts values from exactly T, e.g.,
    • f(0_u8) can't be called with f(&0_u8),
    • f(&mut my_u8) can't be called with f(&my_u8),
    • f(0_u8) can't be called with f(0_i8).

Yes, 0 != 0 (in a mathematical sense) when it comes to types! In a language sense, the operation ==(0u8, 0u16) just isn't defined to prevent happy little accidents.

TypeValues
u8{ 0u8, 1u8, ..., 255u8 }
u16{ 0u16, 1u16, ..., 65_535u16 }
&u8{ 0xffaa&u8, 0xffbb&u8, ... }
&mut u8{ 0xffaa&mut u8, 0xffbb&mut u8, ... }

How values differ between types.

  • However, Rust might sometimes help to convert between types1
    • casts manually convert values of types, 0_i8 as u8
    • coercions automatically convert types if safe2, let x: &u8 = &mut 0_u8;

1 Casts and coercions convert values from one set (e.g., u8) to another (e.g., u16), possibly adding CPU instructions to do so; and in such differ from subtyping, which would imply type and subtype are part of the same set (e.g., u8 being subtype of u16 and 0_u8 being the same as 0_u16) where such a conversion would be purely a compile time check. Rust does not use subtyping for regular types (and 0_u8 does differ from 0_u16) but sort-of for lifetimes. 🔗

2 Safety here is not just physical concept (e.g., &u8 can't be coerced to &u128), but also whether 'history has shown that such a conversion would lead to programming errors'.

Implementations — impl S { }
u8 impl { ... } String impl { ... } Port impl { ... }
impl Port {
    fn f() { ... }
}
  • Types usually come with implementation, e.g., impl Port {}, behavior related to type:
    • associated functions Port::new(80)
    • methods port.close()

What's considered related is more philosophical than technical, nothing (except good taste) would prevent a u8::play_sound() from happening.

Traits — trait T { }
Copy Clone Sized ShowHex
  • Traits ...
    • are way to "abstract" behavior,
    • trait author declares semantically this trait means X,
    • other can implement ("subscribe to") that behavior for their type.
  • Think about trait as "membership list" for types:
Copy Trait
Self
u8
u16
...
Clone Trait
Self
u8
String
...
Sized Trait
Self
char
Port
...

Traits as membership tables, Self refers to the type included.

  • Whoever is part of that membership list will adhere to behavior of list.
  • Traits can also include associated methods, functions, ...
trait ShowHex {
    // Must be implemented according to documentation.
    fn as_hex() -> String;

    // Provided by trait author.
    fn print_hex() {}
}
Copy
trait Copy { }
  • Traits without methods often called marker traits.
  • Copy is example marker trait, meaning memory may be copied bitwise.
Sized
  • Some traits entirely outside explicit control
  • Sized provided by compiler for types with known size; either this is, or isn't
Implementing Traits for Types — impl T for S { }
impl ShowHex for Port { ... }
  • Traits are implemented for types 'at some point'.
  • Implementation impl A for B add type B to the trait membership list:
ShowHex Trait
Self
Port
  • Visually, you can think of the type getting a "badge" for its membership:
u8 impl { ... } Sized Clone Copy Device impl { ... } Transport Port impl { ... } Sized Clone ShowHex
Traits vs. Interfaces
👩‍🦰 Eat 🧔 Venison Eat 🎅 venison.eat()
 

Interfaces

  • In Java, Alice creates interface Eat.
  • When Bob authors Venison, he must decide if Venison implements Eat or not.
  • In other words, all membership must be exhaustively declared during type definition.
  • When using Venison, Santa can make use of behavior provided by Eat:
// 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

  • In Rust, Alice creates trait Eat.
  • Bob creates type Venison and decides not to implement Eat (he might not even know about Eat).
  • Someone* later decides adding Eat to Venison would be a really good idea.
  • When using Venison Santa must import Eat separately:
// 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. ?

Type Constructors — Vec<>
Vec<u8> Vec<char>
  • Vec<u8> is type "vector of bytes"; Vec<char> is type "vector of chars", but what is Vec<>?
ConstructValues
Vec<u8>{ [], [1], [1, 2, 3], ... }
Vec<char>{ [], ['a'], ['x', 'y', 'z'], ... }
Vec<>-

Types vs type constructors.

Vec<>
  • Vec<> is no type, does not occupy memory, can't even be translated to code.
  • Vec<> is type constructor, a "template" or "recipe to create types"
    • allows 3rd party to construct concrete type via parameter,
    • only then would this Vec<UserType> become real type itself.
Generic Parameters — <T>
Vec<T> [T; 128] &T &mut T S<T>
  • Parameter for Vec<> often named T therefore Vec<T>.
  • T "variable name for type" for user to plug in something specfic, Vec<f32>, S<u8>, …
Type ConstructorProduces Family
struct Vec<T> {}Vec<u8>, Vec<f32>, Vec<Vec<u8>>, ...
[T; 128][u8; 128], [char; 128], [Port; 128] ...
&T&u8, &u16, &str, ...

Type vs type constructors.

// S<> is type constructor with parameter T; user can supply any concrete type for T.
struct S<T> {
    x: T
}

// Within 'concrete' code an existing type must be given for T.
fn f() {
    let x: S<f32> = S::new(0_f32);
}

Const Generics — [T; N] and S<const N: usize>
[T; n] S<const N>
  • Some type constructors not only accept specific type, but also specific constant.
  • [T; n] constructs array type holding T type n times.
  • For custom types declared as MyArray<T, const N: usize>.
Type ConstructorProduces Family
[u8; N][u8; 0], [u8; 1], [u8; 2], ...
struct S<const N: usize> {}S<1>, S<6>, S<123>, ...

Type constructors based on constant.

let x: [u8; 4]; // "array of 4 bytes"
let y: [f32; 16]; // "array of 16 floats"

// `MyArray` is type constructor requiring concrete type `T` and
// concrete usize `N` to construct specific type.
struct MyArray<T, const N: usize> {
    data: [T; N],
}
Bounds (Simple) — where T: X
🧔 Num<T> 🎅 Num<u8> Num<f32> Num<Cmplx>   u8 Absolute Dim Mul Port Clone ShowHex
  • If T can be any type, how can we reason about (write code) for such a Num<T>?
  • Parameter bounds:
    • limit what types (trait bound) or values (const bound ?) allowed,
    • we now can make use of these limits!
  • Trait bounds act as "membership check":
// 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.

Bounds (Compound) — where T: X + Y
u8 Absolute Dim Mul f32 Absolute Mul char Cmplx Absolute Dim Mul DirName TwoD Car DirName
struct S<T>
where
    T: Absolute + Dim + Mul + DirName + TwoD
{ ... }
  • Long trait bounds can look intimidating.
  • In practice, each + X addition to a bound merely cuts down space of eligible types.
Implementing Families — impl<>
 

When we write:

impl<T> S<T> where T: Absolute + Dim + Mul {
    fn f(&self, x: T) { ... };
}

It can be read as:

  • here is an implementation recipe for any type T (the impl <T> part),
  • where that type must be member of the Absolute + Dim + Mul traits,
  • you may add an implementation block to S<T>,
  • containing the methods ...

You can think of such impl<T> ... {} code as abstractly implementing a family of behaviors. Most notably, they allow 3rd parties to transparently materialize implementations similarly to how type constructors materialize types:

// If compiler encounters this, it will
// - check `0` and `x` fulfill the membership requirements of `T`
// - create two new version of `f`, one for `char`, another one for `u32`.
// - based on "family implementation" provided
s.f(0_u32);
s.f('x');
Blanket Implementations — impl<T> X for T { ... }
 

Can also write "family implementations" so they apply trait to many types:

// Also implements Serialize for any type if that type already implements ToHex
impl<T> Serialize for T where T: ToHex { ... }

These are called blanket implementations.

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 Parameters — 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?

  • Traits themselves can be generic over two kinds of parameters:
    • trait From<I> {}
    • trait Deref { type O; }
  • Remember we said traits are "membership lists" for types and called the list Self?
  • Turns out, parameters I (for input) and O (for output) are just more columns to that trait's list:
impl From<u8> for u16 {}
impl From<u16> for u32 {}
impl Deref for Port { type O = u8; }
impl Deref for String { type O = str; }
From
SelfI
u16u8
u32u16
...
Deref
SelfO
Portu8
Stringstr
...

Input and output parameters.

Now here's the twist,

  • any output O parameters must be uniquely determined by input parameters I,
  • (in the same way as a relation X Y would represent a function),
  • Self counts as an input.

A more complex example:

trait Complex<I1, I2> {
    type O1;
    type O2;
}
  • this creates a relation relation of types named Complex,
  • with 3 inputs (Self is always one) and 2 outputs, and it holds (Self, I1, I2) => (O1, O2)
Complex
Self [I]I1I2O1O2
Playeru8charf32f32
EvilMonsteru16stru8u8
EvilMonsteru16Stringu8u8
NiceMonsteru16Stringu8u8
NiceMonster🛑u16Stringu8u16

Various trait implementations. The last one is not valid as (NiceMonster, u16, String) has
already uniquely determined the outputs.

Trait Authoring Considerations (Abstract)
👩‍🦰 A<I> 🧔 Car 👩‍🦰 / 🧔 Car A<I> 🎅 car.a(0_u8) car.a(0_f32)
👩‍🦰 B type O; 🧔 Car 👩‍🦰 / 🧔 Car B T = u8; 🎅 car.b(0_u8) car.b(0_f32)
  • Parameter choice (input vs. output) also determines who may be allowed to add members:
    • I parameters allow "familes of implementations" be forwarded to user (Santa),
    • O parameters must be determined by trait implementor (Alice or Bob).
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
SelfI
Xu32
Y...

Santa may add more members by providing his own type for T.

B
SelfO
PlayerString
Xu32

For given set of inputs (here Self), implementor must pre-select O.

Trait Authoring Considerations (Example)
Query vs. Query<I> vs. Query type O; vs. Query<I> type O;
 

Choice of parameters goes along with purpose trait has to fill.


No Additional Parameters

trait Query {
    fn search(&self, needle: &str);
}

impl Query for PostgreSQL { ... }
impl Query for Sled { ... }

postgres.search("SELECT ...");
👩‍🦰 Query 🧔 PostgreSQL Query Sled Query
 

Trait author assumes:

  • neither implementor nor user need to customize API.
 

Input Parameters

trait Query<I> {
    fn search(&self, needle: I);
}

impl Query<&str> for PostgreSQL { ... }
impl Query<String> for PostgreSQL { ... }
impl<T> Query<T> for Sled where T: ToU8Slice { ... }

postgres.search("SELECT ...");
postgres.search(input.to_string());
sled.search(file);
👩‍🦰 Query<I> 🧔 PostgreSQL Query<&str> Query<String> Sled Query<T> where T is ToU8Slice.
 

Trait author assumes:

  • implementor would customize API in multiple ways for same Self type,
  • users may want ability to decide for which I-types behavior should be possible.
 

Output Parameters

trait Query {
    type O;
    fn search(&self, needle: Self::O);
}

impl Query for PostgreSQL { type O = String; ...}
impl Query for Sled { type O = Vec<u8>; ... }

postgres.search("SELECT ...".to_string());
sled.search(vec![0, 1, 2, 4]);
👩‍🦰 Query type O; 🧔 PostgreSQL Query O = String; Sled Query O = Vec<u8>;
 

Trait author assumes:

  • implementor would customize API for Self type (but in only one way),
  • users do not need, or should not have, ability to influence customization for specific Self.

As you can see here, the term input or output does not (necessarily) have anything to do with whether I or O are inputs or outputs to an actual function!

 

Multiple In- and Output Parameters

trait Query<I> {
    type O;
    fn search(&self, needle: I) -> Self::O;
}

impl Query<&str> for PostgreSQL { type O = String; ... }
impl Query<CString> for PostgreSQL { type O = CString; ... }
impl<T> Query<T> for Sled where T: ToU8Slice { type O = Vec<u8>; ... }

postgres.search("SELECT ...").to_uppercase();
sled.search(&[1, 2, 3, 4]).pop();
👩‍🦰 Query<I> type O; 🧔 PostgreSQL Query<&str> O = String; Query<CString> O = CString; Sled Query<T> O = Vec<u8>; where T is ToU8Slice.
 

Like examples above, in particular trait author assumes:

  • users may want ability to decide for which I-types ability should be possible,
  • for given inputs, implementor should determine resulting output type.
Dynamic / Zero Sized Types
MostTypes Sized Normal types. vs. Z Sized Zero sized. vs. str Sized Dynamically sized. [u8] Sized dyn Trait Sized ... Sized
  • A type T is Sized STD if at compile time it is known how many bytes it occupies, u8 and &[u8] are, [u8] isn't.
  • Being Sized means impl Sized for T {} holds. Happens automatically and cannot be user impl'ed.
  • Types not Sized are called dynamically sized types BK NOM REF (DSTs), sometimes unsized.
  • Types without data are called zero sized types NOM (ZSTs), do not occupy space.
示例说明
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.
  • However, there exists invisible default bound T: Sized, so S<str> is not possible out of box.
  • Instead we have to add T : ?Sized to opt-out of that bound:
S<T> S<u8> S<char> S<str>
struct S<T> where T: ?Sized { ... }
Generics and Lifetimes — <'a>
S<'a> &'a f32 &'a mut u8
  • Lifetimes act* like type parameters:
    • user must provide specific 'a to instantiate type (compiler will help within methods),
    • as Vec<f32> and Vec<u8> are different types, so are S<'p> and S<'q>,
    • meaning you can't just assign value of type S<'a> to variable expecting S<'b> (exception: "subtype" relationship for lifetimes, e.g. 'a outliving 'b).
S<'a> S<'auto> S<'static>
  • 'static is only nameable instance of the typespace lifetimes.
// `'a is free parameter here (user can pass any specific lifetime)
struct S<'a> {
    x: &'a u32
}

// In non-generic code, 'static is the only nameable lifetime we can explicitly put in here.
let a: S<'static>;

// Alternatively, in non-generic code we can (often must) omit 'a and have Rust determine
// the right value for 'a automatically.
let b: S;

* There are subtle differences, for example you can create an explicit instance 0 of a type u32, but with the exception of 'static you can't really create a lifetime, e.g., "lines 80 - 100", the compiler will do that for you. 🔗

Note to self and TODO: that analogy seems somewhat flawed, as if S<'a> is to S<'static> like S<T> is to S<u32>, then 'static would be a type; but then what's the value of that type?

Examples expand by clicking.

Type Zoourl

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> Items defined in upstream crates. Serialize Transport ShowHex Device From<u8> Foreign trait impl. for local type. String Serialize Local trait impl. for foreign type. String From<u8> 🛑 Illegal, foreign trait for f. type. String From<Port> Exception: Legal if used type local. Port From<u8> From<u16> Mult. impl. of trait with differing IN params. Container Deref Tgt = u8; Deref Tgt = f32; 🛑 Illegal impl. of trait with differing OUT params. T T T ShowHex Blanket impl. of trait for any type. Your crate.

A walk through the jungle of types, traits, and implementations that (might possibly) exist in your application.

Type Conversionsurl

How to get B when you have A?

fn f(x: A) -> B {
    // How can you obtain B from A?
}
MethodExplanation
IdentityTrivial case, B is exactly A.
ComputationCreate and manipulate instance of B by writing code transforming data.
CastsOn-demand conversion between types where caution is advised.
CoercionsAutomatic conversion within 'weakening ruleset'.1
SubtypingAutomatic 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:

TraitExampleTrait 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 {}*aA 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

AB示例说明
PtrPtrdevice_ptr as *const u8If *A, *B are Sized.
PtrIntegerdevice_ptr as usize
IntegerPtrmy_usize as *const Device
NumberNumbermy_u8 as u16Often surprising behavior.
enum w/o fieldsIntegerE::A as u8
boolIntegertrue as u8
charInteger'A' as u8
&[T; N]*const Tmy_ref as *const u8
fn(...)Ptrf as *const u8If Ptr is Sized.
fn(...)Integerf 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

ABExplanation
&mut T&TPointer weakening.
&mut T*mut T-
&T*const T-
*mut T*const T-
&T&UDeref, if impl Deref<Target=U> for T.
TUUnsizing, if impl CoerceUnsized<U> for T.2 🚧
TVTransitivity, if T coerces to U and U to V.
|x| x + xfn(u8) -> u8Non-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 u8Valid, forever-pointer is also transient-pointer.
&'a u8&'static u8🛑 Invalid, transient should not be forever.
&'a &'b u8&'a &'b u8Valid, same thing. But now things get interesting. Read on.
&'a &'static u8&'a &'b u8Valid, &'static u8 is also &'b u8; covariant inside &.
&'a mut &'static u8&'a mut &'b u8🛑 Invalid and surprising; invariant inside &mut.
Box<&'a static>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<&'a static>Cell<&'a u8>🛑 Invalid, cells are never something else; invariant.
fn(&'static u8)fn(&'u8 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:

ABExplanation
u16u8🛑 Obviously invalid; u16 should never automatically be u8.
u8u16🛑 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 longer lifetime 'a that outlives a shorter 'b is a subtype of 'b.
  • Implies 'static is subtype of all other lifetimes 'a.
  • Whether types with parameters (e.g., &'a T) are subtypes of each other the following variance table is used:
Construct1'aTU
&'a Tcovariantcovariant
&'a mut Tcovariantinvariant
Box<T>covariant
Cell<T>invariant
fn(T) -> Ucontravariantcovariant
*const Tcovariant
*mut Tinvariant

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 of u16), and a Box<u32> would never be sub- or supertype of anything. However, generally a Box<A>, can be subtype of Box<B> (via covariance) if A is a subtype of B, which can only happen if A and B are 'sort of the same type that only differed in lifetimes', e.g., A being &'static u32 and B being &'a u32.

 

Coding Guidesurl

Idiomatic Rusturl

If you are used to programming Java or C, consider these.

IdiomCode
Think in Expressionsx = 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 Typesenum E { Invalid, Valid { ... } } over ERROR_INVALID = -1
enum E { Visible, Hidden } over visible: bool
struct Charge(f32) over f32
Provide BuildersCar::new("Model T").hp(20).build();
Split ImplementationsGeneric types S<T> can have a separate impl per T.
Rust doesn't have OO, but with separate impl you can get specialization.
UnsafeAvoid unsafe {}, often safer, faster solution without it. Exception: FFI.
Implement Traits#[derive(Debug, Copy, ...)] and custom impl where needed.
ToolingWith 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.
DocumentationAnnotate 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! 🔥

 

Async-Await 101url

If you are familiar with async / await in C# or TypeScript, here are some things to keep in mind:

ConstructExplanation
asyncAnything 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.awaitInside 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:

  • might resume execution. It usually does, unless sm / Future dropped.
  • might resume with the previous worker or another worker thread (depends on runtime).

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 1Explanation
sleep_or_block();Definitely bad 🛑, never halt current thread, clogs executor.
set_TL(a); x.await; TL();Definitely bad 🛑, await may return from other thread, thread local invalid.
s.no(); x.await; s.go();Maybe bad 🛑, await will not return if Future dropped while waiting. 2
Rc::new(); x.await; rc();Non-Send types prevent impl Future from being Send; less compatible.

1 Here we assume s is any non-local that could temporarily be put into an invalid state; TL is any thread local storage, and that the async {} containing the code is written without assuming executor specifics.
2 Since Drop is run in any case when Future is dropped, consider using drop guard that cleans up / fixes application state if it has to be left in bad condition across .await points.

 

Closures in APIsurl

There is a subtrait relationship Fn : FnMut : FnOnce. That means a closure that implements Fn STD also implements FnMut and FnOnce. Likewise a closure that implements FnMut STD also implements FnOnce. STD

From a call site perspective that means:

SignatureFunction 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:

ClosureImplements*Comment
|| { moved_s; } FnOnceCaller must give up ownership of moved_s.
|| { &mut s; } FnOnce, FnMutAllows g() to change caller's local state s.
|| { &s; } FnOnce, FnMut, FnMay 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:

RequiringAdvantageDisadvantage
F: FnOnceEasy to satisfy as caller.Single use only, g() may call f() just once.
F: FnMutAllows g() to change caller state.Caller may not reuse captures during g().
F: FnMany can exist at same time.Hardest to produce for caller.
 

Unsafe, Unsound, Undefinedurl

Unsafe leads to unsound. Unsound leads to undefined. Undefined leads to the dark side of the force.

Unsafe Code

  • Code marked unsafe has special permissions, e.g., to deref raw pointers, or invoke other unsafe functions.
  • Along come special promises the author must uphold to the compiler, and the compiler will trust you.
  • By itself unsafe code is not bad, but dangerous, and needed for FFI or exotic data structures.
// `x` must always point to race-free, valid, aligned, initialized u8 memory.
unsafe fn unsafe_f(x: *mut u8) {
    my_native_lib(x);
}

Undefined Behavior (UB)

  • As mentioned, unsafe code implies special promises to the compiler (it wouldn't need be unsafe otherwise).
  • Failure to uphold any promise makes compiler produce fallacious code, execution of which leads to UB.
  • After triggering undefined behavior anything can happen. Insidiously, the effects may be 1) subtle, 2) manifest far away from the site of violation or 3) be visible only under certain conditions.
  • A seemingly working program (incl. any number of unit tests) is no proof UB code might not fail on a whim.
  • Code with UB is objectively dangerous, invalid and should never exist.
if should_be_true() {
   let r: &u8 = unsafe { &*ptr::null() };    // Once this runs, ENTIRE app is undefined. Even if
} else {                                     // line seemingly didn't do anything, app might now run
    println!("the spanish inquisition");     // both paths, corrupt database, or anything else.
}

Unsound Code

  • Any safe Rust that could (even only theoretically) produce UB for any user input is always unsound.
  • As is unsafe code that may invoke UB on its own accord by violating above-mentioned promises.
  • Unsound code is a stability and security risk, and violates basic assumption many Rust users have.
fn unsound_ref<T>(x: &T) -> &u128 {      // Signature looks safe to users. Happens to be
    unsafe { mem::transmute(x) }         // ok if invoked with an &u128, UB for practically
}                                        // everything else.
 

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.
 

API Stabilityurl

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.
 

Miscurl

These are other great guides and tables.

Cheat SheetsDescription
Rust Learning⭐Probably the best collection of links about learning Rust.
Functional Jargon in RustA collection of functional programming jargon explained in Rust.
Periodic Table of TypesHow various types and references correlate.
FuturesHow to construct and work with futures.
Rust Iterator Cheat SheetSummary of iterator-related methods from std::iter and itertools.
Type-Based Rust Cheat SheetLists common types and how they convert.
 

All major Rust books developed by the community.

Books ️📚Description
The Rust Programming LanguageStandard introduction to Rust, start here if you are new.
     API GuidelinesHow to write idiomatic and re-usable Rust.
     Asynchronous Programming 🚧Explains async code, Futures, ...
     Design PatternsIdioms, Patterns, Anti-Patterns.
     Edition GuideWorking with Rust 2015, Rust 2018, and beyond.
     Guide to Rustc DevelopmentExplains how the compiler works internally.
     Little Book of Rust MacrosCommunity's collective knowledge of Rust macros.
     Reference 🚧Reference of the Rust language.
     RFC BookLook up accepted RFCs and how they change the language.
     Performance BookTechniques to improve the speed and memory usage.
     Rust CookbookCollection of simple examples that demonstrate good practices.
     Rust in Easy EnglishExplains concepts in simplified English, good alternative start.
     Rust for the Polyglot ProgrammerA guide for the experienced programmer.
     Rustdoc BookTips how to customize cargo doc and rustdoc.
     RustonomiconDark Arts of Advanced and Unsafe Rust Programming.
     Unsafe Code Guidelines 🚧Concise information about writing unsafe code.
     Unstable BookInformation about unstable items, e.g, #![feature(...)].
The Cargo BookHow to use cargo and write Cargo.toml.
The CLI BookInformation about creating CLI tools.
The Embedded BookWorking with embedded and #![no_std] devices.
     The EmbedonomiconFirst #![no_std] from scratch on a Cortex-M.
The WebAssembly BookWorking with the web and producing .wasm files.
     The wasm-bindgen GuideHow 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 ChangelogSee all the things that changed in a particular version.
Rust ForgeLists release train and links for people working on the compiler.
     Rust Platform SupportAll supported platforms and their Tier.
     Rust Component HistoryCheck nightly status of various Rust tools for a platform.
ALL the Clippy LintsAll the clippy lints you might be interested in.
Configuring RustfmtAll rustfmt options you can use in .rustfmt.toml.
Compiler Error IndexEver wondered what E0404 means?
 

Online services which provide information or tooling.

Services ⚙️Description
crates.ioAll 3rd party libraries for Rust.
std.rsShortcut to std documentation.
docs.rsDocumentation for 3rd party libraries, automatically generated from source.
lib.rsUnofficial overview of quality Rust libraries and applications.
caniuse.rsCheck which Rust version introduced or stabilized a feature.
Rust PlaygroundTry and share snippets of Rust code.
Rust Search ExtensionBrowser extension to search docs, crates, attributes, books, …
 

Printing & PDFurl

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).