1. Getting Started

  1. 使用 rustup 安装,默认安装在 $HOME/.cargo$HOME/.rustup
  2. rustup update
  3. rustup self uninstall
  4. cargo 常用命令
    1. cargo new PROJECT
    2. cargo build [--release]
    3. cargo run

2. Programming a Guessing Game

  1. Cargo.toml 里依赖部分的版本号 “MAJOR.MINOR.PATCH” 实际是 “^MAJOR.MINOR.PATCH” 的简写,表示允许语义版本的升级
  2. cargo update 默认只升级 PATCH 部分
  3. cargo doc --open 查看文档

3. Common Programming Concepts

  1. 不可变变量:let VAR: TYPE = VALUE;
  2. 可变变量:let mut VAR: TYPE = VALUE;
  3. 常量:const FOO_BAR: TYPE = VALUE;
  4. Shadowing: 同一作用域里,同名变量可以重复声明,之前声明的变量被遮蔽。
  5. Scalar types:
    1. Integer types: i8, u8, i16, u16, i32, u32, i64, u64, i128, u128, isize, usize,后两者长度跟机器相关,一般用于集合、数组的索引和长度。Debug 模式下溢出回绕会panic,Release模式下溢出回绕不报错。使用标准库里类型 Wrapping 显示表明期望溢出回绕行为。
    2. Integer literals: 98_222, 0xff, 0o77, 0b1111_0000, b’A’,除了 byte literal,其它字面量都支持类型后缀,比如 57u8 表示一个 u8 类型的值 57。整型字面量默认类型为 i32
    3. Floating-point types: f32, f64,默认为 f64
    4. Boolean type: bool, true, false。布尔类型长度为一字节。
    5. Character type: char,四个字节,表示一个 Unicode Scalar Value, 范围为 [U+0000, U+D7FF] 和 [U+E000, U+10FFFF]。
  6. Compound Types
    1. Tuple: (x, y, z),类型声明 (t1, t2, t3)
    2. Array: [x, y, z],类型声明 [t; size]。 使用语法 [x; n] 创建 n 个 x 值的数组。数据访问会检查是否越界。
  7. Functions: fn foo(x:t1, y:t2) -> t3 { ... }
  8. Control flow:
    1. if condition { ... } else if condition { ... } else { ... }
    2. loop { .... break VALUE; ... }
    3. while condition { ... }
    4. for VAR in ITER { ... }

4. Understanding Ownership

  1. Ownership rules:没有实现 Copy trait 的类型,变量赋值时是 move 语义,owner 超出作用域时自动调用 drop()

    1. Each value in Rust has a variable that’s called its owner.
    2. There can only be one owner at a time.
    3. When the owner goes out of scope, the value will be dropped.
  2. Reference: borrow 语义

    1. 多个只读引用可以同时指向一个变量
    2. 同时只能有一个可写引用指向一个变量,不能有其它只读或者可写引用指向同一个变量
    3. Non-Lexical Lifetimes(NLL): 引用的作用域从声明开始,直到它最后一次被使用位置,并不是按照词法作用域判断
  3. Slice: borrow 语义

    1. &s[i..j] 表示 [i, j) 范围
    2. &str 表示 string slice,定义字符串函数时,经常使用 &str 作为参数类型,以同时支持 String 和 &str
    3. slice 记录第一个元素的引用,以及一个长度字段
  1. 定义结构体:struct Xxx { f1: t1, f2: t2 }

  2. 新建实例:Xxx { f1: v1, f2: v2 }

  3. 当变量名与字段名同名时可以简写 Xxx { f1, f2: v2}等价于 Xxx { f1: f1, f2: v2}

  4. 根据已有结构体实例修改部分字段以创建新实例:Xxx { f1: v1, ..xxx},xxx 为已有实例

  5. Tuple Struct: struct Point(i32, i32, i32) 可以使用 point.0, point.1 来引用字段。

  6. Unit-like struct: (),没有任何字段。

  7. 在结构体定义时使用 annotation #[derive(Debug)] 可以让它能被 println!{:?} 或者 {:#?} 置位符输出。

  8. 定义方法:

    impl Rectangle {
        fn area(&self) -> u32 {
            self.width * self.height
        }
    }
    
  9. 同一个结构体可以有多个 impl 块。

