Published on

Rust 笔记

其实是在不断的学习 https://doc.rust-lang.org/book/ 的过程中,记录一些重要的概念和代码片段。 (这个版本更友好: https://rust-book.cs.brown.edu/title-page.html)

1.3

命令功能是否生成二进制优化级别适用场景
cargo check只检查代码语法和类型❌ 否-快速验证代码正确性
cargo build编译并生成可执行文件✅ 是Debug开发调试
cargo build --release编译并生成优化后的可执行文件✅ 是Release生产环境部署

3.1

const vs let 区别

特性constlet
可变性❌ 永远不可变✅ 可用 mut 声明可变
类型标注⚠️ 必须显式指定类型✅ 可自动推断类型
作用域🌍 可在任何作用域声明🏠 通常用于局部作用域
初始化要求⏱️ 必须是编译期常量表达式🚀 可以是运行时计算的值
内存分配📌 编译期直接替换🏗️ 运行时分配内存

示例:

const PI: f64 = 3.14159;        // 正确
// const VAR = 10;              // 错误:缺少类型标注
// const mut X: i32 = 5;        // 错误:const不能mut

let x = 10;                     // 类型自动推断为i32
let mut y = 20;                 // 可变绑定

Shadowing vs mut 区别

特性Shadowingmut
类型变更🔄 可改变变量类型❌ 必须保持相同类型
内存分配🆕 每次shadowing创建新绑定♻️ 复用同一内存位置
可变性🎭 新绑定可自由设置可变性🔧 只能在声明时设置可变性
作用域📦 只在当前作用域有效📦 作用域内持续有效

代码示例:

// Shadowing示例
let x = 5;          // i32
let x = "hello";    // &str - 类型改变
let x = x.len();    // usize - 再次改变

// mut示例
let mut y = 10;     // i32
y = 20;             // 合法 - 同类型修改
// y = "world";     // 错误 - 不能改变类型

3.2

1. 标量类型 (Scalar Types)

类型示例说明
整数 (Integer)i32, u8, i64有符号(i)/无符号(u), 8-128位
浮点 (Float)f32, f64单精度/双精度浮点数
布尔 (Boolean)true, false1字节大小
字符 (Char)'A', '🦀'4字节 Unicode 标量值

特点:

  • 所有标量类型在编译期确定大小
  • 默认整数类型是 i32,浮点是 f64
  • char 不同于其他语言的1字节字符

2. 复合类型 (Compound Types)

类型示例说明
元组 (Tuple)(i32, f64, char)固定长度,异构类型,解构访问
数组 (Array)[i32; 5]固定长度,同构类型,栈上分配
切片 (Slice)&[i32]动态长度视图,借用数组的一部分

3.3

函数参数术语

术语说明示例
Parameter形参(函数定义中的参数)fn foo(x: i32)
Argument实参(调用时传入的值)foo(5)

Statement vs Expression

特性Statement (语句)Expression (表达式)
返回值❌ 没有返回值✅ 总是产生一个值
结束标记分号 ; 结尾无分号(除非在语句中使用)
典型示例let x = 5;5 + 5
函数体不能作为函数最后一行可以作为函数返回值

代码示例:

// 语句示例
let x = 6;        // 这是一个statement
fn foo() {}       // 函数定义是statement

// 表达式示例
5 + 5             // 返回10的expression
{                  // 代码块也是expression
    let y = 10;
    y + 1          // 返回11(注意没有分号)
}

4.1

所有权三原则

  1. 每个值有且只有一个所有者 (One owner per value)
  2. 同一时间只能有一个所有者 (Single owner at any time)
  3. 所有者离开作用域时值自动释放 (Drop when out of scope)
概念说明
移动 (Move)赋值操作转移所有权,原变量失效
克隆 (Clone)显式深度拷贝数据
借用 (Borrow)通过引用临时访问数据
作用域 (Scope)变量从声明处开始到当前块结束

典型示例

fn main() {
    // 所有权示例
    let s = String::from("hello");  // s 进入作用域
    takes_ownership(s);             // s 的值移动到函数里
    // println!("{}", s);          // 错误!s 已被移动

    let x = 5;                      // x 进入作用域
    makes_copy(x);                  // x 是i32,自动拷贝
    println!("{}", x);              // 合法!x仍然有效
}

fn takes_ownership(some_string: String) { // some_string 进入作用域
    println!("{}", some_string);
} // some_string 离开作用域,`drop`被自动调用

fn makes_copy(some_integer: i32) { // some_integer 进入作用域
    println!("{}", some_integer);
} // some_integer 离开作用域,无特殊操作

4.2

引用四大原则

  1. 单一可变或多不可变:同一时间,要么只有一个可变引用,要么多个不可变引用
  2. 引用必须有效:引用的数据在其生命周期内必须有效
  3. 作用域不交叉:不可变与可变引用的作用域不能重叠
  4. 显式生命周期:每个引用都有明确的生命周期(作用域范围)

代码示例解析

let mut s = String::from("hello");

// 合法:多个不可变引用共存
let r1 = &s; 
let r2 = &s;
println!("{} and {}", r1, r2); // 关键点:在此结束r1/r2的作用域

// 合法:前面不可变引用已不再使用
let r3 = &mut s; 
println!("{}", r3);

4.3

slice类型

是一个reference类型,不拥有数据.

ownership / borrowing(reference) / slice ===> 保证编译时内存安全!!!


5.1 Struct

struct User {
	field: String,
}
  1. Rust不允许指定field为mutable。要么都是,如
let mut ss = User { field: "ddd" };
  1. 【field init shorthand syntax】,就是当参数和field名称一致的时候,直接使用。
user { field }
  1. 【Struct Update Syntax】 就是使用其他struct 实例来创建新的实例
    let user2 = User {
        active: user1.active,
        username: user1.username,
        email: String::from("another@example.com"),
        sign_in_count: user1.sign_in_count,
    };

修改为:

    let user2 = User {
        email: String::from("another@example.com"),
        ..user1 // 说明,剩下其他的field数据都来自user1,此处是move,因此无法继续使用 user1 的数据
    };
  1. 【tuple struct】
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);

fn main() {
    let black = Color(0, 0, 0);
    let origin = Point(0, 0, 0);
}
  1. 【 unit-like structs 】 Unit-like structs can be useful when you need to implement a trait on some type but don’t have any data that you want to store in the type itself.
struct AlwaysEqual;  // 零大小类型

impl SomeTrait for AlwaysEqual { /* 实现特质 */ }

let subject = AlwaysEqual;

10.1 generic data types: 1、function fn func<T>(p: T);

2、struct

struct Point<T> {
    x: T,
    y: T,
}

3、enum

enum Option<T> {
    Some(T),
    None,
}

4、methods

//impl<T> to specify that we’re implementing methods on the type Point<T>.
impl<T> Point<T> {
    fn x(&self) -> &T {
        &self.x
    }
}
// use the concrete type f32, meaning we don’t declare any types after impl.
impl Point<f32> {
    fn distance_from_origin(&self) -> f32 {
        (self.x.powi(2) + self.y.powi(2)).sqrt()
    }
}

generic 会运行的慢吗? 不会,因为 Monomorphization(单态化) ,Rust实例化在编译期发生,所有,没有runtime cost。 Rust accomplishes this by performing monomorphization of the code that is using generics at compile time. Monomorphization is the process of turning generic code into specific code by filling in the concrete types that are used when compiled.


trait 定义类型的共同行为!!!


coherence or  orphan rule
we can’t implement external traits on external types. For example, we can’t implement the Display trait on Vec<T> within our aggregator crate, because Display and Vec<T> are defined in the standard library and aren’t local to our aggregator crate. 

理解为; One restriction to note with trait implementations is that we can implement a trait on a type only if at least one of the trait or the type is local to our crate. trait 或者 类型,必须有一个是local的才可以!!!

-- 来打破这个约束 using the newtype pattern, which involves creating a new type in a tuple struct.

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);
}

其实,就是抽象。【没有什么类型是加一层抽象解决不了的,如果有,那就再加一层】


trait 做参数:【impl Trait syntax】


pub fn notify(item: &impl Summary) {
    println!("Breaking news! {}", item.summarize());
}

【 trait bound 】

pub fn notify<T: Summary>(item: &T) {
    println!("Breaking news! {}", item.summarize());
}

【 Trait Bounds with the + Syntax】

pub fn notify<T: Summary + Display>(item: &T) {

【Trait Bounds with where Clauses】

fn some_function<T, U>(t: &T, u: &U) -> i32
    where T: Display + Clone,
          U: Clone + Debug
{}

trait 作为返回值 使用 impl Trait syntax 来返回,实现了某个 trait 的实例。

【blanket implementations】 Implementations of a trait on any type that satisfies the trait bounds are called blanket implementations。 就是说每个trait 实现一个block。

impl trait1 for A {}
impl trait2 for A {}

10.3 Rust compiler 用 borrow checker 来比较判断 scope 内的所有 borrow 的数据是否有效。

lifetime annotation 语法 1、lowercase 2、short &'a i32

lifetime in function fn longest<'a>(x: &'a str, y: &'a str) -> &'a str;

lifetime in struct

struct ImportantExcerpt<'a> {
    part: &'a str,
}

Lifetime Elision These aren’t rules for programmers to follow; they’re a set of particular cases that the compiler will consider, and if your code fits these cases, you don’t need to write the lifetimes explicitly.

fn first_word(s: &str) -> &str {
    let bytes = s.as_bytes();

    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return &s[0..i];
        }
    }

    &s[..]
}

所有,这个情况,不用显示的标注声明周期 'a ...


input lifetime function 和 method 的 parameter 的lifetime 叫做: input lifetime function 和 method 的 return value 的lifetime : output lifetime

compiler 使用 3 个rules 来判断 reference 拥有的 lifetime,如果这三个判断都失败了,则抛出error。 1、每个reference parameter,都有自己的 lifetime。 fn foo<'a, 'b>(x: &'a i32, y: &'b i32);

2、如果只有一个 input lifetime,那么所有的output lifetime,都是 input lifetime fn foo<'a>(x: &'a i32) -> &'a i32

3、如果有多个input lifetime,但是其中一个是 &self / &mut self,所以,self 的 lifetime assign to 所有的 output lifetime pameters.


带(&self/&mut self)的叫做: method.


'static lifetime 程序的生命周期

use std::fmt::Display;