6. Enums and Pattern Matching

  1. 定义:

    enum Message {
        Quit,
        Move { x: i32, y: i32 },		// 匿名 struct
        Write(String),
        ChangeColor(i32, i32, i32),
    }
    
  2. enum 跟 struct 一样,也可以用 impl 定义 method

  3. Option类型包含在 prelude 中,可以直接使用 Option 甚至它的 variants SomeNone

    enum Option<T> {
        Some(T),
        None,
    }
    
  4. match 表达式:

    match expr {
      pattern => ...,
      _ => ...,
    }
    
  5. if let 表达式:

    if let pattern = expr {
        ...;
    } else {
        ...;
    }
    

7. Managing Growing Projects with Packages, Crates, and Modules

  1. Rust module system:

    1. Packages: A Cargo feature that lets you build, test, and share crates
    2. Crates: A tree of modules that produces a library or executable
    3. Modules and use: Let you control the organization, scope, and privacy of paths
    4. Paths: A way of naming an item, such as a struct, function, or module
  2. 一个 package 的目录结构如下,src/lib.rs 和 src/main.rs 的 crate name 与 package 同名

    package/   一个 package 最多有一个 library crate,可以有多个 binary crate
    ├── Cargo.toml
    └── src
        ├── bin
        │   ├── bar.rs	# 额外的 binary crate,隐含 module "crate",此文件称为 crate root
        │   └── foo.rs	# 额外的 binary crate,隐含 module "crate",此文件称为 crate root
        ├── lib.rs    	# 默认的 library crate,隐含 module "crate",此文件称为 crate root
        └── main.rs   	# 默认的 binary crate,隐含 module "crate",此文件称为 crate root
    
  3. module tree 和 directory tree 不是一一对应的,一个 .rs 文件里可以定义平行或者嵌套的多个 module。module tree 里的 path 规则:

    1. 绝对路径:当前 crate crate::... ,其它 crate some_crate::...
    2. 相对路径:self::..., super::..., some_identifier
  4. 符号的可见性

    1. 所有符号,包括 mod, struct, enum, fn, const 默认都是私有的,需要用 pub 修饰符公开
    2. child mod 可以看到 parent mod 的所有符号,parent mod 只能看到 child mod 公开的符号
    3. pub struct 的 field 需要额外加 pub 才是公开的,而 pub enum 的 variants 都是公开的
  5. 使用 use 导入 path,类似于文件系统中 ln -s PATH .

    1. 习惯上,导入 function 时,导入到 mod 级别,使用 some_mod::some_fn 调用,以容易识别函数来自哪个模块
    2. 习惯上,导入 struct 和 enum 时,导入到符号本身级别,比如 use std::collectioons::HashMap;
    3. 使用 as 关键字导入为别名:use std::io::Result as IoResult;
    4. use 导入的符号默认为私有,使用 pub use 达到 re-exporting 为公开的效果。
    5. Nested paths: use std::{cmp::Ordering, io}; 等价于 use std::cmp::Ordering; use std::io;use std::io::{self, Write}; 等价于 use std::io; use std::io::Write;
    6. 引入一个 mod 里的所有公开符号:use std::collections::*; ,一般用于单元测试代码里。
  6. 切分 modules 到不同文件

    1. mod xxx; 等价于 mod xxx { ...加载 module/path/to/xxx.rs 文件内容.... }
      1. 在 crate root 里,mod xxx; 等价于 mod xxx { ...load xxx.rs... }
      2. 在 crate::foo::bar 里,mod xxx; 等价于 mod xxx { ... load foo/bar/xxx.rs... }
    2. use 只是做符号连接,并不会加载模块