fn longest_with_an_announcement<'a, T>(
    x: &'a str,
    y: &'a str,
    ann: T,
) -> &'a str
where
    T: Display,
{
    println!("Announcement! {}", ann);
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

lifetimes are a type of generic!!!


dbg!() 比 debug! 更好用。

The dbg!(..) macro moves the input!


6.1 enum 关联类型

enum IpAddr {
    V4(String),
    V6(String),
}

7.1 crate 定义 A crate is a binary or library. The crate root is a source file that the Rust compiler starts from and makes up the root module of your crat

package 定义 A package is one or more crates that provide a set of functionality. A package contains a Cargo.toml file that describes how to build those crates.

enum 默认 是 public struct 默认是 private


use std::io::Result as IoResult;


8.1 用 enum 封装不同类型

enum SpreadsheetCell {
    Int(i32),
    Float(f64),
    Text(String),
}

let row = vec![
    SpreadsheetCell::Int(3),
    SpreadsheetCell::Text(String::from("blue")),
    SpreadsheetCell::Float(10.12),
];

8.3 hashmap ownership 1、实现了copy trait的类型,如i32,复制数据到hashmap 2、对于自持有的数据,如string,移动到hashmap

如果没有数据,则插入 scores.entry(String::from("Yellow")).or_insert(50);

By default, HashMap uses a hashing function called SipHash that can provide resistance to Denial of Service (DoS) attacks involving hash tables1


基本数据: Vector / Hashmap / Strings


9.1 错误处理 1、不可恢复的用 panic! 2、可以恢复的用 Result


13.1

All closures implement at least one of the traits: closure 通过三种方式来捕获环境变量,以下三个 Fn trait 1、FnOnce 通过获取 ownership 的方式,移动到closure中。 2、FnMut 可以修改 环境变量 3、Fn 通过 borrow 的方式,immutable 使用

其中,move 来说明,怎么捕获环境变量。

// Fn trait 的继承关系

pub trait FnMut<Args>: FnOnce<Args> 
pub trait Fn<Args>: FnMut<Args>

13.2 iterator 是 lazy 的,直到去comsume他才去执行。


14.1 [profile.dev] opt-level = 0

14.2

fn call_with_one<F>(func: F) -> usize
    where F: Fn(usize) -> usize {
    func(1)
}

let double = |x| x * 2;
assert_eq!(call_with_one(double), 2);
assert_eq!(call_with_one(double), 2);



fn do_twice<F>(mut func: F)
    where F: FnMut()
{
    func();
    func();
}

let mut x: usize = 1;
{
    let add_two_to_x = || x += 2;
    do_twice(add_two_to_x);
}

assert_eq!(x, 5);


fn consume_with_relish<F>(func: F)
    where F: FnOnce() -> String
{
    // `func` consumes its captured variables, so it cannot be run more
    // than once.
    println!("Consumed: {}", func());

    // Attempting to invoke `func()` again will throw a `use of moved
    // value` error for `func`.
}

let x = String::from("x");
let consume_and_return_x = || x;
consume_with_relish(consume_and_return_x);
// consume_with_relish(consume_and_return_x); 第二次调用就会出错,因为ownership一变

继承链: Fn -> FnMut -> FnOnce. (-> 继承与)


插播: trait reference trait 定义了接口,可以包含三种类型: 1、function 2、types 3、constant

【trait function不能是 async / const】 async trait functions are not currently supported consider using the async-trait crate: https://crates.io/crates/async-trait

如:

trait Example {
    const CONST_NO_DEFAULT: i32;
    const CONST_WITH_DEFAULT: i32 = 99;
    type TypeNoDefault;
    fn method_without_default(&self);
    fn method_with_default(&self) {}
}

使用

trait Example {
    const CONST_NO_DEFAULT: i32;
    const CONST_WITH_DEFAULT: i32 = 99;
}
struct A;
impl Example for A {
    // trait Example 中的数据必须定义
    const CONST_NO_DEFAULT: i32=100;
}

fn main() {
    println!("{}", <A as Example>::CONST_WITH_DEFAULT);
}

object safe 定义,满足一下约束: 1、所有的supertrait都是object safe的 2、Must not require self: Sized 3、没有associated constants 4、。。。


15 smart pointer 基本用struct 来实现智能指针,同时实现 Deref / Drop traits.. 1、Smart pointers are usually implemented using structs. 2、distinguishes a smart pointer from an ordinary struct is that smart pointers implement the Deref and Drop traits.

Rust 基本智能指针包括:

1、Box<T>
2、Rc<T>
3、Ref<T> / RefMut<T>
4、RefCell<T> => borrow checker 强制到rumtime

Box<T> 的应用场景:
1、封装编译期不确定size的类型
2、大量数据想transfer ownership,确保不会copy的

Deref coercion is a convenience that Rust performs on arguments to functions and methods. Deref coercion works only on types that implement the Deref trait. Deref coercion converts a reference to such a type into a reference to another type.

就是可以在调用方法、函数时候,参数自动解Deref,获取内部的类型。

三种场景:

Rust does deref coercion when it finds types and trait implementations in three cases:

From &T to &U when T: Deref<Target=U>
From &mut T to &mut U when T: DerefMut<Target=U>
From &mut T to &U when T: Deref<Target=U> //Converting one mutable reference to one immutable reference will never break the borrowing rules.

为了可以 multiple ownership,可以用 Rc<T>, 直到引用技术为0,再销毁。Rc<T>只能再单线程中使用。

interior mutability 的应用场景

  • Introducing mutability ‘inside’ of something immutable 就是多个 owner 要修改内部值

  • Implementation details of logically-immutable methods 比如这个类型 span_tree_cache: RefCell<Option<Vec<(i32, i32)>>>, 业务需要

  • Mutating implementations of Clone The clone method is expected to not change the source value, and is declared to take &self, not &mut self. Therefore, any mutation that happens in the clone method must use cell types. 看 Rc 的实现:

use std::cell::Cell;
use std::ptr::NonNull;
use std::process::abort;
use std::marker::PhantomData;

struct Rc<T: ?Sized> {
    ptr: NonNull<RcBox<T>>,
    phantom: PhantomData<RcBox<T>>,
}

struct RcBox<T: ?Sized> {
    strong: Cell<usize>,
    refcount: Cell<usize>,
    value: T,
}

impl<T: ?Sized> Clone for Rc<T> {
    fn clone(&self) -> Rc<T> {
        self.inc_strong();
        Rc {
            ptr: self.ptr,
            phantom: PhantomData,
        }
    }
}

RcBox 就是用了 Cell<usize> 来修改。

【interior mutability’】, in contrast with typical Rust types that exhibit 【‘inherited mutability’】.

Interior mutability 就是Rust允许修改immutable数据。 Mutating the value inside an immutable value is the interior mutability pattern.

inherited mutability

RefCell<T> type represents single ownership over the data it holds,invariants are enforced at runtime.

---

reasons to choose Box<T>, Rc<T>, or RefCell<T>:
1、Rc<T>可以让一份数据,有多个owner,Box、RefCell是单个owner
2Box允许immutable、mutable在compiler time检查、
Rc allow immutable checker at compiler time.
RefCell allow immutale / mutable at runtime.

组合用法:

If you have an Rc<T> that holds a RefCell<T>, you can get a value that can have multiple owners and that you can mutate!
let value = Rc::new(RefCell::new(5));

16/17/18/19

16.2 channel a channel of water, such as a stream or a river.

包含2部分:a transmitter and a receiver,如果有一方drop,则channel关闭。 use std::sync::mpsc; multiple producer, single consumer.

let (sender, receiver) = channel(); 创建 异步channel。


通过 let counter = Arc::new(Mutex::new(0)); 的方式,多个线程来贡献数据! 对应 Rc::new(RefCell::new(0))

Arc<T> 是线程安全的;意思是:
只能保证it thread safe to have multiple ownership of the same data, but it doesn’t add thread safety to its data. 
也就是说,多个owner是可以的,但是不能保证 data 的线程安全。
看实现就知道了。data 没有加锁,也不是atomic。
#[repr(C)]
struct ArcInner<T: ?Sized> {
    strong: atomic::AtomicUsize,
    weak: atomic::AtomicUsize,

    data: T,
}

所以,Arc 要配合同步方法来使用: Mutx<T>, RwLock, atomic。

Because RefCell<T> borrows are dynamic it is possible to attempt to borrow a value that is already mutably borrowed; when this happens it results in thread panic.

【待进一步研究】

Send / Sync

  • The Send trait indicates that a value of this type is safe to [send] from one thread to another.
  • The Sync trait indicates that a value of this type is safe to [share] between multiple threads.

The Send marker trait indicates that ownership of values of the type implementing Send can be transferred between threads.

  • This trait is automatically implemented when the compiler determines it’s appropriate.
  • a non-Send type is the reference-counting pointer rc::Rc.

The Sync marker trait indicates that it is safe for the type implementing Sync to be referenced from multiple threads. In other words, any type T is Sync if &T (an immutable reference to T) is Send, 【meaning the reference can be sent safely to another thread】.

pub unsafe auto trait Sync { }
  • This trait is automatically implemented when the compiler determines it’s appropriate.

  • **As a reminder, the Send marker trait expresses that it is safe to be passed from thread to thread. **

  • Sync expresses that it is safe to have a reference be passed from thread to thread.


关于 static dispathc 和 dynamic dispatch When we use 【trait objects】, Rust must use dynamic dispatch. The compiler doesn’t know all the types that might be used with the code that is using trait objects, so it doesn’t know which method implemented on which type to call. Instead, at runtime, Rust uses the pointers inside the trait object to know which method to call. There is a runtime cost when this lookup happens that doesn’t occur with static dispatch. Dynamic dispatch also prevents the compiler from choosing to inline a method’s code, which in turn prevents some optimizations.


19.1 unsafe 的能力: 1、deference raw pointer 2、调用 unsafe function / method 3、修改 mutable static variable 4、实现 unsafe triat 5、访问 union


raw pointer(类型) 1、 * const T 2、 * mut T

raw pointer 和 reference / smart pointer 区别: 1、可以ignore borrowing rules,可以有多个mutable pointer 指向同一份数据 2、不保证指向有效的内存 3、可以是null 4、不实现 自动cleanup

let mut num = 5;

let r1 = &num as *const i32;
let r2 = &mut num as *mut i32;

In Rust, global variables are called static variables.

constant 和 static 区别 1、static 生命周期 'static 2、static 地址是固定的 3、static 可以被 mutable,但是 unsafe的,同时不能保证没有 data race.


19.2

default type parameters

trait Add<Rhs=Self> {
    type Output;

    fn add(self, rhs: Rhs) -> Self::Output;
}

19.3


fn foo() ->{}

Functions that return never are called diverging functions!


19.4

function pointer 函数指针。 在栈上,也可以用Box包到heap上

fn add_one(x: i32) -> i32 {
    x + 1
}

fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 {
    f(arg) + f(arg)
}

fn main() {
    let answer = do_twice(add_one, 5);

    println!("The answer is: {}", answer);
}

返回 closure

Closures are represented by traits, which means you can’t return closures directly. you’re not allowed to use the function pointer fn as a return type。

We can use a trait object: 返回一个 trait object.

fn returns_closure() -> Box<dyn Fn(i32) -> i32> {
    Box::new(|x| x + 1)
}

trait object 定义

A trait object points to both an instance of a type implementing our specified trait as well as a table used to look up trait methods on that type at runtime. 
创建 trait object
We create a trait object by specifying some sort of pointer, such as a & reference or a Box<T> smart pointer, then the dyn keyword, and then specifying the relevant trait。
用途:
1、trait objects differ from traditional objects in that we can’t add data to a trait object
2、their specific purpose is to allow abstraction across common behavior.
trait Config {
    type T;
    fn sortx(&self) -> Self::T; 
}

struct A;
impl Config for A {
    type T = u32;
    fn sortx(&self) -> Self::T {
        3_u32
    }
}

let obj : Box<dyn Config<T=u32>> = Box::new(A);
let _x = obj.sortx();

associated functions 就是没有 &self、&mut Self的方法


cargo doc --open 生成项目文档,用默认browser打开


cargo tree -p pallet-genesisx 显示这个 package 的dependce


AGT: associate generic type

generic 和 AGT 的区别:

第一点: In short, use generics when you want to type A to be able to implement a trait any number of times for different type parameters, such as in the case of the From trait.

Use associated types if it makes sense for a type to only implement the trait once, such as with Iterator and Deref.

第二点: Associated types are a grouping mechanism, so they should be used when it makes sense to group types together.

trait MySum {
    type Item;
    fn sum<I>(iter: I)
    where
        I: MyIter<Item = Self::Item>;
}

trait MyIter {
    type Item;
    fn next(&self) {}
    fn sum<S>(self)
    where
        S: MySum<Item = Self::Item>;
}

补充:

impl Iterator for Counter {
    type Item = u32;

    fn next(&mut self) -> Option<Self::Item> {
        // --snip--
pub trait Iterator<T> {
    fn next(&mut self) -> Option<T>;
}
The difference is that when using generics, as in Listing 19-13, we must annotate the types in each implementation; because we can also implement Iterator<String> for Counter or any other type, we could have multiple implementations of Iterator for Counter. In other words, when a trait has a generic parameter, it can be implemented for a type multiple times, changing the concrete types of the generic type parameters each time. When we use the next method on Counter, we would have to provide type annotations to indicate which implementation of Iterator we want to use.

With associated types, we don’t need to annotate types because we can’t implement a trait on a type multiple times. In Listing 19-12 with the definition that uses associated types, we can only choose what the type of Item will be once, because there can only be one impl Iterator for Counter. We don’t have to specify that we want an iterator of u32 values everywhere that we call next on Counter.

Monomorphism https://stackoverflow.com/questions/66575869/what-is-the-difference-between-dyn-and-generics

Monomorphism means that the code is generated at compile-time.


"turbofish" syntax. (涡轮鱼;真形象) The :: token is required before the opening < for generic arguments to avoid ambiguity with the less-than operator.

顺序: The order of generic arguments is restricted to lifetime arguments, then type arguments, then const arguments, then equality constraints.

(0..10).collect::<Vec<_>>();
Vec::<u8>::with_capacity(1024);

按照顺序来定义

struct A<'a, T>(&'a T);
impl<'a, T> A<'a, T> {
    pub fn a() {}
}
A::<u32>::a();

self / Self 区别

【self】 resolves the path relative to the current module. self can only be used as the first segment, without a preceding ::.

fn foo() {}
fn bar() {
    self::foo();
}

【Self】 is used to refer to the implementing type within traits and implementations.


trait T {
    type Item;
    const C: i32;
    // `Self` will be whatever type that implements 【`T`】.
    fn new() -> Self;
    // `Self::Item` will be the type alias in the implementation.
    fn f(&self) -> Self::Item;
}
struct S;
impl T for S {
    type Item = i32;
    const C: i32 = 9;
    fn new() -> Self {           // `Self` is the type `S`.
        S
    }
    fn f(&self) -> Self::Item {  // `Self::Item` is the type `i32`.
        Self::C                  // `Self::C` is the constant value `9`.
    }
}

【trait】专题

什么是 auto traits

The Send, Sync, Unpin, UnwindSafe, and RefUnwindSafe traits are auto traits.

The Sized trait indicates that the size of this type is known at compile-time;


【DST】

Sized 相反的是 Dynamically sized types.

Slice 和 trait object 都属于 DST.

指向 DST 的指针是 Sized,但是pointer的大小是指向 sized type 的2倍

  • Pointers to slices also store the number of elements of the slice.
  • Pointers to trait objects also store a pointer to a vtable.

DST可以用在有 ?Sized bound 的地方。 variables, function parameters, const items, and static items must be Sized.


struct A<T, const N: usize>(T);
impl <T, const N: usize> A<T, N> {
    fn r#do() {
        println!("N : {}", N);
    }
}

A::<u32, 3usize>::r#do();

trait Shape { fn area(&self) -> f32; }

&dyn Shape reference is composed of two pointer:

  • a pointer to the object, and
  • a pointer to a virtual table. virtual table exist for each type that implements the trait, but each instance of the same type share the same virtual table.

   &dyn Shape      ╭──────> Rectangle     ╭─> vtable of Shape for Rectangle
 ┏━━━━━━━━━━━━━┓   │       ┏━━━━━━━━━┓    │        ┏━━━━━━━━━┓
 ┃ data        ┠───╯       ┃ w       ┃    │        ┃ area() ┣━━━━━━━━━━━━━┫           ┣━━━━━━━━━┫    │        ┣━━━━━━━━━┫
 ┃ vtable ptr  ┠─────╮     ┃ h       ┃    │        ┃ drop() ┗━━━━━━━━━━━━━┛     │     ┗━━━━━━━━━┛    │        ┣━━━━━━━━━┫
                     ╰────────────────────╯        ┃ size    ┃
                                                   ╏         ╏


---

【协变】主要是用在函数的返回值上, 【逆变】用在函数参数上, 这样的规则也就能遵循【里氏替换原则】. 里氏替换原则通俗的来讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能。

F<T> is covariant over T if T being a subtype of U implies that F<T> is a subtype of F<U> (subtyping "passes through")
F<T> is contravariant over T if T being a subtype of U implies that F<U> is a subtype of F<T>
F<T> is invariant over T otherwise (no subtyping relation can be derived)

subtype

Since 'static outlives the lifetime parameter 'a, 【&'static str】 is a subtype of 【&'a str】.

谁的声明周期长、谁就是 subtype。


Higher-Rank Trait Bounds (HRTBs)

file:///home/ztgx/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/share/doc/rust/html/nomicon/hrtb.html


Lifetime bounds 定义

Lifetime bounds can be applied to types or to other lifetimes. The bound 'a: 'b is usually read as 'a outlives 'b. 'a: 'b means that 'a lasts at least as long as 'b, so a reference &'a () is valid whenever &'b () is valid.

fn f<'a, 'b>(x: &'a i32, mut y: &'b i32) where 'a: 'b {
    y = x;                      // &'a i32 is a subtype of &'b i32 because 'a: 'b
    let r: &'b &'a i32 = &&0;   // &'b &'a i32 is well formed because 'a: 'b
}
i32: 'static and &'static str: 'a are satisfied, but Vec<&'a ()>: 'static is not.

'a is unused within the struct's body, it's unbounded. unbounded lifetimes and types are forbidden in struct definitions.


lifetime elision(消除)

lifetime arguments can be elided in 【function item】, 【function pointer】, and 【closure trait signatures】.

Rules:

  • Each elided lifetime in the parameters becomes a distinct lifetime parameter.
  • If there is exactly one lifetime used in the parameters (elided or not), that lifetime is assigned to all elided output lifetimes.

In method signatures there is another rule

  • If the receiver has type &Self or &mut Self, then the lifetime of that reference to Self is assigned to all elided output lifetime parameters.

Both 【constant and static】 declarations of reference types have implicit 'static lifetimes unless an explicit lifetime is specified.


memory order 就是指定一系列围绕 an atomic operation 的内存访问顺序。 std::memory_order specifies how memory accesses, including regular, non-atomic memory accesses, are to be ordered around an atomic operation.


trait std::clone::Clone / std::marker::Copy 关系

pub trait Copy: Clone {}
  • clone 深拷贝、copy 浅拷贝
  • clone 显式调用、copy 隐式调用
  • clone 是 copy 的 super trait
  • 如果一个类型是 Copy,则 Clone需要 return *self 即可
  • Shared references (&T) are also Copy, so a type can be Copy, even when it holds shared references of types T that are not Copy.
  • 实现了 trait Drop 的类型不能 Copy
  • 实现了 trait Copy 的类型还有:
Function pointer types(fn() -> i32);
Tuple types,(如果每个item都实现了copy)
Closure types, (如果capture no value or 都实现了copy)
  • shared reference always implement Copy
  • mutable reference never implement Copy
struct Generate<T>(fn() -> T);

impl<T> Copy for Generate<T> {}

impl<T> Clone for Generate<T> {
    fn clone(&self) -> Self {
        *self
    }
}

std::pin

pin data 到 memory,不让 move。 场景: 构建自我引用的struct结构。

* Pin<P> wraps a pointer type P, so `Pin<Box<T>>` functions much like a regular Box<T>,  
* Pin<&mut T> is a lot like &mut T.
* Pin<P> does not let clients actually obtain a Box<T> or &mut T to pinned data, which implies that you cannot use operations such as mem::swap
* It is worth reiterating that Pin<P> does not change the fact that a Rust compiler considers all types movable.
* Note that pinning and Unpin only affect the pointed-to type P::Target, not the pointer type P itself that got wrapped in Pin<P>.

type 别名

A type alias defines a new name for an existing type.

  • A type alias to a tuple-struct or unit-struct cannot be used to qualify that type's constructor:
struct MyStruct(u32);

use MyStruct as UseAlias;
type TypeAlias = MyStruct;

let _ = UseAlias(5); // OK
let _ = TypeAlias(5); // Doesn't work

unit-like struct

A unit-like struct is a struct without any fields

struct Cookie;
let c = [Cookie, Cookie {}, Cookie, Cookie {}];

async function 机制

Async functions do no work when called: instead, they capture their arguments into a future. When polled, that future will execute the function's body.

An async function is roughly equivalent to a function that returns impl Future and with an [async move block] as its body:

// Source
async fn example(x: &str) -> usize {
    x.len()
}

等于

// Desugared
fn example<'a>(x: &'a str) -> impl Future<Output = usize> + 'a {
    async move { x.len() }
}

const function

Const functions are not allowed to be 【async】, and cannot use the 【extern function qualifier】.

struct B;
impl B {
    const fn my_const_fn(&self, x: u8) -> u8 {
        let c = x + 1;
        c+3
    }
}

const B_I: B = B;
let _c = B_I.my_const_fn(3);

let b = B;
let x = 3_u8;
let _d = b.my_const_fn(x);

Raw Indentifier 就是把Rust的关键字当成一般字符来使用。

fn r#try () {
    println!("use keyword try.");
}
r#try();

crate, self, super, Self 这四个就不能做Raw Indentifier


Underscore Imports 语法 主要用于引入trait,避免名字冲突。

mod foo {
    pub trait Zoo {
        fn zoo(&self) {
            println!("trait zoo.");
        }
    }

    impl<T> Zoo for T {}
}

use foo::Zoo as _;
struct Zoo;  // Underscore import avoids name conflict with this item.

let z = Zoo;
z.zoo();


06.26 *** 第二遍 Rust 深入学习 ***

Iterator 特点

  • allows you to perform some task on a sequence of items in turn
  • responsible for the logic of iterating over each item
  • iterators are lazy, meaning they have no effect until you call methods that consume the iterator to use it up.
  • starting a variable at index 0

Iterator trait All iterators implement a trait named Iterator that is defined in the standard library.

pub trait Iterator {
    // 这个叫做; associated type
    type Item;

    fn next(&mut self) -> Option<Self::Item>;
}
let v1 = vec![1, 2, 3];

let mut v1_iter = v1.iter();

assert_eq!(v1_iter.next(), Some(&1));