8. Common Collections

  1. Vec<T>: 创建实例 Vec::new(), vec![1, 2, 3, 4]

  2. v[i] 在 i 越界时会 panic,v.get(i) 返回 Option<T>,因此在越界时返回 None。

  3. 字符串的 + 调用的 fn add(self, s: &str) -> String,第一个参数的所有权会被拿走,后面的参数必须是引用。

  4. format!(...) 拼接字符串,不拿走任何参数的所有权。

  5. 字符串的 len() 返回的是「字节数」不是「字符数」! 使用 s.chars().count() 可以拿到 unicode scalar value 个数,而通常人为感知的「字符」指 grapheme cluster,需要用第三方库处理。

  6. HashMap:

    use std::collections::HashMap;
    
    let mut scores = HashMap::new();
    scores.insert(String::from("Blue"), 10);	// 如果已经存在,则会覆盖
    
    let team_name = String::from("Blue");
    let score = scores.get(&team_name);		// 返回 Option<&V>
    
    for (key, value) in &scores {
        ...
    }
    
    // 不存在时才插入,返回 &mut V
    let score = scores.entry(String::from("Blue")).or_insert(50);
    *score += 10;
    
  7. HashMap 默认使用 cryptographically strong SipHash,如果感觉性能更重要,可以换用其它 hasher。

9. Error Handling

  1. 不可恢复错误:panic!,默认使用 unwind 策略,释放栈上的对象,在 Cargo.toml 里使用 profile 换成 abort 策略,也即立即退出。

    [profile.release]
    panic = 'abort'
    
  2. 使用 RUST_BACKTRACE=1 环境变量在 panic 时打印调用栈

  3. 可恢复错误:

    enum Result<T, E> {
        Ok(T),
        Err(E),
    }
    
  4. ? 操作符:someResult? 表示如果是 Ok 则返回 T,如果是 Err,则转换成当前函数返回类型(必须是Result)并返回。

10. Generic Types, Traits, and Lifetimes

  1. struct, enum, fn 都可以使用泛型,在类型或者函数名字后面使用 <T1, T2, ...> 添加泛型参数。

  2. impl<T> Point<T> { ... } 中 impl 后的泛型参数,表示 Point<T> 里的T 是泛型参数,而不是具体参数。

  3. Trait 很像其它语言的 interface,支持默认实现。

    // 定义 trait
    trait Summary {
        fn summarize(&self) -> String;
        fn abstract(&self) -> String {
            String::from("....")
      }
    }
    
    // 为某个 struct or enum 实现 trait
    impl Summary for Tweet {
        fn summarize(&self) -> String {
            format!("...", ...)
      }
    }
    
    // 要求函数参数满足某个 trait,这种写法一般用在参数个数少的情况
    fn notify(item: impl Summary) {
       ....
    }
    
    // 更通用的 Trait 约束语法
    fn notify<T: Summary>(item: T) {
        ...
    }
    
    // 满足多个 trait
    fn notify(item: impl Summary + Display) { ... }
    fn notify<T: Summary + Display>(item: T) { ... }
    fn notify<T>(item: T) where T: Summary + Display { ... }  // 更容易阅读函数签名
    
    // 函数返回值满足某个 trait,注意由于 Rust 编译器实现限制,
    // impl Summary 返回值约束下,函数只能固定返回某种固定的具体类型!
    fn return_summarizable() -> impl Summary { ... }
    
  4. 利用 trait bound 来条件化的实现方法

    use std::fmt::Display;
    
    struct Pair<T> {
        x: T,
        y: T,
    }
    
    impl<T> Pair<T> {
        fn new(x: T, y: T) -> Self {
            Self {
                x,
                y,
            }
        }
    }
    
    impl<T: Display + PartialOrd> Pair<T> {
        fn cmp_display(&self) {
            if self.x >= self.y {
                println!("The largest member is x = {}", self.x);
            } else {
                println!("The largest member is y = {}", self.y);
            }
        }
    }
    
  5. blanket implentation: 为满足某个 trait bound 的所有类型实现 method。

    impl<T: Display> ToString for T {
        fn to_string(&self) -> String { ... }
    }
    
  6. Lifetime 也是一种泛型参数,修饰于引用类型上,比如 &'a TYPE,lifetime 名字紧接 & 后面,以单引号开头,任意字符串作为名字,习惯上跟泛型类型参数一样用单个字符。作为泛型参数的一种,其也需要在 struct, enum, fn 的名字后面用 <'a, 'b> 这样指定上, 对于 impl<'a, 'b>' 也如此。

    fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { ... }
    
    struct ImportantExcerpt<'a> {
        part: &'a str,
    }
    
    impl<'a> ImportantExcerpt<'a> {
        ...
    }
    
  7. 函数签名 lifetime 省略的三条规则(Rust 编译器以后可能加入更多规则以方便代码编写)。当 lifetime annotation 没有针对函数的输入参数(对应input lifetime parameter)和返回值(对应output lifetime parameter) 指定时:

    1. 每个没有指定 lifetime 的输入参数获得自己独有的 lifetime
    2. 如果只有一个输入参数,且返回值没有指定 lifetime,则返回值和输入参数有同样的 lifetime
    3. 如果第一个参数是 &self or &mut self(意味着这个函数是个method),且返回值没有指定 lifetime,则返回值和 self 有同样的 lifetime
  8. 'static 称为静态生命周期,指代一个引用在整个程序运行期间都有效,比如字面字符串的引用,其隐含了 'static 生命周期。注意编译器在提示 “lifetime 'static required” 时往往是错的,程序员应该正确指定合适的 lifetime。

11. Writing Automated Tests

  1. 单元测试代码示例:

    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        #[should_panic(expected = "...some error message...")]
        fn it_works() {
            assert_eq!(2 + 2, 4);
        }
    }
    
  2. assert!, assert_eq!, assert_ne! 都可以接受额外的错误消息模板以及参数,类似 format!

  3. 测试函数可以返回 Result<(), String>,成功时返回 Ok(()),失败时返回 Err(String::from("..."))

  4. 对于 library cratetests/ 目录下每个 .rs 文件被当作一个 crate,cargo test 会执行每个文件里的测试函数。通用的 setup 代码可以放到 tests/common/mod.rs 里,不要放到 tests/common.rs 因为这样会被当作一个集成测试文件。

    use adder;
    
    mod common;
    
    #[test]
    fn it_adds_two() {
        common::setup();
        assert_eq!(4, adder::add_two(2));
    }
    

12. An I/O Project: Building a Command Line Program

  1. std::env::args() 需要命令行参数是合法的 Unicode,否则会 panic。对非 Unicode 字符集使用 std::env::args_os()

  2. std::fs 操作文件

  3. std::process::exit(i32) 退出进程

  4. 可能遇到任意错误的返回值:

    use std::error::Error;
    
    fn main() -> Result<(), Box<dyn Error>> { ... }
    
  5. std::env::var(KEY) 获取环境变量

  6. eprintln!(...) 输出到 stderr

13. Functional Language Features: Iterators and Closures

  1. Closure 语法:

    fn  add_one_v1   (x: u32) -> u32 { x + 1 }
    let add_one_v2 = |x: u32| -> u32 { x + 1 };
    let add_one_v3 = |x|             { x + 1 };
    let add_one_v4 = |x|               x + 1  ;
    
  2. Closure 的参数、返回值类型在第一次使用 closure 时才推断确定。

  3. Closure 在实现时,实际是创建了一个匿名 struct,实现了 trait FnOnce, FnMut, Fn 中的一个,并保存了捕获的上下文变量。

  4. 在 closure 参数列表前加 move 表示显式的将捕获的环境变量的所有权转移到 closure 上,这种用法一般用在把 closure 传递给另一个线程时。

    let x = vec![1, 2, 3];
    let equal_to_x = move |z| z == x;
    
  5. Iterator trait:

    pub trait Iterator {
        type Item;
    
        fn next(&mut self) -> Option<Self::Item>;
    
        // methods with default implementations elided
    }
    
  6. Iterator 的三个构造方法:

    1. iter(): return immutable reference
    2. iter_mut(): return mutable reference
    3. into_iter(): take ownership and return owned value