nextfor 的区别:(用for遍历的话,for会获取ownership)

  • Each call to next eats up an item from the iterator.
  • We didn’t need to make v1_iter mutable when we used a for loop because the loop took ownership of v1_iter and made it mutable behind the scenes.

其他方法: iter() produces an iterator over immutable references. into_iter() takes ownership of v1 and returns owned values. iter_mut() iterate over mutable references.

let x = &mut [1, 2, 4];
for elem in x.iter_mut() {
    *elem += 2;
}
assert_eq!(x, &[3, 4, 6]);

Comsume 方法: 【consuming adaptor】 Iterator trait 中其他调用 next 方法的 method 叫做: 【consuming adaptors】。因为 next 是use up the iterator的方法。 比如 sum

let v1 = vec![1, 2, 3];

let v1_iter = v1.iter();

let total: i32 = v1_iter.sum();

assert_eq!(total, 6);

Produce 其他iterator方法: 【iterator adaptor】 允许allow you to change iterators into different kinds of iterators的方法叫做: iterator adaptors。 比如 map

let v1: Vec<i32> = vec![1, 2, 3];

let v2: Vec<_> = v1.iter().map(|x| x + 1).collect();

assert_eq!(v2, vec![2, 3, 4]);

配合 closure 使用 比如 filter , filter 作为 iterator adaptor 使用 closure 创建另一个 iterator。

#[derive(PartialEq, Debug)]
struct Shoe {
    size: u32,
    style: String,
}

fn shoes_in_size(shoes: Vec<Shoe>, shoe_size: u32) -> Vec<Shoe> {
    shoes.into_iter().filter(|s| s.size == shoe_size).collect()
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn filters_by_size() {
        let shoes = vec![
            Shoe {
                size: 10,
                style: String::from("sneaker"),
            },
            Shoe {
                size: 13,
                style: String::from("sandal"),
            },
            Shoe {
                size: 10,
                style: String::from("boot"),
            },
        ];

        let in_my_size = shoes_in_size(shoes, 10);

        assert_eq!(
            in_my_size,
            vec![
                Shoe {
                    size: 10,
                    style: String::from("sneaker")
                },
                Shoe {
                    size: 10,
                    style: String::from("boot")
                },
            ]
        );
    }
}

创建自己的 Iterator With Iterator trait

struct Counter {
    count: u32,
}

impl Counter {
    fn new() -> Counter {
        Counter { count: 0 }
    }
}
impl Iterator for Counter {
    type Item = u32;

    fn next(&mut self) -> Option<Self::Item> {
        if self.count < 5 {
            self.count += 1;
            Some(self.count)
        } else {
            None
        }
    }
}

        let mut counter = Counter::new();

        assert_eq!(counter.next(), Some(1));
        assert_eq!(counter.next(), Some(2));
        assert_eq!(counter.next(), Some(3));
        assert_eq!(counter.next(), Some(4));
        assert_eq!(counter.next(), Some(5));
        assert_eq!(counter.next(), None);

使用其他 trait 方法 zip 合并两个iterator,返回一个tuple skip 跳过n个元素 map new iterator

let sum: u32 = Counter::new()
    .zip(Counter::new().skip(1))
    .map(|(a, b)| a * b)
    .filter(|x| x % 3 == 0)
    .sum();
assert_eq!(18, sum);

Loop 和 Iterator 选谁???

  • Iterators are one of Rust’s zero-cost abstractions, by which we mean using the abstraction imposes no additional runtime overhead.
  • the zero-overhead principle: What you don’t use, you don’t pay for. And further: What you do use, you couldn’t hand code any better.
  • 【Unrolling】 is an optimization that removes the overhead of the loop controlling code and instead generates repetitive code for each iteration of the loop.
  • Closures and iterators are Rust features inspired by functional programming language ideas. They contribute to Rust’s capability to clearly express high-level ideas at low-level performance.
  • This is part of Rust’s goal to strive to provide zero-cost abstractions.

19: Advanced Features

Unsafe Rust 为什么要存在 Unsafe 操作? 1、要编译器更灵活 2、计算机硬件是unsafe的,给Rust操作底层的能力。 3、interfacing with C code 4、when building up safe abstractions that the borrow checker doesn’t understand.

语法:

unsafe {

}

Unsafe superpower

1Dereference a raw pointer
2Call an unsafe function or method
3Access or modify a mutable static variable
4Implement an unsafe trait
5Access fields of unions

unsafe的含义:

  • borrow check 等依然有效, if you use a reference in unsafe code, it will still be checked.
  • 只是给了访问上述5个features,不检查!

Raw pointers: Unsafe Rust has two new types called raw pointers.

  • *const T
  • and *mut T

这里的 * 不是解引用的操作符,是 raw pointer 名称的一部分。 immutable means that the pointer can’t be directly assigned to after being dereferenced.

Raw pointer 主要区别:

  • allowed to ignore the borrowing rules by having both immutable and mutable pointers or multiple mutable pointers to the same location
  • Aren’t guaranteed to point to valid memory
  • Are allowed to be null
  • Don’t implement any automatic cleanup
let mut num = 5;

let r1 = &num as *const i32;
let r2 = &mut num as *mut i32;

unsafe {
    println!("r1 is: {}", *r1);
    println!("r2 is: {}", *r2);
}
  • we use the dereference operator * on a raw pointer that requires an unsafe block.
  • Creating a pointer does no harm; it’s only when we try to access the value that it points at that we might end up dealing with an invalid value.

注意!!! If we instead tried to create an immutable and a mutable reference to num, the code would not have compiled because Rust’s ownership rules don’t allow a mutable reference at the same time as any immutable references.

2、 Calling An Unsafe Function or Method In fact, wrapping unsafe code in a safe function is a common abstraction.

Borrowing different parts of a slice is fundamentally okay because the two slices aren’t overlapping, but Rust isn’t smart enough to know this. When we know code is okay, but Rust doesn’t, it’s time to reach for unsafe code.

3、Accessing or Modifying a Mutable Static Variable

static HELLO_WORLD: &str = "Hello, world!";

fn main() {
    println!("name is: {}", HELLO_WORLD);
}
  • In Rust, global variables are called static variables.
  • Static variables can only store references with the 'static lifetime, which means the Rust compiler can figure out the lifetime and we aren’t required to annotate it explicitly.
  • Accessing an immutable static variable is safe.
  • values in a static variable have a fixed address in memory. Using the value will always access the same data.
  • Another difference between constants and static variables is that static variables can be mutable.
  • Accessing and modifying mutable static variables is unsafe.

4、Implementing an Unsafe Trait A trait is unsafe when at least one of its methods has some invariant(不变量) that the compiler can’t verify.

5、Accessing Fields of a Union

  • A union is similar to a struct, but only one declared field is used in a particular instance at one time
  • Unions are primarily used to interface with unions in C code

10章

10.2 Trait

  • We can use traits to define shared behavior in an abstract way.
  • We can use trait bounds to specify that a generic type can be any type that has certain behavior.
  • Traits are similar to a feature often called interfaces in other languages.

定义 Trait Trait definitions are a way to group method signatures together to define a set of behaviors necessary to accomplish some purpose.

和 regular method 的区别: 需要使用 use 把trait 引过来。

  • The only difference is that the trait has to be brought into scope as well as the types to get the additional trait methods.

孤儿原则: This restriction is part of a property of programs called coherence, and more specifically the orphan rule, so named because the parent type is not present. One restriction to note with trait implementations is that we can implement a trait on a type only if at least one of the trait or the type is local to our crate.

使用默认 trait 实现 impl Trait for X {}

Trait 作为参数: &impl Summary

pub fn notify(item: &impl Summary) {
    println!("Breaking news! {}", item.summarize());
}

trait bound

pub fn notify<T: Summary>(item: &T) {
    println!("Breaking news! {}", item.summarize());
}

Specifying Multiple Trait Bounds with the + Syntax