14. More About Cargo and Crates.io

  1. crate 级别(src/lib.rs) 开头以及 module 开头,使用 //! ... 编写 Markdown 文档

  2. struct, enum, fn 之类前面使用 /// ... 编写 Markdown 文档,一般包含:

    • # Examples: 使用 ``` 包围的代码段,在执行 cargo test 时会被当成测试用例执行
    • # Panics: 是否调用了 panic!
    • # Errors: 对 Result 返回值什么情况下返回 Err 的阐述
    • # Safefy: 如何使用 unsafe
  3. Cargo workspace: 在顶层目录创建 Cargo.toml,然后在此目录下再 cargo new 创建 package,所有 package 共享顶层目录的 Cargo.lock,但各自的 Cargo.toml 完全独立。

    [workspace]
    
    members = [
        "pkg1",
        "pkg2"
    ]
    
  4. Cargo workspace 里 package 之间的 library crate 依赖:

    # pkg1/Cargo.toml
    
    [dependencies]
    pkg2 = { path = "../pkg2" }
    
  5. 使用 cargo run -p PKGcargo test -p PKG 局限于单个 package。

  6. 使用 cargo install PKG 安装 binary crate

  7. PATH 里带有 cargo- 前缀的命令都自动成为 cargo 的子命令,可以用这个方式扩展 Cargo。

15. Smart Pointers

  1. smart pointers 指实现了 trait DerefDrop 的 struct,从这个意义上讲,StringVec 也是。

    1. Box<T>: 在堆上分配内存
    2. Rc<T>: 引用技术,支持多个 owner
    3. Ref<T>, RefMut<T> 使用 RefCell<T> 访问,运行时应用 borrowing 规则
  2. 实现一个智能指针

    use std::ops::Deref;
    
    struct MyBox<T>(T);		// 定义 tuple struct
    
    impl<T> Deref for MyBox<T> {
        fn new(x: T) -> MyBox<T> {
            MyBox(x)
        }
    
        type Target = T;		// 需要类型
    
        fn deref(&self) -> &T {
            &self.0					// 返回 tuple 第一个元素的引用
        }
    }
    
  3. 创建 Box:

    let x = Box::new(String::from("xxx"));
    
  4. 隐式的 Deref Coercion:

    1. From &T to &U when T: Deref
    2. From &mut T to &mut U when T: DerefMut
    3. From &mut T to &U when T: Deref
  5. 使用 drop 函数提前显式地析构对象。

  6. 引用计数

    use std::rc::Rc;
    
    let x = Rc::new(String::from("xxx"));
    let y = Rc::clone(&x);
    println!("y's strong_count={}", Rc::strong_count(&y));
    
  7. Rc 只能用在单线程里,并且只能共享不可变引用。std::cell::RefCell 也只能用在单线程。

  8. interior mutability 指不可变的类型内部通过 unsafe 代码可以安全的修改数据,RefCell 是一个典型的例子。

  9. The reasons to choose Box, Rc, or RefCell:

    • Rc enables multiple owners of the same data; Box and RefCell have single owners.
    • Box allows immutable or mutable borrows checked at compile time; Rc allows only immutable borrows checked at compile time; RefCell allows immutable or mutable borrows checked at runtime.
    • Because RefCell allows mutable borrows checked at runtime, you can mutate the value inside the RefCell even when the RefCell is immutable.
  10. RefCell<T>::borrow() 返回 Ref<T>RefCell<T>::borrow_mut()返回 RefMut<T>,两个函数可以当作 &&mut 看待。

  11. Rc<T> 用于多个 owner 对同一个数据的只读访问, Rc<RefCell<T>>用于多个 owner 对同一个数据的可写访问。

  12. Rc::downgrade(&Rc<T>) 得到 Weak<T> 引用,用 Rc::upgrade(&Weak<T>) 得到 Option<Rc<T>>

16. Fearless Concurrency

  1. 新建线程:

    use std::thread;
    
    fn main() {
      let handle = thread::spawn(move || {
        ...
      });
    
      handle.join().unwrap();
    }
    
  2. 使用 multiple producer, single consumer channel 跨线程通讯

    use std::sync::mpsc;
    use std::thread;
    use std::time::Duration;
    
    fn main() {
        let (tx, rx) = mpsc::channel(); // tx 指 transmiter, rx 指 reciever
        let tx2 = mpsc::Sender::clone(&tx); // multiple producer
    
        thread::spawn(move || {
            tx.send(String::from("hello")).unwrap();
            thread::sleep(Duration::from_secs(1));
        });
    
        thread::spawn(move || {
            tx2.send(String::from("world")).unwrap();
            thread::sleep(Duration::from_secs(1));
        });
    
        let received = rx.recv().unwrap();	// 阻塞式 recv(), 非阻塞式 try_recv()
        println!("Got: {}", received);
    
        for received in rx { // 迭代器方式
            println!("Got: {}", received);
        }
    }
    
  3. 类似 Rc<RefCell<T>>达到同一个线程里多个owner对同一个数据的内部修改性,使用 Arc<Mutex<T>> 达到多个线程对同一个数据的互斥修改:

    use std::sync::{Arc, Mutex};
    use std::thread;
    
    fn main() {
        // Arc:  // atomically reference counted type
        let counter = Arc::new(Mutex::new(2i64));
        let mut handles = vec![];
    
        for _ in 0..10 {
            let counter = Arc::clone(&counter);
            let handle = thread::spawn(move || {
                let mut num = counter.lock().unwrap();
                *num += 2;
            });
            handles.push(handle);
        }
    
        for handle in handles {
            handle.join().unwrap();
        }
    
        println!("result={}", counter.lock().unwrap());
    }
    
  4. Trait std::marker::Send 表示一个类型的 ownership 是否可以安全的转到另一个线程里。

    • 除了原始指针外,其它基本类型都是 Send
    • 成员全是 Send 的复合类型也是 Send
    • Rc<T>RefCell<T> 不是 Send ,不能跨线程传递。
  5. Trait std::marker::Sync 表示一个类型是否可以安全的从多个线程里引用。

    • 如果 &TSend,那么 TSync
    • 成员全是 Sync 的复合类型也是 Sync
    • 基本类型是 Sync
    • Rc<T>RefCell<T> 不是 Sync。

17. Object Oriented Programming Features of Rust

  1. Rust 的 struct 成员默认是私有的,只能通过方法访问,因此 Rust 具备 OOP 里的封装语义;
  2. Rust 不支持 OOP 风格的继承来重用代码,只能通过 trait 里的默认方法来实现有限的继承语义;
  3. trait object 使用 dyn SomeTrait 声明,在使用时需要是引用或者智能指针,比如 Vec<Box<dyn Error>>
  4. trait object 用来实现 OOP 的运行时多态,采用 dymaic dispatch,也即运行时才能知道调用哪个方法。
  5. trait object 需要 trait 满足 object safety 约束,最重要的两条:
    • trait 里的方法不能返回 Self 类型,比如 dyn Clone 就不能作为 trait object 使用
    • trait 里不能有泛型参数

18. Patterns and Matching

  1. 可以使用模式匹配的地方:

    match VALUE {
        PATTERN => EXPRESSION,
        PATTERN => EXPRESSION,
        PATTERN => EXPRESSION,
    }
    
    if let PATTERN = EXPRESSION {
    }
    
    while let PATTERN = EXPRESSION {
    }
    
    for PATTERN in EXPRESSION {
    }
    
    let PATTERN = EXPRESSION;
    
    fn print_coordinates(&(x, y): &(i32, i32)) {
        println!("Current location: ({}, {})", x, y);
    }
    
  2. PATTERN 的语法

    P1 | P2      表示「或」
    1..=5				 表示闭区间,只适用于整数和char
    
    
    let Point {x: a, y: b} = Point {x: 1, y: 2};
    let Point {x, y} = Point {x: 1, y: 2};
    
    
    _    				 忽略
    _x				   绑定,忽略未使用变量
    ..			     忽略剩余部分
    
    
    // match guard
    match VALUE {
      PATTERN if CONDITION => EXPRESSION,
    }
    
    
    // 使用 @ 在匹配时赋值
    enum Message {
        Hello { id: i32 },
    }
    let msg = Message::Hello { id: 5 };
    match msg {
        Message::Hello { id: id_variable @ 3..=7 } => {
            println!("Found an id in range: {}", id_variable)
        },
        Message::Hello { id: 10..=12 } => {
            println!("Found an id in another range")
        },
        Message::Hello { id } => {
            println!("Found some other id: {}", id)
        },
    }
    