pub fn notify(item: &(impl Summary + Display)) {
pub fn notify<T: Summary + Display>(item: &T) {

where clause:

fn some_function<T: Display + Clone, U: Clone + Debug>(t: &T, u: &U) -> i32 {
fn some_function<T, U>(t: &T, u: &U) -> i32
    where T: Display + Clone,
          U: Clone + Debug
{

impl trait 作为返回值

fn returns_summarizable() -> impl Summary {

Using the clone function means we’re potentially making more heap allocations in the case of types that own heap data like String, and heap allocations can be slow if we’re working with large amounts of data.

Using Trait Bounds to Conditionally Implement Methods blanket implementations 的定义 We can also conditionally implement a trait for any type that implements another trait. Implementations of a trait on any type that satisfies the trait bounds are called blanket implementations. For example, the standard library implements the ToString trait on any type that implements the Display trait.

impl<T: Display> ToString for T {
    // --snip--
}

Trait 总结:

  • Traits 和 trait bounds 让我们使用 generic type 组合写出简洁的代码
  • 同时告诉编译器,generic type 拥有指定的 behavior
  • 各种约束,编译器在 compile 阶段进行check

第四章 : 理解 ownership 概述: Ownership 是 Rust 最重要的feature!

  • It enables Rust to make memory safety guarantees without needing a garbage collector!

什么是 Ownership? Ownership is a set of rules that governs how a Rust program manages memory. 不同于其他语言,Rust uses a third approach: 【memory is managed through a system of ownership with a set of rules that the compiler checks. 】 If any of the rules are violated, the program won’t compile. None of the features of ownership will slow down your program while it’s running.

Ownership 3条 Rules?? 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.

以String的例子 为什么用String? we want to look at data that is stored on the heap and explore how Rust knows when to clean up that data, and the String type is a great example.

Ways Variables and Data Interact: MOVE Rust will never automatically create “deep” copies of your data. Therefore, any automatic copying can be assumed to be inexpensive in terms of runtime performance.

Ways Variables and Data Interact: Clone 理解为深拷贝,heap 上的数据也会copy!。where the heap data does get copied.

Stack-Only Data: Copy If a type implements the Copy trait, a variable is still valid after assignment to another variable. Rust won’t let us annotate a type with Copy if the type, or any of its parts, has implemented the Drop trait. If the type needs something special to happen when the value goes out of scope and we add the Copy annotation to that type, we’ll get a compile-time error.

Ownership and Functions

  • Passing a variable to a function will move or copy, just as assignment does.
  • Returning values can also transfer ownership.
  • Rust has a feature for using a value without transferring ownership, called references.
#[derive(Debug, Copy, Clone)]
struct PlainX {
    pub x: u32
}

let x = PlainX { x: 0_u32 };
let y = x;
println!("x: {:?}", x);

此处PlainX要实现Copy,必须实现Clone。 You can derive Copy on any type whose parts all implement Copy. A type that implements Copy must also implement Clone, because a type that implements Copy has a trivial implementation of Clone that performs the same task as Copy.


References and Borrowing

什么是Reference: A reference is like a pointer in that it’s an address we can follow to access data stored at that address that is owned by some other variable.

  • they allow you to refer to some value without taking ownership of it.
  • a reference is guaranteed to point to a valid value of a particular type.

什么是 Borrowing: We call the action of creating a reference borrowing.

问题: So what happens if we try to modify something we’re borrowing? it doesn’t work! immutable by default!

问题:A data race is similar to a race condition and happens when these three behaviors occur:

1、Two or more pointers access the same data at the same time. 2、At least one of the pointers is being used to write to the data. 3、There’s no mechanism being used to synchronize access to the data.

Rust prevents this problem by refusing to compile code with data races!

问题: immutable 和 mutable scope 的交叉问题 The ability of the compiler to tell that a reference is no longer being used at a point before the end of the scope is called Non-Lexical Lifetimes (NLL for short),

Dangling References the compiler guarantees that references will never be dangling references: if you have a reference to some data, the compiler will ensure that the data will not go out of scope before the reference to the data does.

The Rules of References 2条规则

  • At any given time, you can have either one mutable reference or any number of immutable references.
  • References must always be valid.
  • mutable can't interject with immutable reference.

The Slice Type 什么是 Slice?

  • Slices let you reference a contiguous sequence of elements in a collection rather than the whole collection.
  • A slice is a kind of reference, so it does not have ownership.

Defining a function to take a string slice instead of a reference to a String makes our API more general and useful without losing any functionality.

fn first_word(s: &str) -> &str;

Slice 组成:

  • storing a reference to the first element
  • and a length

本章总结: Rust 三大件: Ownership 、 borrowing 、 slices 保证了 Rust 编译器的内存安全!!! The concepts of ownership, borrowing, and slices ensure memory safety in Rust programs at compile time.


Variables

immutable variables, constant 的区别: 1、constant 用 const 定义,不可以用mut修饰,必须指明类型 2、Constants can be declared in any scope 3、constants may be set only to a constant expression 4、Rust’s naming convention for constants is to use all uppercase with underscores between words.

Shadowing

you can declare a new variable with the same name as a previous variable. Rustaceans say that the first variable is shadowed by the second。

Shadowing 和 mut 区别: 1、Shadowing 必须用 let 去做重新赋值 2、Shadowing 可以 we can change the type of the value but reuse the same name.


Using Trait Objects That Allow for Values of Different Types

  • A trait object points to both an instance of a type implementing our specified trait
  • as well as a table used to look up trait methods on that type at runtime.

Trait objects aren’t as generally useful as objects in other languages: their specific purpose is to allow abstraction across common behavior.

[duck typing]的概念: in dynamically typed languages: if it walks like a duck and quacks like a duck, then it must be a duck!

Trait Object 的好处: The advantage of using trait objects and Rust’s type system to write code similar to code using duck typing is that we never have to check whether a value implements a particular method at runtime or worry about getting errors if a value doesn’t implement a method but we call it anyway.

Trait Objects Perform Dynamic Dispatch

!!! the compiler generates nongeneric implementations of functions and methods for each concrete type that we use in place of a generic type parameter. !!!

static dispatch: static dispatch, which is when the compiler knows what method you’re calling at compile time.

dynamic dispatch: dynamic dispatch, which is when the compiler can’t tell at compile time which method you’re calling.

When we use trait objects, Rust must use dynamic dispatch.

Rust uses the pointers inside the trait object to know which method to call. There is a runtime cost when this lookup happens that doesn’t occur with static dispatch. Dynamic dispatch also prevents the compiler from choosing to inline a method’s code, which in turn prevents some optimizations.

Monomorphization:(单态化) the process of turning generic code into specific code by filling in the concrete types that are used when compiled. The process of monomorphization makes Rust’s generics extremely efficient at runtime.


fully qualified syntax is defined as follows:

<Type as Trait>::function(receiver_if_method, next_arg, ...);

dyn 和 impl 在trait object上的区别

  • dyn is a prefix of a trait object’s type.

  • The dyn keyword is used to highlight that calls to methods on the associated Trait are dynamically dispatched. To use the trait this way, it must be ‘object safe’.

  • dyn Trait is likely to produce smaller code than impl Trait / generic parameters as the method won’t be duplicated for each concrete type.

  • `impl Trait` only allowed in function and inherent method return types,
    

Traits as Parameters

we use the impl Trait syntax

pub fn notify(item: &impl Summary) {
    println!("Breaking news! {}", item.summarize());
}

Returning Types that Implement Traits

also use the impl Trait syntax in the return position to return a value of some type that implements a trait

fn returns_summarizable() -> impl Summary {
    Tweet {
        username: String::from("horse_ebooks"),
        content: String::from(
            "of course, as you probably already know, people",
        ),
        reply: false,
        retweet: false,
    }
}

Function Pointers

  • The fn type is called a function pointer.
  • Unlike closures, fn is a type rather than a trait
  • one example of where you would want to only accept fn and not closures is when interfacing with external code that doesn’t have closures: C functions can accept functions as arguments, but C doesn’t have closures.
  • Traits are not type!!!
  • Closures are represented by traits, which means you can’t return closures directly.

Closures: Anonymous Functions that Can Capture Their Environment

  • To define structs, enums, or function parameters that use closures, we use generics and trait bounds

  • Storing Closures Using Generic Parameters and the Fn Traits

  • All closures implement at least one of the traits: Fn, FnMut, or FnOnce

  • closures have an additional capability that functions don’t have: they can capture their environment and access variables from the scope in which they’re defined.

  • Closures can capture values from their environment in three ways, which directly map to the three ways a function can take a parameter: taking ownership, borrowing mutably, and borrowing immutably. These are encoded in the three Fn traits as follows:

    • FnOnce consumes the variables it captures from its enclosing scope, known as the closure’s environment. To consume the captured variables, the closure must take ownership of these variables and move them into the closure when it is defined. The Once part of the name represents the fact that the closure can’t take ownership of the same variables more than once, so it can be called only once.
    • FnMut can change the environment because it mutably borrows values.
    • Fn borrows values from the environment immutably.
  • Most of the time when specifying one of the Fn trait bounds, you can start with Fn and the compiler will tell you if you need FnMut or FnOnce based on what happens in the closure body.

  • move closures may still implement Fn or FnMut, even though they capture variables by move. This is because the traits implemented by a closure type are determined by what the closure does with captured values, not how it captures them.

Fearless Concurrency

  • the ownership and type systems are a powerful set of tools to help manage memory safety and concurrency problems!

  • Message-passing concurrency, where channels send messages between threads

    • One major tool Rust has for accomplishing message-sending concurrency is the channel
    • A channel in programming has two halves: a transmitter and a receiver.
    • mpsc stands for multiple producer, single consumer.
  • Shared-state concurrency, where multiple threads have access to some piece of data

  • The move keyword is often used with closures passed to thread::spawn because the closure will then take ownership of the values it uses from the environment

  • Rust can’t tell how long the spawned thread will run, so it doesn’t know if the reference to v will always be valid.

  • The Sync and Send traits, which extend Rust’s concurrency guarantees to user-defined types as well as types provided by the standard library

  • The reason is that thread safety comes with a performance penalty that you only want to pay when you really need to.

What Is Ownership?

  • A String is made up of three parts, shown on the left: a pointer to the memory that holds the contents of the string, a length, and a capacity. This group of data is stored on the stack. On the right is the memory on the heap that holds the contents.

  • But because Rust also invalidates the first variable, instead of calling it a shallow copy, it’s known as a move.

  • there’s a design choice that’s implied by this: Rust will never automatically create “deep” copies of your data. Therefore, any automatic copying can be assumed to be inexpensive in terms of runtime performance.

  • Stack-Only Data: Copy

    • types such as integers that have a known size at compile time are stored entirely on the stack, so copies of the actual values are quick to make.

    • Rust won’t let us annotate a type with Copy if the type, or any of its parts, has implemented the Drop trait.

    • Copy and Drop are exclusive

    • Types that are Copy get implicitly duplicated by the compiler, making it very hard to predict when, and how often destructors will be executed. As such, these types cannot have destructors.

The Slice Type

  • Slices let you reference a contiguous sequence of elements in a collection rather than the whole collection.

  • A slice is a kind of reference, so it does not have ownership.

  • the slice data structure stores the starting position and the length of the slice,

  •  let hello = &s[0..5];
    
  • String slice range indices must occur at valid UTF-8 character boundaries.

  • “string slice” is written as &str

  • string literal are slice. let s = "Hello, world!";

  • This is also why string literals are immutable; &str is an immutable reference.

  • Defining a function to take a string slice instead of a reference to a String makes our API more general and useful without losing any functionality.

  • The concepts of ownership, borrowing, and slices ensure memory safety in Rust programs at compile time.

Using Trait Objects That Allow for Values of Different Types

  • A trait object points to both an instance of a type implementing our specified trait as well as a table used to look up trait methods on that type at runtime. We create a trait object by specifying some sort of pointer, such as a & reference or a Box<T> smart pointer, then the dyn keyword, and then specifying the relevant trait.
  • Due to Rust’s need to know certain details, such as how much space to allocate for a value of a particular type, there is a corner of its type system that can be confusing: the concept of dynamically sized types
  • Every trait is a dynamically sized type we can refer to by using the name of the trait.
  • we mentioned that to use traits as trait objects, we must put them behind a pointer, such as &dyn Trait or Box<dyn Trait> (Rc<dyn Trait> would work too).
  • In general, this is the way in which dynamically sized types are used in Rust: they have an extra bit of metadata that stores the size of the dynamic information. The golden rule of dynamically sized types is that we must always put values of dynamically sized types behind a pointer of some kind.

Visibility and Privacy

  • By default, everything is private
  • with two exceptions: Associated items in a pub Trait are public by default; Enum variants in a pub enum are also public by default.
  • When an item is declared as pub, it can be thought of as being accessible to the outside world.

Using Trait Objects That Allow for Values of Different Types

  • Using trait objects to store values of different types that implement the same trait
  • The advantage of using trait objects and Rust’s type system to write code similar to code using duck typing is that we never have to check whether a value implements a particular method at runtime or worry about getting errors if a value doesn’t implement a method but we call it anyway.
  • when we use trait bounds on generics: the compiler generates nongeneric implementations of functions and methods for each concrete type that we use in place of a generic type parameter.
  • The code that results from monomorphization is doing static dispatch, which is when the compiler knows what method you’re calling at compile time.
  • dynamic dispatch, which is when the compiler can’t tell at compile time which method you’re calling. In dynamic dispatch cases, the compiler emits code that at runtime will figure out which method to call.
  • When we use trait objects, Rust must use dynamic dispatch.
  • at runtime, Rust uses the pointers inside the trait object to know which method to call.
  • Dynamic dispatch also prevents the compiler from choosing to inline a method’s code, which in turn prevents some optimizations.
  • There is a runtime cost when this lookup happens that doesn’t occur with static dispatch.

Generic Data Types

  • In Function Defination

    • fn largest<T>(list: &[T]) -> T
  • In Struct Defination

  • In Enum Defination

  • In Method Defination

    • impl<T> Point<T> {
          fn x(&self) -> &T {
              &self.x
          }
      }
      
    • Because this is declaring the generic again, we could have chosen a different name for the generic parameter than the generic parameter declared in the struct definition, but using the same name is conventional.

  • Performance of Code using generics

    • Rust accomplishes this by performing monomorphization of the code that is using generics at compile time.
    • Monomorphization(单态化) is the process of turning generic code into specific code by filling in the concrete types that are used when compiled.
    • the compiler looks at all the places where generic code is called and generates code for the concrete types the generic code is called with.
    • When Rust compiles this code, it performs monomorphization.
    • Rust compiles generic code into code that specifies the type in each instance, we pay no runtime cost for using generics.

Traits: Defining Shared Behavior

  • Trait as parameter

  • pub fn notify(item: &impl Summary)
    
  • This parameter accepts any type that implements the specified trait.

  • Trait Bound Syntax

  • pub fn notify<T: Summary>(item: &T)
    
  • The impl Trait syntax is convenient and makes for more concise code in simple cases. The trait bound syntax can express more complexity in other cases.

  • Returning type that impl Traits

  • fn returns_summarizable() -> impl Summary
    
  • We can also use the impl Trait syntax in the return position to return a value of some type that implements a trait

  • Another kind of generic that we’ve already been using is called lifetimes.

Validating References with Lifetimes

  • every reference in Rust has a lifetime, which is the scope for which that reference is valid.
  • Rust requires us to annotate the relationships using generic lifetime parameters to ensure the actual references used at runtime will definitely be valid.
  • The main aim of lifetimes is to prevent dangling references.
  • its scope is larger, we say that it “lives longer.”
  • The Rust compiler has a borrow checker that compares scopes to determine whether all borrows are valid.

The Rules of References

  • At any given time, you can have either one mutable reference or any number of immutable references.
  • References must always be valid.

Dynamically Sized Types and the Sized Trait

  • these types let us write code using values whose size we can know only at runtime.

  • not &str, but str on its own, is a DST.

  • Rust needs to know how much memory to allocate for any value of a particular type, and all values of a type must use the same amount of memory.

  • This is why it’s not possible to create a variable holding a dynamically sized type.

  • all local variables must have a statically known size
    
  • the trait `Sized` is not implemented for `str`
    
  • In this case, you already know the answer: we make the types of s1 and s2 a &str rather than a str.

  • &str is two values: the address of the str and its length.

  • it’s twice the length of a usize. That is, we always know the size of a &str, no matter how long the string it refers to is.

Implicit Deref Coercions with Functions and Methods

  • Deref coercion is a convenience that Rust performs on arguments to functions and methods.

  • Deref coercion works only on types that implement the Deref trait.

  • Deref coercion converts a reference to such a type into a reference to another type.

  • Deref coercion happens automatically when we pass a reference to a particular type’s value as an argument to a function or method that doesn’t match the parameter type in the function or method definition.

  • use the Deref trait to override the * operator on immutable references

  • use the DerefMut trait to override the * operator on mutable references.

  • From &T to &U when T: Deref<Target=U>
    From &mut T to &mut U when T: DerefMut<Target=U>
    From &mut T to &U when T: Deref<Target=U> // 下面是解释
    
  • Rust will also coerce a mutable reference to an immutable one. But the reverse is not possible: immutable references will never coerce to mutable references. Because of the borrowing rules, if you have a mutable reference, that mutable reference must be the only reference to that data (otherwise, the program wouldn’t compile). Converting one mutable reference to one immutable reference will never break the borrowing rules.

Smart Pointers

  • The most common kind of pointer in Rust is a reference
  • References are indicated by the & symbol and borrow the value they point to.
  • Smart pointers, on the other hand, are data structures that not only act like a pointer but also have additional metadata and capabilities
  • an additional difference between references and smart pointers is that references are pointers that only borrow data; in contrast, in many cases, smart pointers own the data they point to.
  • We’ve already encountered a few smart pointers in this book, such as String and Vec<T> in Chapter 8, although we didn’t call them smart pointers at the time. Both these types count as smart pointers because they own some memory and allow you to manipulate it. They also have metadata (such as their capacity) and extra capabilities or guarantees (such as with String ensuring its data will always be valid UTF-8).
  • Smart pointers are usually implemented using structs.
  • The characteristic that distinguishes a smart pointer from an ordinary struct is that smart pointers implement the Deref and Drop traits.
  • The Deref trait allows an instance of the smart pointer struct to behave like a reference so you can write code that works with either references or smart pointers. The Drop trait allows you to customize the code that is run when an instance of the smart pointer goes out of scope.
  • Box<T> for allocating values on the heap
  • Rc<T>, a reference counting type that enables multiple ownership
  • Ref<T> and RefMut<T>, accessed through RefCell<T>, a type that enforces the borrowing rules at runtime instead of compile time
  • interior mutability pattern where an immutable type exposes an API for mutating an interior value.

Using Box to Point to Data on the Heap

  • The most straightforward smart pointer is a box

  • Boxes allow you to store data on the heap rather than the stack.

  • What remains on the stack is the pointer to the heap data.

  • Boxes don’t have performance overhead

  • You’ll use them most often in these situations:

  • * When you have a type whose size can’t be known at compile time and you want to use a value of that type in a context that requires an exact size
    * When you have a large amount of data and you want to transfer ownership but ensure the data won’t be copied when you do so
    * When you want to own a value and you care only that it’s a type that implements a particular trait rather than being of a specific type
    
  • The Box<T> type is a smart pointer because it implements the Deref trait, which allows Box<T> values to be treated like references.

  • When a Box<T> value goes out of scope, the heap data that the box is pointing to is cleaned up as well because of the Drop trait implementation.

Running Code on Cleanup with the Drop Trait

  • Drop, which lets you customize what happens when a value is about to go out of scope.
  • We’re introducing Drop in the context of smart pointers because the functionality of the Drop trait is almost always used when implementing a smart pointer.
  • Specify the code to run when a value goes out of scope by implementing the Drop trait.
  • Variables are dropped in the reverse order of their creation
  • std::mem::drop
  • The drop function in Rust is one particular destructor.
  • Rust doesn’t let us call drop explicitly because Rust would still automatically call drop on the value at the end of main. This would be a double free error because Rust would be trying to clean up the same value twice.

Rc, the Reference Counted Smart Pointer

  • To enable multiple ownership, Rust has a type called Rc<T>, which is an abbreviation for reference counting.
  • The Rc<T> type keeps track of the number of references to a value to determine whether or not the value is still in use.
  • If there are zero references to a value, the value can be cleaned up without any references becoming invalid.
  • Imagine Rc<T> as a TV in a family room.
  • We use the Rc<T> type when we want to allocate some data on the heap for multiple parts of our program to read and we can’t determine at compile time which part will finish using the data last.
  • Rc<T> is only for use in single-threaded scenarios.

Using Rc to Share Data

  • We need to add a use statement to bring Rc<T> into scope because it’s not in the prelude.
  • We could have called a.clone() rather than Rc::clone(&a), but Rust’s convention is to use Rc::clone
  • The implementation of Rc::clone doesn’t make a deep copy of all the data like most types’ implementations of clone do.
  • Rc::clone only increments the reference count, which doesn’t take much time.
  • By using Rc::clone for reference counting, we can visually distinguish between the deep-copy kinds of clones and the kinds of clones that increase the reference count. When looking for performance problems in the code, we only need to consider the deep-copy clones and can disregard calls to Rc::clone.
  • the implementation of the Drop trait decreases the reference count automatically when an Rc<T> value goes out of scope.
  • Via immutable references, Rc<T> allows you to share data between multiple parts of your program for reading only.

RefCell and the Interior Mutability Pattern

  • Interior mutability is a design pattern in Rust that allows you to mutate data even when there are immutable references to that data;

  • To mutate data, the pattern uses unsafe code inside a data structure to bend Rust’s usual rules that govern mutation and borrowing.

  • RefCell<T> type represents single ownership over the data it holds

  • With RefCell<T>, these invariants are enforced at runtime.

  • With RefCell<T>, if you break these rules, your program will panic and exit.

  • borrowing rules

  • At any given time, you can have either (but not both of) one mutable reference or any number of immutable references.
    References must always be valid.
    
  • The advantage of checking the borrowing rules at runtime instead is that certain memory-safe scenarios are then allowed, whereas they are disallowed by the compile-time checks.

  • Halting Problem

  • The RefCell<T> type is useful when you’re sure your code follows the borrowing rules but the compiler is unable to understand and guarantee that.

  • RefCell<T> is only for use in single-threaded scenarios and will give you a compile-time error if you try using it in a multithreaded context.

  • a recap of the reasons to choose Box<T>, Rc<T>, or RefCell<T>:

  • 1/ Rc<T> enables multiple owners of the same data; Box<T> and RefCell<T> have single owners.
    2/ Box<T> allows immutable or mutable borrows checked at compile time; Rc<T> allows only immutable borrows checked at compile time; RefCell<T> allows immutable or mutable borrows checked at runtime.
    3/ RefCell<T> allows mutable borrows checked at runtime, you can mutate the value inside the RefCell<T> even when the RefCell<T> is immutable.
    
  • Mutating the value inside an immutable value is the interior mutability pattern.

  • When creating immutable and mutable references, we use the & and &mut syntax, respectively. With RefCell<T>, we use the borrow and borrow_mut methods, which are part of the safe API that belongs to RefCell<T>.

  • The borrow method returns the smart pointer type Ref<T>, and borrow_mut returns the smart pointer type RefMut<T>.

  • The RefCell<T> keeps track of how many Ref<T> and RefMut<T> smart pointers are currently active. Every time we call borrow, the RefCell<T> increases its count of how many immutable borrows are active.

  •         fn send(&self, message: &str) {
                let mut one_borrow = self.sent_messages.borrow_mut();
                let mut two_borrow = self.sent_messages.borrow_mut();
      
                one_borrow.push(String::from(message));
                two_borrow.push(String::from(message));
            }
      
    This makes two mutable references in the same scope, which isn’t allowed. 
    
  • Having Multiple Owners of Mutable Data by Combining Rc and RefCell

    • A common way to use RefCell<T> is in combination with Rc<T>.
    • Cell<T> implements interior mutability by moving values in and out of the Cell<T>.

Reference Cycles Can Leak Memory

  • We can see that Rust allows memory leaks by using Rc<T> and RefCell<T>: it’s possible to create references where items refer to each other in a cycle. This creates memory leaks because the reference count of each item in the cycle will never reach 0, and the values will never be dropped.

  • Creating reference cycles is not easily done, but it’s not impossible either.

  • If you have RefCell<T> values that contain Rc<T> values or similar nested combinations of types with interior mutability and reference counting, you must ensure that you don’t create cycles;

  • Preventing Reference Cycles: Turning an Rc into a Weak

    • You can also create a weak reference to the value within an Rc<T> instance by calling Rc::downgrade and passing a reference to the Rc<T>.

    • Rc::clone` increases the `strong_count` of an `Rc<T>
      
    • calling Rc::downgrade increases the weak_count by 1.

    • Weak references don’t express an ownership relationship.

    • to do anything with the value that a Weak<T> is pointing to, you must make sure the value still exists. Do this by calling the upgrade method on a Weak<T> instance, which will return an Option<Rc<T>>.

    • upgrade returns an Option<Rc<T>>, Rust will ensure that the Some case and the None case are handled, and there won’t be an invalid pointer.

Tips

& vs ref & denotes that your pattern expects a reference to an object. Hence & is a part of said pattern: &Foo matches different objects than Foo does.

ref indicates that you want a reference to an unpacked value. It is not matched against: Foo(ref foo) matches the same objects as Foo(foo).


THE END