19. Advanced Features

  1. unsafe 可以获得额外的能力:

    • Dereference a raw pointer
    • Call an unsafe function or method
    • Access or modify a mutable static variable
    • Implement an unsafe trait
    • Access fields of unions
  2. trait 关联类型,默认泛型参数,操作符重载:

    use std::ops::Add;	// std::ops 中的操作符可以重载
    
    /*
    trait Add<RHS=Self> {	// 默认泛型参数
        type Output;    	// 关联类型
    
        fn add(self, rhs: RHS) -> Self::Output;
    }
    */
    
    #[derive(Debug, PartialEq)]
    struct Point {
        x: i32,
        y: i32,
    }
    
    impl Add for Point {
        type Output = Point;
    
        fn add(self, other: Point) -> Point {
            Point {
                x: self.x + other.x,
                y: self.y + other.y,
            }
        }
    }
    
  3. 使用方法全名来区分 struct 以及实现的 trait 的同名函数

    struct Dog;
    trait Animal { ... }
    
    let dog = Dog;
    dog.method()		// 调用 Dog struct 上直接 impl 的方法 fn method(&self)
    Animal::method(&dog)	// 调用 Dog struct 上 impl Animal 的方法 fn method(&self)
    
    Dog::func()			// 调用 Dog struct 上直接 impl 的函数 fn func()
    <Dog as Animal>::func()	// 调用 Dog struct 上 impl Animal 的函数 fn func()
    
  4. supertrait

    trait B: A { ... }   // 实现了 trait B 的 struct 必须也要实现 trait A
    
  5. impl Trait on Type 只有当 Trait 和 Type 至少有一个是当前 crate 定义的时才被允许,如果 Trait 和 Type 都是外部 crate 的,要想扩展 Type 则需要使用 newtype 模式,也就在包装的 tuple struct 类型上实现 trait,Rust 编译器在编译时会自动消除这层包装。如果想要 wrapper 类型包含目标类型的所有方法,则可以实现 Deref trait。

    use std::fmt;
    
    struct Wrapper(Vec<String>);
    
    impl fmt::Display for Wrapper {
        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
            write!(f, "[{}]", self.0.join(", "))
        }
    }
    
    fn main() {
        let w = Wrapper(vec![String::from("hello"), String::from("world")]);
        println!("w = {}", w);
    }
    
  6. type alias: type Result<T> = std::result::Result<T, std::io::Error>;

  7. never type: ! 表示 empty type,比如在 match 的某个分支里使用 continue 或者 panic!,这个分支的返回值类型就是 !,比如在函数里有无限循环 loop { ... },此函数的返回值类型就是 !

  8. trait Sized 表示编译时知道类型大小,特殊语法 ?Sized 表示可能知道也可能不知道类型大小。

    fn foo<T>(t: T) {} // 相当于 fn foo<T: Sized>(t: T) {}
    fn bar<T: ?Sized>(t: &T) {}   // 由于 T 的大小未知,所以用 &T 类型作为参数,也可以用 Box<T>
    
  9. function pointer: fn(n: i32) -> i32,可以省略参数名字,function pointer 实现了 closure trait Fn, FnMut, FnOnce,所以在以函数作为参数时,推荐使用 closure trait 以支持 function pointer 和 closure 两种。但在与 C 语言交互时,只能使用 function pointer,因为 C 语言不支持 closure。

    fn foo(f: impl Fn(i32) -> i32, n: i32) -> i32 {
        f(n)
    }
    
    // 返回 closure:
    // 第一种返回 trait object,允许函数里不同分支返回不同具体类型的closure 或者函数指针;
    // 第二种和第三种类似,只能返回一种具体类型
    // 由于 Box 实现了 Deref trait,所以使用上都可以写 foo()(12)
    fn foo() -> Box<dyn Fn(i32) -> i32> { Box::new(|x| x + x) }
    fn foo() -> impl Fn(i32) -> i32 { Box::new(|x| x + x) }
    fn foo() -> impl Fn(i32) -> i32 { |x| x + x }
    
  10. declarative macro, aka macro by example, aka pattern macro

  11. procedural macro, aka syntax extension, aka compiler plugin

    • custom #[derive] macro
    • attribute-like macro
    • function-like macro

Cheat sheets:

  1. https://cheats.rs/
  2. https://upsuper.github.io/rust-cheatsheet/