Crate、Mod、可见性、文件结构

文件结构规则

拆分文件

Rust一般库中文件名/文件夹都表示mod (测试文件规则比较特殊)

  • modfoo在其父modbar中声明
  • 如果modfoo没有子mod,将其实现放在foo.rs文件中
  • 若modfoo有子mod,创建文件夹foo,将其实现放在 foo/mod.rs

以上是文件拆分规则,也可以不拆分文件

库Crate

库crate中lib.rs相当于该crate顶层mod(根mod)

  • 所有的mod直接或间接(祖先mod)声明于此,否则不能识别
  • 从引用库crate的外部crate角度来看,其名称和库crate同名 extern crate crate_name;的同时就use crate_name;, 此时可将引用其的mod视为根mod的父mod

库、二进制Crate

crate中可以同时有lib.rs和main.rs,此时库crate和二进制 crate应该看作相互独立

  • 在两处都使用mod关键字声明定义mod(不能在main.rs 中使用use声明使用mod)

  • main.rs中使用extern crate crate_name引入 “外部”库crate

可见性规则

Mod默认私有

  • 默认仅crate内部可见
    • 父mod处直接可用
    • 兄弟mod、子mod可以通过“回溯“声明使用
  • pub声明为公用后,对外部crate也可见

Fn默认私有

  • 默认仅mod“内部”可见(包括后代mod)
    • 当前mod内直接可用
    • 子mod可以通过“回溯”声明可用
  • pub声明为公用后,对外部mod也可见

说明

  • 项(mod、fn)的声明使用路径都是相对于当前项,即默认调用 其后代项(mod、fn),通过以下“回溯”方式调用非直接后代项
    • super直接父mod路径起始:super::child_mod
    • ::根mod起始:::child_mod
  • fn和mod的可见规则相似的,只是注意:fn是否可见只与mod有关 ,mod是否可见只有crate有关。从这个意义上说,crate不能 看作是“大号“的mod

相关关键字

好像都是单一用途(意义),罕见

  • extern引入外部crate(同时包含use crate_name;
  • crate:标记外部crate
  • mod/fn声明定义(注册)mod/fn(同crate内仅一次 ,位于其父mod处)
  • use声明使用项(mod、fn),用于缩略代码

Rust 错误(Panic)处理规范

panic!与不可预期(不可恢复)错误

panic!时程序默认开始展开(unwinding)、回溯栈并清理函数据

如果希望二进制文件尽量小,可以选择“终止(abort)”,此时 程序内存由操作系统进行清理,在Cargo.toml中添加

[profile]
panic='abort'

[profile.release]
panic='abort'

前者是配置debug时,后者配置release版本

Result与潜在(可预期、可恢复)错误

Result枚举类型

Result<T, E>{
    Ok<T>,
    Err<E>,
}
  • T:成功时Ok成员中的数据类型
  • E:失败时Err成员中返回的数据类型

直接处理

  • Result值进行模式匹配,分别处理

    let f = File::open(“hello.txt”); let mut f = match f { Ok(file) => file, Err(error) => panic!(“error:{:?}”, error), }

  • 使用Result上定义的方法(类似以上)

    • Result.unwrap()

      • T = Ok<T>.unwrap()
      • Err<E>.unwrap()使用默认信息调用panic
    • Result.expect(&str)

      • T = Ok<T>.expect(&str)
      • Err<E>.expect(&str)使用&str调用!panic
    • Result.unwrap_or_else

      Result.unwrap_or_else(|err|{

        clojure...
      

      })

      • T = Ok<T>.unwrap_or_else()
      • Err<E>.unwrap_or_else()E作为闭包参数调用 闭包
    • Result.is_err()

      • False = Ok<T>.is_err()
      • True = Err<E>.is_err()

传播错误(Propagating)

Result对象进行匹配,提前返回Err<E>,需要注意返回值 类型问题,尤其是在可能存在多处潜在错误需要返回

let f = File:open("hello.txt");
let mut f match f {
    Ok(file) => file,
    Err(error) => return Err(error),
}

?简略写法(效果同上)

let mut f = File::open("hello.txt")?
  • ?会把Err(error)传递给from函数(定义在标准库From trait中),将错误从转换为函数返回值中的类型,潜在 错误类型都实现了from函数
  • ?只能用于返回值为Result类型的函数内,因为其”返回值” 就是Err(E)(如果有)

Rust 所有权、引用、生命周期

变量、值、所有权

变量、值

  • 变量:用来代表值进行操作、没有对应内存空间的字符串, 如:a,b
  • 值:在内存中有对应的空间,如:5,”asdf”

变量默认不可变

  • var变量绑定的值1不能更改,对应的内存数据不能改变

    • 不允许赋值操作,但是变量的声明和绑定可以分开,即使 声明不可变变量,也可以之后绑定值,注意区分

      1
      2
      3
      4
      5
      6
      7
      8
      fn ret_int() -> i32{ 5 }
      // 以下代码可编译,且正确
      let num = &mut ret_int();
      *num += 1;
      // 以下不可
      let num;
      num = &mut ret_int();
      *num += 1;

      todo

    • 不允许获取可变引用、所有权转移给可变变量(对函数 即限制参数类型,类似于默认const)

    若其中包含(或就是)引用,引用值是可以更改的

    1
    2
    let ref = &mut ori;
    *ref += 1;
  • 但是变量var可以重新绑定为其他的值

    • 虽然值1不能更改,但是var变量可以绑定其他值

      1
      2
      let var = 3;
      let var = 2;
    • 此时虽然值1虽然无法被访问、使用,但是离开作用域 之前不会被丢弃,只是被“隐藏”

所有权规则

  • 每个值(内存)都有一个称为所有者(owner)的变量

  • 值(内存)有且只有一个所有者

    • 如果多个变量拥有某值(内存)所有权,有可能会多次释放 同一内存,造成内存二次污染
    • rust中只有一个变量拥有所有权避免内存污染问题
  • 所有者(变量)离开作用域,值将被丢弃(内存被回收) (rust为其调用drop函数)

    在生命周期结束时释放资源的方法在C++称为RAII (Resource Aquistion Is Initialization),这里的 initialization是指对资源跟踪、管理初始化,RAII就是 将对象(变量)同资源生命周期相关联,在C++中体现为 析构函数

  • rust的所有权管理是编译是进行检查,没有runtime性能损失
  • 相较于gc(垃圾回收)性能影响较小
  • 相较于手动分配、释放内存不容易代码疏忽导致的内存问题

所有权转移

DropCopytrait

  • Drop:值离开作用域时rust自动调用
  • Copy:赋值时调用,赋值之后原变量仍然可以继续使用

需要分配内存、本身就是某种资源形式不会实现Copy trait,实现 Copy trait类型有

  • 存储在stack上的类型,值拷贝速度快,定长
  • 整型、bool型、浮点型这样(标量)原生数据类型
  • 所有元素都copy的tuple

rust不允许任何类型同时同时实现DropCopytrait

所有权转移

对于没有实现Copytrait的类型:赋值(包括函数传参、返回值) 操作会将原变量所有权转移(move)给新变量,之后不允许使用 原变量,编译时即报错,这样就避免了同时释放同一内存造成 二次污染

有些情况下所有权不允许转移,如vec中元素

实现了Copytrait的类型,赋值(包括函数传参)操作将按照 Copytrait复制一个新值,将新值(包括所有权)赋给新变量, 如此原变量可以之后继续使用,没有违反rust的所有权规则,因为 实际上两个值(内存)

Reference(引用)

引用(references,&,获取引用作为函数参数称为借用

单一所有权情况下,仅仅想使用值而不获取所有权,尤其是函数 传参,虽然可以获取所有权之后再将所有权转移,但是操作麻烦, 而且函数返回值可能有其他用途

引用特点

  • 引用允许变量使用值但是不获取所有权
  • 引用离开作用域时不会丢弃其指向的值,不会内存二次污染
  • 分为可变引用、不可变引用,类似于变量绑定

引用规则

  • 任意时间内,特定作用域、特定变量只允许
    • 一个可变引用
    • 任意数量不可变引用
  • 引用必须总是有效的

注意:获取可变引用引用赋值给可变变量的区别

1
2
3
4
5
6
let m = 5;
let mut n = &m;
//将引用赋值给可变变量
let n = &mut m;
//获取可变引用赋值给变量,这里会报错,因为`m`是不可变
//变量,不允许通过其获取可变引用

规则1

第一条规则避免以下情况导致的数据竞争

  • 多个指针可以访问同一数据
  • 至少有一个指针可以写入数据
  • 没有有效的同步数据访问机制

这条规则在显式的赋值、声明易发现、遵守,需要注意的是

  • 函数调用创建可变引用
  • 自运算符创建可变引用(+=*=

规则2

rust会在编译时检查引用是否有效,即引用是否是悬垂指针

悬垂指针:指向的内存已经被分配给其他所有者或值被丢弃, 常见于函数中返回局部变量

对于rust中的“变量(有所有权)”而言则不存此问题

  • 赋值操作要么转移所有权,值不会被丢弃
  • 要么实现copytrait,返回新值

解引用

rust中引用更像c中的指针

  • 引用有对应的解引用(dereferance),且“多层次”引用也需要 ”多层次“解引用

  • 教程上的引用附图也是引用“指向”原“变量”,不是“值(内存)”

自动引用和解引用

  • 方法中self
  • +自己实现了解引用(不知道是否算自动解引用)
    1
    2
    impl<'a> Add<&'a i32> for i32{}
    impl Add<i32> for i32{}
  • todo

生命周期

  • rust每个引用都有生命周期,即引用保持有效的作用域 (避免悬垂引用)
  • 大部分情况下,生命周期是隐含、可推断的 (类似于类型可推断)

    • 借用检查器(编译器一部分)可以分析函数代码得到引用的 生命周期,通过作用域确保借用有效
    • 但是函数被调用、被函数之外的代码引用时,每次生命周期 都不一样,rust无法分析
  • 有时也会出现引用生命周期以一些不同的方式相关联,此时需要 使用泛型生命周期参数标注关系

生命周期注解语法

  • 生命周期参数必须以”’“开头(和一般泛型参数区别)
  • 参数名称通常小写
  • 位于引用”&“之后,”mut“(如果存在)之前

函数生命周期注解

只存在于函数签名

1
2
3
4
5
6
7
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str{
if x.len() > y.len(){
x
}else{
y
}
}
  • 生命周期注解并不改变参数、返回值的生命周期,只是在 函数签名中增加了生命周期“协议”

    • 函数体中返回值不遵守“协议”,函数体编译错误
    • 传参、返回值接收变量不遵守“协议”,调用处编译错误
    • 生命周期注解是用于联系函数不同参数和返回值的生命 周期,一旦形成某种联系,编译器就能获取足够信息判断 引用是否有效(是否内存安全、产生悬垂指针)
  • 生命周期注解是为了保证函数返回值引用有效,同函数 用途有关,因此以下签名也可以通过编译

    1
    2
    3
    fn longest<'a>(x: &'a str, y: &str) -> &a' str{
    x
    }

生命周期注解省略规则

  • 每个是引用的参数都有自己的输入生命周期参数
  • 如果只有一个输入生命周期参数,会被赋予所有输出生命周期 参数
  • 方法存在多个输入生命周期参数,且首个参数self 为引用(&self&mut self),将其生命周期参数赋给 所有输出生命周期参数

编译器检查完以上三条规则之后,所有引用均有生命周期参数,则 无需额外生命周期注解,但是若函数体返回值不遵守“协议”,仍 无法编译通过

1
2
3
4
5
6
7
impl<'a> stct<'a>{
fn other_str(&self, &str1) -> &str{
str1
}
}
// 检查完规则之后,所有引用均有注解,但是
// 函数体中的生命周期和签名中不一致

这个例子说明生命周期注解不只是“注解”,是真的需要例子考虑 返回值的生命周期

结构体生命周期注解

在结构体成员为引用时需要增加生命周期注解

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

此类结构体在实现方法时不能省略生命周期注解,方法签名可根据 规则省略注解

1
2
3
4
impl<'a> ImportantExcerpt<'a>{
fn (&self){
}
}

泛型结构体(枚举)作为函数参数、返回值类型时,替换泛型参数 为引用时也需要添加生命周期注解

1
2
fn new(args: &[String]) -> Result<Config, &'a str>{}
fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str>{}

静态生命周期('static

  • 存活于整个程序生命期间
  • 所有的&str(字符串字面值)都拥有'static生命周期
  • 可以用于指定引用的生命周期,但是使用之前三思,应先考虑 悬垂引用、生命周期不匹配的问题

高级生命周期

Lifetime Subtyping

生命周期子类型:确保某个生命周期长于另一个生命周期

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
struct Context<'s>(&'s str);
struct Parser<'c, 's: 'c>{
//`'s: 'c`声明一个不短于`'c`的生命周期`'s`
context: &'c Context<'s>;
}
impl<'c, 's: 'c> Parser<'c, 's: 'c>{
fn parse(&self) -> Result<(), &'s str>{
//根据生命周期省略规则,若`'s`省略,则赋予`&self`的
//生命周期
//使用生命周期子类型语法,指定(要求)`&str`生命周期
//长于`&Context`
Err(&self.context.0[1..]
//这里没有考虑字符串切片的有效性,如果这个切片不是
//有效的unicode字符串(utf8字节序列),会panic
}
}
fn parse_context(context: Context) -> Result<(), &str>{
//方法获取`context`的所有权
Parser{ context: &Context }.parse()
//`&Context`的生命周期只有整个函数内
//函数体中的返回值是`context.0[1..]`,为保证返回值有效,
//其生命周期必须长于整个函数
//返回值中的`&str`类型的生命周期是`'s`,长于context`'c`
//满足返回值的生命周期长于函数的要求,能编译通过
}

Lifetime Bounds

生命周期bounds:帮助Rust验证泛型引用不会比其引用的数据存在 更久

1
2
3
4
5
struct Ref<'a, T: 'a> { &'a T };
//为`T`增加生命周期bound,指定`T`引用的生命周期不短于
//`'a`,保证结构体成员有效
struct StaticRef<T: 'static> { &'static T };
//限制`T`为只拥有`'static`生命周期的引用或没有引用的类型

trait对象生命周期推断

1
2
3
4
5
6
7
8
9
trait Red {}
struct Ball<'a> {
diameter: &'a i32,
}
impl<'a> Red for Ball<'a> {}
fn main(){
let num = 5;
let obj = Box::new(Ball {diameter: &num}) as Box<Red>;
}

以上代码能编译通过,因为生命周期和trait对象必须遵守:

  • trait对象默认的生命周期为'static
  • 若有&'a X&'a mut x,则默认生命周期为'a
  • 如有T: 'a从句,则默认生命周期为'a
  • 若有多个类似T: 'a从句,则需明确指定trait对象生命周期, Box<Red + 'a>Box<Red + 'static>

    todo

正如其他bound,任何Redtrait的实现内部包含引用,必须拥有和 trait对象bound中所指定的相同的生命周期

Rust 测试

测试常用宏、属性注解

  • assert!(exp,...)
  • assert_eq!(val1, val2,...)
  • assert_ne!(val1, val2,...) 以上宏均可以传入自定义信息,所有的...中参数都将传递给 format!

属性注解

  • #[test]$>cargo test时将测试此函数

  • #[ignore]:除非指定,否则$>cargo test默认不测试此 函数(仍需和#[test]注解)

  • #[should_panic(expected=&str)]:测试中出现panic测试 通过,可以传递expected参数,当参数为panic信息的起始 子串才通过

cargo test命令

命令行参数和可执行文件参数用“—”分隔

1
$>cargo test cargo_params -- bin_params 

可执行文件常用参数

  • 控制测试线程数目
    1
    $>cargo test -- --test-thread=1(不使用任何并行机制)
  • 禁止捕获输出(测试函数中的标准输出)
    1
    $>cargo test -- --nocapture
  • 测试#[ignore]标注的测试函数
    1
    $>cargo test -- --ignore

命令行常用参数

  • 指定部分测试函数
    1
    $>cargo test function_name(cargo匹配以此开头的函数)
  • 指定部分集成测试文件
    1
    $>cargo test --test test_filename

单元测试、集成测试

单元测试

在隔离环境中一次测试一个模块,可以测试私有接口,常用做法是 在每个文件中创建包含测试函数的tests模块,并使用 #[cfg(test)]标注,告诉rust仅在cargo test时才编译该mod

集成测试

相当于外部库,和用户使用代码的方式相同,只能测试公有接口, 可以同时测试多个模块

新建/project/tests目录(和src同级),cargo自动寻找此目录 中集成测试文件

  • cargo将每个文件当作单独的crate编译(模仿用户) 其中的文件也不能共享相同的行为(fn、mod)

    • 需要像外部用户一样extern crate引入外部文件,因此 如果二进制库没有lib.rs文件,无法集成测试,推荐 采用main.rs调用lib.rs的逻辑结构

    • 不需要添加任何#[cfg(test)]注解,cargo会自动将 tests中文件只在cargo test时编译

    • 即使文件中不存在任何#[test]注解的测试函数,仍然会 对其进行测试,只是结果永远是通过

  • 而文件夹则不会当作测试crate编译

    • cargo test不会将文件夹视为测试crate,而是看作 一个mod

    • 所以可以创建tests/common/mod.rs,并在测试文件中 通过mod common;声明定义common mod共享行为 (相当于所有的测试crate = 测试文件 + common mod

Rust技巧

代码省略

  • for i in i.iter()
  • array[m..n]

问题明确

  • rust中slice也是左闭右开区间

其他技巧

  • 随便定义变量数据类型,编译后通过编译器给出的信息得到 某个函数返回值类型

Unsafe Rust

不安全的Rust存在原因

  • Rust在编译时强制执行内存安全保证,但这样的静态分析是 保守的,有些代码编译器认为不安全,但其实合法
  • 底层计算机硬件的固有的不安全性,必须进行某些不安全操作 才能完成任务

因此需要通过unsafe关键字切换到不安全的Rust,开启存放 不安全代码的块,只能在不安全Rust中进行的操作如下

  • 解引用裸指针,
  • 调用不安全的函数、方法
  • 访问或修改可变静态变量
  • 实现不安全trait

需要注意的是,unsafe不会关闭借用检查器或其它Rust安全检查, 在不安全Rust中仍然会检查引用,unsafe关键字只告诉编译器忽略 上述4中情况的内存安全检查,此4种的内存安全由用户自己保证, 这就保证出现内存安全问题只需要检查unsafe块。可以将不安全 代码封装进安全的抽象并提供API,隔离不安全代码。

解引用裸指针(raw pointer)

  • *const TT类型不可变裸指针
  • *mut TT类型可变裸指针

裸指针的上下文中,裸指针意味着指针解引用后不能直接赋值, 裸指针和引用、智能指针的区别

  • 允许忽略借用规则,允许同时拥有不可变和可变指针,或者 多个相同位置(值)的可变指针
  • 不保证指向有效的内存
  • 允许为空
  • 不能实现任何自动清理功能
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let mut num = 5;
let r1 = &num as *const i32;
let r2 = &num as *mut i32;
//`as`将不可变引用和可变引用强转为对应的裸指针类型
//同时创建`num`的可变裸指针和不可变裸指针
//创建裸指针是安全的
unsafe{
println!("r1 is: {}", *r1);
println!("r2 is: {}", *r2);
//解引用裸指针是不安全的,需要放在`unsafe`块中
}

let address = 0x012345usize;
//创建任意地址
let r = address as *const i32;
//创建指向任意内存地址的裸指针

调用不安全的函数或方法

不安全函数和方法类似常规,在开头有unsafe关键字标记,表示 函数含有内存不安全的内容,Rust不再保证此函数内存安全,需要 程序员保证。

但是包含不安全代码并不意味着整个函数都需要标记为不安全,相反 将不安全代码封装于安全函数中是隔离unsafe代码的方法。应该 将不安全代码与调用有关的函数标记为unsafe

1
2
3
4
5
6
7
8
9
10
11
12
13
14
unsafe fn dangerous() {}
//`unsafe`关键字表示此函数为不安全函数,含有内存不安全
//内容,需要程序员自身保证其内存安全
//但是,包含不安全代码的函数不意味着整个函数都需要标记为
//不安全,相反的,将不安全代码封装进安全函数是常用的

//不安全函数体也是`unsafe`块,在其中进行不安全操作时,
//不需要包裹于`unsafe`块

unsafe{
dangerous();
//调用不安全函数也需要在`unsafe`块中,表示调用者确认此
//“不安全”函数在此上下文中是*内存安全*
}

调用不安全的函数时也需要放在unsafe中,表示程序员确认此函数 在调用上下文中是内存安全的。

split_at_mut的实现

1
2
3
4
5
6
7
let mut v = vec![1, 2, 3, 4, 5, 6];
let r = &mut v[..];
let (a, b) r.split_at_mut(3);
//以index=3分隔为两个列表引用(左开右闭)

assert_eq!(a, &mut [1, 2, 3]);
assert_eq!(b, &mut [4, 5, 6]);

split_at_mut方法无法指通过安全Rust实现,一个大概的“函数” 实现可以如此

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
use std::slice;

fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
//这里根据生命周期省略规则省略了生命周期注解

//在所有权里就有提到,这里不也是可变引用吗,为啥这样
//还可以通过编译,是对方法中的`self`有特殊的处理吗

let len = slice.len();
let ptr = slice.as_mut_ptr();
//`as_mut_ptr`返回`*mut T`可变裸指针

assert!(mid <= len);

unsafe{
(slice::from_raw_parts_mut(ptr, mid),
//`from_raw_parts_mut`根据裸指针和长度两个参数
//创建slice,其是不安全的,因为其参数是一个
//裸指针,无法保证内存安全,另外长度也不总是有效
slice::from_raw_parts_mut(ptr.offset(mid as isize), len - mid))
//`offset`同样是不安全的,其参数地址偏移量无法
//保证始终有效
}
}

使用extern函数调用外部代码

extern关键字用于创建、使用外部函数接口

  • 外部函数接口FFI:foreign function interface,编程语言 用以定义函数的方式,允许不同(外部)编程语言调用这些 函数
  • 应用程序接口ABI:application binary interface,定义了 如何在汇编层面调用函数
1
2
3
4
5
6
7
8
9
10
extern "C" {
//`"C"`定义了外部函数所使用的ABI
fn abs(input: i32) -> i32;
//希望调用的其他语言中的(外部)函数签名
}
fn main(){
unsafe{
println!("absolute value of -3 according to C: {}", abs(-3));
}
}

extern块中声明的函数总是不安全的,因为其他语言并不强制执行 Rust的内存安全规则,且Rust无法检查,因此调用时需要放在 unsafe块中,程序员需要确保其安全

通过其他语言调用Rust函数

1
2
3
4
5
6
#[no_mangle]
//告诉Rust编译器不要mangle此函数名称
pub extern "C" fn call_from_c(){
//此函数编译器为动态库并从C语言中链接,就可在C代码中访问
println!("just called a Rust function from C!");
}

mangle发生于编译器将函数名修改为不同的名称,这会增加 用于其他编译器过程中的额外信息,但是会使其名称难以阅读 而不同的编程语言的编译器mangle函数名的方式可能不同

访问或修改可变静态变量

全局变量:Rust中称为静态(static)变量

1
2
3
4
5
static HELLO_WORLD: &str = "Hello, world!";
//静态变量(不可变)
fn main(){
println!("name is: {}", HELLO_WORLD);
}
  • 名称采用SCREAMING_SNAKE_CASE写法,必须标注变量类型
  • 只能存储‘static生命周期的引用,因此无需显著标注
  • 不可变静态变量和常量(不可变变量)有些类似
    • 静态变量值有固定的内存地址,使用其总会访问相同地址
    • 常量则允许在任何被用到的时候复制数据

访问不可变静态变量是安全的,但访问、修改不可变静态变量都是 不安全的,因为可全局访问的可变数据难以保证不存在数据竞争, 因此在任何可能情况,优先使用智能指针,借助编译器避免数据竞争

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static mut COUNTER: u32 = 0;
//可变静态变量

fn add_to_count(inc: u32){
unsafe {
COUNTER += inc;
//修改可变静态变量
}
}

fn main(){
add_to_count(3);

unsafe{
println!("COUNTER: {}", COUNTER);
//访问可变静态变量
}
}

实现不安全trait

存在方法中包含编译器不能验证的不变量的trait时不安全的,可以 在trait前增加unsafe将trait生命为unsafe,且实现trait 也需要标记为unsafe

1
2
3
4
unsafe trait Foo{
}
unsafe impl Foo for i32{
}

如为裸指针类型实现(标记)SendSynctrait时需要标记 unsafe,因为Rust不能验证此类型可以安全跨线程发送或多线程 访问,需要自行检查

Rust 语法

Rust是基于表达式的语言

表达式返回一个值,而语句不返回,rust中除两种语句外,全是 表达式

  • let引入绑定
    • 可变绑定赋值是表达式,返回空tuple
    • 声明后初始化绑定?#todo
  • 表达式语句:表达式后跟分号转换为语句

代码中rust希望语句后跟语句,使用分号分隔表达式,所以rust 看起来和其他大部分分号结尾语言相似

{}包裹的代码块内最后一“句”没有以”;”结尾,那么是表达式,且 返回该表达式的值,整个代码块可以看作是表达式,否则为语句, 没有返回值,函数同

模式匹配

Refutability(可反驳性)

  • refutable(可反驳的):对某些可能值匹配失败的模式, if letwhile let只能接受可反驳的模式,因为这就用于 处理可能的失败
  • irrefutable(不可反驳的):能匹配任何传递的可能值,let 语句、函数参数、for循环只能接受不可反驳的模式,因为 通过不匹配值的程序无意义

可能值是指“类型”相同,可用于匹配的值

1
2
3
4
5
6
7
8
let Some(x) = some_optiona_value;
//`Some(x)`是refutable模式,若`some_optional_value`为
//None,则此时无法成功匹配,此语句可能无法正常工作
if let x = 5 {
//`x`是inrefutable模式,对所有可能值都可以匹配,此语句
//无意义
println!("{}", x);
}

Refutable

match控制流

  • 各分支模式同值“类型”必须完全一致才能匹配
  • 返回类型必须一致,如果有返回值
  • 匹配必须是穷尽的,可以使用通配符_(匹配所有的值)代替
    • match是匹配到就退出,不像switch一样会继续下沉
    • 通配符不总是需要的,对于枚举类型只要含有所有枚举 成员的分支即可
1
2
3
4
5
6
7
8
9
10
11
let x = Some(5);
let y = 10;

match x{
Some(50) => println!("got 50"),
//`Some(50)`这个模式规定了“值”
Some(y) => println!("matched, y = {:?}", y),
//match表达式作用域中的`y`会覆盖周围环境的`y`
_ => println!("default case, x = {:?}", x),
}
println!("at the end: x = {:?}, y = {:?}", x, y);

if let[else]简洁控制流

  • 只匹配关心的一个模式
  • 可以添加else语句,类似match通配符匹配
  • if letelse ifelse if let等相互组合可以提供更多 的灵活性
    • else if可以不是模式匹配
    • if后的比较的值可以没有关联
  • 没有穷尽性检查,可能会遗漏一些情况
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
fn main(){
let favorite_color: Option<&str> = None;
let is_tuesday = false;
let age: Result<u8, _> = "34".parse();

if let Some(color) = favorite_color{
//模式匹配
//注意这里是`=`而不是一般值比较`==`
//`while`同
println!("favorite color is {}", color};
}else if is_tuesday{
//普通`if`条件语句
println!("Tuesday is green day!");
}else if let Ok(age) = age{
//`age`变量覆盖原变量
//此时`age`是`u8`类型
if age > 30 {
//因此此条件不能提出,因为两个`age`变量不同,
//不能共存在同一条语句中
println!("Using purple as the background color");
}else{
println!("Using orange as the background color");
}
}else{
println!("Using blue as the background color");
}
}

Irrefutable

while let条件循环

if let条件表达式类似,循环直到模式不匹配

1
2
3
4
5
6
7
8
9
fn main(){
let mut stack = Vec::new();
stack.push(1);
stack.push(2);
stack.push(3);
while let Some(top) = stack.pop(){
println!("{}", top);
}
}

for解构

1
2
3
4
5
6
7
fn main(){
let v = vec!['a', 'b', 'c'];
for (index, value) in v.iter().enumerate(){
//解构tuple
println!("{} is at index {}", value, index);
}
}

let解构

let语句本“应该“看作是模式匹配

1
2
3
4
5
6
7
8
9
let PARTTERN = EXPRESSION;
//这样就和`if let`模式匹配中`=`一致
//应该可以把`=`看作是模式匹配的符号

let x = 5;
//这里`x`是一个模式“变量”
let (x, y, z) = (1, 2, 4);
//`(x, y, z)`是模式“3元元组“
//解构元组

函数参数

类似于let语句,函数参数也“应该”看作是模式匹配

1
2
3
4
5
6
7
8
9
fn foo(x: i32){
//`x`表示模式“变量”
}
fn print_coordinates(&(x, y): &(i32, i32)){
//这里`(x, y)`是模式“元组”
//但是这里处于函数变量类型的要求,有点像元组结构体
//调用时使用元组即可
println!("Current location: ({}, {})", x, y);
}

模式匹配用法

|...“或“

  • |“或”匹配多个模式
  • ...闭区间范围模式,仅适用于数值、char
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
let x = 1;
match x {
1 => println!("one"),
//`1`是模式“字面值”
2 | 3 => println!("two or three"),
//`|`分隔,匹配多个模式
5...10 => println!("through 5 to 10"),
//`...`表示匹配一个**闭区间**范围的值
//这个语法只能用于数字或者是`char`值,因为编译器会
//检查范围不为空,而只有数字、`char`值rust可以判断
_ => println!("anything"),
}

let x = 'c';
match x {
'a'...'j' => println!("early ASCII letter"),
'k'...'z' => println!("late ASCII letter"),
_ => println!("something else"),
}

_..”忽略“

  • _忽略整个值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    fn foo(_: i32, y: i32){
    //函数签名中忽略整个值
    println!("this code only use the y parameter: {}", y);
    }
    fn main(){
    foo(3, 4);

    let mut setting_value = Some(5); let new_setting_value = Some(10);
    match (setting_value, new_setting_value){
    (Some(_), Some()) => {
    //嵌套`_`忽略部分值
    //此时没有任何值所有权
    println!("can't overwrite an exsiting customized value");
    }
    _ => {
    setting_value = new_setting_value;
    }
    }
    println!("setting is {:?}", setting_value);
    }
  • _var变量名前添加下划线忽略未使用变量,此时值所有权 仍然会转移,只是相当于告诉编译器忽略该未使用变量

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    fn main(){
    let _x = 5;
    //两个变量未使用,但是编译时只有一个warning
    let y = 10;

    let s = Some(5);
    if let Some(_s) = s {
    //值所有权已转移,`s`不能继续使用
    //编译器忽略未使用`_s`,不给出warning
    println!("get a integer");
    }
    }
  • ..忽略剩余值

    • ..的使用必须无歧义
    • 对于结构体即使只有一个field,需使用..忽略剩余值, 不能使用_
      1
      2
      3
      4
      5
      6
      7
      8
      fn main(){
      let numbers = (2, 4, 6, 8, 10);
      match numbers{
      (first, .., last) => {
      println!("Some numbers: {}, {}", first, last);
      }
      }
      }
1
2
3
4
let (x, _, z) = (1, 2, 4);
//`_`忽略模式中各一个值
let (x, .., z) = (1, 2, 3, 4);
//`..`忽略模式中多个值

解构结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
struct Point{
x: i32,
y: i32,
}

fn main(){

let p = Point{x: 0, y: 7};
let Point{x: a, y: b} = p;
//`Point{x: a, y: b}`是模式”Point结构体“
//解构结构体

let p = Point{x: 0, y: 7};
let Point{x, y} = p;
//模式匹配解构结构体简写
//只要列出结构体字段,模式创建相同名称的变量

let p = Point{x: 0, y: 7};
match p {
Point {x, y: 0} => println!("on the x axis at {}", x),
Point {x: 0, y} => println!("on the y axis at {}", y),
Point {x, y} => println!("on neither axis: ({}, {})", x, y),
//这里没有`_`通配符,因为`Point {x, y}`模式已经
//是irrefutable,不需要
}
}

解构枚举

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
enum Message{
Quit,
Move{x: i32, y:i32},
Write(String),
ChangeColor(i32, i32, i32),
}
fn main(){

let msg = Message::ChangeColor(0, 160, 255);

let p = match msg{
//这里`match`返回值必须类型完全相同
Message::Move{x, y} if x == 0 => (x, y),
//对于`Message`中的匿名结构体类型的成员,匿名
//结构体没有枚举类型外的定义、名称,无法、也
//不应该直接获取结构体
Message::Write(ref str) => {
//`Message::Write`不是`Message`的一个枚举成员
//必须`Message::Write(str)`才是(能够匹配)
println!("write {}", str);
(1,1)
},
Message::ChangeColor(..) => (1,0),
//类似的,需要使用`..`忽略值,仅`ChangeColor`
//不是`Message`成员
_ => {
println!("quit");
(0,0)
},
};
}

&refref mut”引用“

  • &匹配引用,“获得”值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    let points = vec![
    Point {x: 0, y: 0},
    Point {x: 1, y: 5},
    Point {x: 10, y: -3},
    }

    let sum_of_squares: i32 = points
    .iter()
    .map(|&Point {x, y}| x * x + y * y)
    //`&`匹配一个引用
    .sum();
  • ref匹配值,“获得”不可变引用

    1
    2
    3
    4
    5
    6
    7
    8
    let robot_name = Some(String::from("Bors"));
    match robot_name{
    Some(ref name) => println!("found a name: {}", name),
    //使用`ref`获取不可变引用才能编译成功
    //否则所有权转移,之后报错
    None => (),
    }
    println!("robot_name is: {:?}", robot_name);
  • ref mut匹配值,“获得”可变引用

    1
    2
    3
    4
    5
    6
    let robot_name = Some(String::from("Bors"));
    match robot_name{
    Some(ref mut name) => *name = String::from("NewName"),
    None => (),
    }
    println!("robot_name is: {:?}", robot_name);

ifmatch guard

匹配守卫match guard:放在=>之前的if语句,match 分支的额外条件,条件为真才会继续执行分支代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let num = Some(4);
let y = 5;
match num{
Some(x) if x < y => println!("'less than y: {}", x),
Some(x) => println!("{}", x),
None => (),
}

let x = 4;
let y = false;
match x {
4 | 5 | 6 if y => println!("yes"),
//`4 | 5 | 6`整体作为一个模式,match guard作用于
//模式整体,而不是单独的`6`
_ => println!("no"),
}

@绑定

@允许在创建存放值的变量时,同时测试值是否匹配模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
enum Message{
Hello { id: i32},
}

let msg = Message::Hello { id: 5 };
match msg{
Message::Hello{ id: id_variable @ 3...7 } => {
//匹配结构体模式(值绑定`id_variable`)&&值在`3...7`范围
println!("Found an id in range: {}", id_variable)
},
Message::Hello{ id: 10...12 } => {
//此分支模式指定了值的范围,但是没有绑定值给变量`id`
//结构体匹配简略写法不能应用与此
println!("Found an id in another range")
}
Message::Hello{ id } => {
//此分支结构体匹配简略写法,值绑定于`id`
println!("Found some other id: {}", id)
},
}

闭包closures和函数指针

闭包是可以保存进变量或作为参数传递给其他函数的匿名函数, 可以在一个地方创建闭包,而在不同的上下文执行闭包。和函数 的区别在于,其可以捕获调用者作用域中的值,当然这会有性能 损失,如果不需要捕获调用者作用域中的值可以考虑使用函数

1
2
3
4
let closures = |param1, param2|{
...
expression
}
  • 闭包参数:调用者使用,
    • 创建闭包赋值给变量,再通过变量调用闭包
    • 创建闭包作为参数传递,其他函数调用
  • 捕获环境变量:创建闭包作为参数传递,直接使用周围环境变量

闭包类型推断和注解

闭包不要求像函数一样需要在参数和返回值上注明类型,函数需要 类型注解因为其是需要暴露给的显示接口的一部分,而闭包不用于 作为对外暴露的接口

  • 作为匿名函数直接使用,或者存储在变量中
  • 通常很短,使用场景上下文比较简单,编译器能够推断参数和 返回值类型

当然,闭包也可以添加注解增加明确性

1
2
3
4
5
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;
//闭包体只有一行可以省略`{}`

Rust会根据闭包出调用为每个参数和返回值推断类型,并将其锁定, 如果尝试对同一闭包使用不同类型的参数调用会报错

1
2
3
4
5
let example_closure = |x| x;
let s = example_closure(String::from("hello"));
//此时已经锁定闭包参数、返回值类型
let n = example_closure(5);
//尝试使用`i32`类型调用闭包会报错

Fntrait bound

每个闭包实例有自己独有的匿名类型,即使两个闭包有相同的签名, 其类型依然不同。为了定义使用闭包的结构体、枚举、函数参数, (这些定义中都需要指定元素类型),需要使用泛型和trait bound

  • FnOnce:获取从周围环境捕获的变量的所有权,因此只能调用 一次,即Once的含义
  • Fn:获取从周围环境捕获的变量的不可变引用
  • FnMut:获取从周围环境捕获的变量的可变引用

所有的闭包都实现了以上3个trait中的一个,Rust根据闭包如何 使用环境中的变量推断其如何捕获环境,及实现对应的trait

1
2
3
4
5
6
struct Cacher<T>
where T: Fn(u32) -> u32{
//`T`的类型中包括`Fn`、参数、返回值三个限定
calculation: T,
value: Option<u32>,
}

Function Pointer fn

1
2
3
4
5
6
7
8
9
10
11
fn add_one(x: i32) -> i32{
x + 1
}
fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32{
//`f`是`fn`函数指针类型
f(arg) + f(arg)
}
fn main(){
let answer = do_twice(add_one, 5);
println!("the anwser is: {}", anwser);
}

函数指针类型实现了以上全部FnFnMutFnOnce三个 trait,所以总是可以在调用期望闭包作为参数的函数时传递函数 指针,因此倾向于使用泛型和闭包trait bound的函数,这样可以 同时使用闭包、函数指针作为参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let list_of_numbers = vec![1, 2, 3];
let list_of_Strings: Vec<String> = list_of_numbers
.iter()
.map(|i| i.to_string())
//闭包作为参数传递
.collect();

let list_of_strings: Vec<String> = list_of_numbers
.iter()
.map(ToString::to_string)
//函数作为参数传递
//使用了完全限定语法,因为存在多个`to_string`函数
//标准库为所有实现了`Display`的类型实现了此trait
.collect();

与不存在闭包的外部代码(如C语言,只有函数没有闭包)交互时, 只能使用函数作为参数,不能使用闭包。

move关键字

move关键字强制闭包获其捕获的环境变量的所有权,在将闭包 传递给新线程以便将数据移动到新线程时非常实用

1
2
3
4
5
6
7
fn main(){
let x = vec![1, 2, 3]
let equal_to_x = move |z| z == x;
println!("can't use x here: {:?}", x);
//此时`x`的所有权已经转移进闭包,不能在闭包外使用
let y = vec![1, 2, 3];
assert!(equal_to_x(y));

返回闭包

闭包表现为trait,没有确定的类型、大小,无法直接返回,也不 允许使用函数指针fn作为返回值类型,需要使用trait对象返回 闭包

1
2
3
4
fn return_closure() -> Box<Fn(i32) -> i32>{
//trait对象作为返回值
Box::new(|x| x + 1)
}

迭代器Iterator

迭代器负责遍历序列中的每一项和决定序列何时结束的逻辑。Rust 中迭代器时惰性的,直到调用方法”消费“迭代器之前都不会有效果

Iteratortrait

迭代器都实现了标准库中Iteratortrait

1
2
3
4
5
6
7
8
trait Iterator{
type Item;
//定义`Iterator`的关联类型
fn next(&mut self) -> Option<Self::Item);
//参数是`&mut self`,要求迭代器是`mut`类型
//`next`方法改变了迭代器中用来记录序列位置的状态
//methods with default implementation elided
}

next方法

nextIterator唯一要求被实现的方法,其返回迭代器中封装 在Some中的一项(消费迭代器中的一项),迭代器结束时, 返回None

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#[test]
fn iterator_demostration(){
let v1 = vec![1, 2, 3];
let mut v_iter = v1.iter();
//注意`v_iter`声明为`mut`
//使用`for`循环时无需使`v1_iter`可变,`for`会获取其
//所有权并在后台使其可变

assert_eq!(v1_iter.next(), Some(&1));
assert_eq!(v1_iter.next(), Some(&2));
assert_eq!(v1_iter.next(), Some(&3));
//真的很难理解,rust中&integer是怎么比较的
assert_eq!(v1_iter.nett(), None));
}

消费适配器Comsuming Adaptors

Iteratortrait中定义,调用next方法,消耗迭代器

1
2
3
4
5
6
7
8
#[test]
fn iterator_sum(){
let v1 = vec![1, 2, 3];
let v1_iter = v1.iter();

let total:i32 = v1_iter.sum();
//`sum`获取迭代器所有权,`v1_iter`不能继续使用
assert_eq!(total, 6);

迭代器适配器Iterator Adaptors

Iteratortrait中定义,将当前迭代器变为其他迭代器,同样是 惰性的,必须调用消费适配器以便获取迭代适配器的结果

1
2
3
4
5
6
let v1:Vec<i32> = vec![1, 2, 3];
v1.iter().map(|x| x + 1);
//这里因为没有调用消费适配器,其实没有做事
let v2:Vec<_> = v1.iter().map(|x| x + 1).collect();
assert_eq!(v2, vec![2, 3, 4])

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#[derive(PartialEq, Debug)]
struct Shoe{
size: u32,
style: String,
}

fn shoes_in_my_size(shoes: Vec<Shoe>, show_size: u32) -> Vec<Shoe>{
shoes.into_iter()
//获取vector所有权的迭代器
.filter(|s| s.size == show_size)
//这里使用闭包获取外部环境变量
.collect()
}

#[test]
fn filter_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_my_size(shoes, 10);

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

实现Iterator

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
struct Counter{
count: i32,
}
impl Counter{
fn new() -> Counter{
Counter{count: 0}
}
}

impl Iterator for Counter{
type Item = u32;
//迭代器将返回`u32`值集合

fn next(&mut self) -> Option<Self::Item>{
self.count += 1;

if self.count < 6{
Some(self.count)
}else{
None
}
}
}

#[test]
fn using_other_iterator_trait_methods(){
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);

并发(并行)

多线程可以改善性能,但是也会增加复杂性

  • 竞争状态Race Conditions:多个线程以不一致的顺序访问资源
  • 死锁Dead Lock:线程互相等待资源释放,阻止继续运行
  • 只会在特定情况出现、无法稳定重现的bug

线程模型

  • 1:1模型:一个OS线程对应一个语言线程,语言调用操作系统 API创建线程,性能较好
  • M:N模型:语言有自己的线程实现,其提供的线程称为 绿色(green)线程,M个绿色线程对应N个OS线程,更好的 运行控制、更底的上下文切换成本

Rust为了更小的运行时(这里表示二进制文件中语言自身提供的 代码)考虑,标准库中只提供了1:1线程模式实现。可以通过一些 crate扩展M:N线程模式。

spawn创建新线程

std::thread::spawn接受一个闭包作为参数,返回JoinHandle 类型的句柄。作为spawn参数的闭包和一般的闭包有些不同, 线程直接独立执行,所以此时闭包捕获外部环境变量不能按照默认 的获取不可变引用,因为此时捕获的变量值可能已经被丢弃,必须 使用move关键字获取所有权,而一般的闭包是顺序执行的,没有 特殊需要可以直接获取不可变引用,而能够保证值不被丢弃。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
use std::thread;
use std::time:Duration;
fn main(){
let handle = thread::spawn(|| {
//`thread::spawn`接受一个闭包作为参数,返回
//`JoinHandle`类型的值(不是引用)
for i in 1..10{
println!("number {} from spwaned thread!", i);
thread::sleep(Duration::from_millis(1));
}
);

for i in 1..5{
println!("number{} from main thread!", i);
thread::sleep(Duration::from_millis(1));
}

handle.join().wrap();
//`JoinHandle.join()`将阻塞直到其对应的线程结束
//如果调用`join`,spawn线程可能无法执行完毕
//因为主线程执行完,整个进行结束
//注意`join`调用的位置决定阻塞的位置

let v = vec![1, 2 , 3];
let handle = thread::spawn(move || {
//这里`move`关键字将捕获的外部环境中变量`v`所有权
//移入spawn线程,否则无法正常编译
prinln!("here's a vector: {:?}", v);
});
handle.join().unwrap();
}

消息传递

Rust中实现消息传递并发的主要工具是通道(channel)

mpsc::channel

mpsc:multiple producer single consumer,多个生产者,单个 消费者,即Rust标准库实现通道的方式允许多个产生值的发送端,但 只能有一个消费这些值的接收端。发送端或接收端任一被丢弃时, 意味着通道被关闭

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
use std::thread;
use std::sync:mpsc;
use std::time:Duration;

fn main(){
let (tx, rx) = mpsc::channel();
//`tx`表示发送端,`rx`表示接收端

let tx1 = mpsc::Sender::clone(&tx);
//clone发送端创建多个生产者

thread::spawn(move || {
let vals = vec![
String::from("hi"),
String::from("from"),
String::from("the"),
String::from("thread"),
];

for val in vals {
tx1.send(val).unwrap();
//`send`会获取参数所有权归接收者所有,避免
//值被其他线程丢弃、修改导致意外结果

//`send`返回`Result<T, E>`,如果接收端被丢弃
//将没有发送值的目标,将返回`Err<E>`
thread::sleep(Duration::from_secs(1));
}
});

thread::spawn(move || {d
let vals = vec![
String::from("move"),
String::from("messages"),
String::from("for"),
String::from("you"),
];

for val in vals{
tx.send(val).unwrap();
thread::sleep(Duration::from_secs(1));
}
});

let received = rx.recv().unwrap();
//`recv`将阻塞直到接收到一个值,返回`Result<T, E>`
//通道发送端关闭时`recv`将返回`Err<E>`表明不会有新值

let received = rx.try_recv().unwrap();
//`try_recv`不阻塞,立刻返回`Result<T, E>`,`Err<E>`
//表示当前没有消息
//可以再循环中多次调用`try_recv`,有消息进行处理,
//否则进行其他工作直到下次调用`try_recv`

for received in rx{
//将接收端`rx`当作迭代器使用,返回接收到值
println!("Got: {}", received);
}
}

共享状态

(任何语言)通道都类似于单所有权,一旦值通过通道传送,将无法 再次使用,而共享内存类似于多所有权

Mutex<T>

互斥器mutex:mutual exclusion,任意时刻只允许一个线程访问 数据

  • 线程在访问数据之前需要获取互斥器的锁lock,lock是作为 互斥器一部分的数据结构,记录数据所有者的排他访问权
  • 处理完互斥器保护的数据之后,需要解锁,这样才能允许其他 线程获取数据

Mutex<T>类似于线程安全版本的RefCell<T>cell族), 提供了内部可变性,Mutex<T>有可能造成死锁,如一个操作需要 两个锁,两个线程各持一个互相等待。

Arc<T>原子引用计数atomically reference counted,则是 线程安全版本的Rc<T>,而线程安全带有性能惩罚,如非必要, 使用单线程版本Rc<T>性能更好。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
use std::sync::{Mutex, Arc};
use std::thread;

fn main(){
let counter = Arc::new(Mutex::new(0));
//因为需要在多个线程内引用,所以需要使用多所有权
//数据结构,而`Rc`不是线程安全的,需要使用线程安全
//`Arc`
let mut handles = vec![];

for _ in 0..10{
let counter = Arc::clone(&counter);
let handle = thread::spawn(move || {
let mut num = counter.lock().unwrap();
//`lock`返回一个`MutexGuard`类型的智能指针,
//实现了`Deref`指向其内部数据,`Drop`当
//`MutexGuard`离开作用域时自动释放锁

//只有`lock`才能获取值,类型系统保证访问数据之前
//获取锁;而锁的释放自动发生,保证锁一定会释放

//这里发生了一次强制解引用多态,将`counter`
//解引用为`Mutex<T>`类型
*num += 1;
});

handles.push(handle);
}

for handle in handles{
handle.join().unwrap();
}
println!("Result: {}", counter.lock.unwrap());
}

Synctrait、Sendtrait

Rust的并发模型大部分属于标准库的实现,但是 std::marker::Sendstd::marker::Sync时内嵌于语言的

  • Send:表明类型所有权可以在线程间传递,几乎所有类型都是 Send的,Rc<T>是其中的一个例外,因为Rc<T>clone之后 在两个线程间可能同时更新引用计数,trait bound保证无法将 不安全的Rc<T>在线程间传递。任何全部由Send组成的类型 会自动标记为Send
  • Sync:表明类型可以安全的在多线程中拥有其值的引用,对于 任何类型,如果&TSend的,那么T就是Sync的。 Cell<T>系列不是Sync的,Mutex<T>是。基本类型是Sync 的,全部由Sync组成的类型也是Sync

SendSync是标记trait,不需要实现任何方法,全部是SendSync组成的类型就是SendSync,一般不需要手动实现 它们,而且手动实现这些标记trait涉及编写不安全代码

Rust 标准库数据类型

通用集合类型

Vec<T>

vector允许在单独数据结构中存储多于一个的值,它们在内存中相邻 排列,vector被丢弃时,其中的数据也会被丢弃

存储不同类型

vector只能存储相同类型的值,因为vector必须在编译前知道所有 存储元素所需内存、允许的元素类型,否则对vector进行操作可能 会出错。但是可以使用枚举类型存储”不同类型”(Message为例)

1
vec![Message::Write(String::from("ab"), Message::Move{x:5, y:6}]

常用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
let mut v:Vec<i32> = Vec::new();
let mut v = Vec::with_capacity(3);
//为vector预先分配空间,比`new`稍有效率
//但是这个为啥不用指定类型啊,这样怎么分配空间
let v = vec![1, 2, 3];

v.push(5)
let third:&i32 = &v[2]
//尝试获取一个引用值,如果越界则报错
let third:Option<&i32> = v.get(2)
//尝试获取Option<&i32>,越界则返回None

let mut v = vec![100, 32, 57]
for i in &mut v{
*i += 50;
}

let iter = v.iter();
//不可变引用迭代器
let iter_mut = v.iter_mut();
//可变引用迭代器
let iter_owner = v.into_iter();
//所有权引用迭代器

使用enum+match就能保证处理所有类型,不会出错

字符串

通常意义上的字符串往往是以下两种的”综合“

  • rust核心语言中的字符串slice&str:存储在别处的utf-8 编码字节序列的引用

    字符串slice是&str类型,这个好像体现了rust引用更像 指针,字符串字面值应该是一系列字节序列(流)存储, 所以”返回值“应该是”首地址“,因此是引用类型

  • rust标准库中String类型,是Vec<u8>的封装

    • 可增长
    • 可变
    • 有所有权
    • utf-8编码

索引字符串

因此String类型不能索引获取字符,索引操作预期是常数时间, 而utf-8字列序列并不能保证在常数时间内获取“字符”,rust需要 从头检查。另外,字符串中可能有不可见字符(如发音字符), 即字形簇字符串不等价,此时索引的意义也不明确。

更加有价值的是使用[]和range创建一个字符串slice需要注意的是 如果字符串slice不是有效处utf-8编码序列,程序会在运行时 panic!

1
2
3
4
5
let len = String::from("Здравствуйте").len()
//`len`返回的是utf-8编码序列的长度20,不是“字符”数目12

let hello = "Здравствуйте";
let s = &hello[0..4];

遍历字符串

  • 返回字符Unicode值char类型
    1
    2
    3
    for c in "नमस्ते".chars() {
    println!("{}", c);
    }
    将会打印出6个字符,两个不可见的发音字符
    1
    2
    3
    4
    5
    6






  • 返回字节byte值u8类型
    1
    2
    3
    for b in "नमस्ते".bytes() {
    println!("{}", b);
    }

String常用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
let mut s = String::new();
let s = String::from("initial contet");

let data = "initial content";
let s = data.to_string();
let s = "initial content".to_string();

let mut s = String::from("foo");
s.push_str("bar");
let s2 = "bar";
s.push_str(&s2);
println!(s2);
//此时,`s2`仍然可以打印出来,因为`push_str`的参数是它的
//一个引用,应该是方法中对`&&str`类型做了处理?
s.push('l');

let s1 = String::from("hello, ");
let s2 = String::from("world";
let s3 = s1 + &s2;
//此时`s1`所有权被转移给`s3`不能再使用

let s1 = String::from("tic");
let s2 = String::from("tac");
let s3 = string::from("toc");
//let s = s1 + "-" + &s2 + "-" + &s3;
let s = format!("{}-{}-{}", s1, s2, s3);
//`format!`宏是更好地连接字符串的方法,且不会获取任何
//参数的所有权

HashMap

HashMap键、值必须是同质的,相对来说使用频率较低,没有引入 prelude,使用之前需要用use关键字引入

HashMap默认使用一种密码学安全的哈希函数,它可以抵抗拒绝 服务(Denial of Service, DoS)攻击。然而这并不是可用的最快的 算法,不过为了更高的安全性值得付出一些性能的代价。可指定不同 hasher来切换为其它函数。hasher是一个实现了BuildHasher trait的类型

常用函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
use std::collections::HashMap;

let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 30);
//对于没有实现`copy`trait的`string`类型,所有权将转移给
//HashMap
scores.insert(String::from("Blue", 20);
//之前存储的值被覆盖

scores.entry(String::from("Yellow")).or_insert(90);
scores.entry(String::from("Red")).or_insert(90);
//`entry`以想要检查的键作为参数,返回`Entry`类型的枚举
//代表可能存在的值,`or_insert`方法在键对应的值存在时
//返回值`Entry`(实际上时值的可变引用),否则将参数作为
//新值插入,并返回修改后的`Entry`

let text = "hello world wonderful word";
let mut map = HashMap::new();
for word in text.split_whitespace(){
let count = map.entry(word).or_insert(0);
*count += 1;
}
//这段将在`map`中存储`text`中个单词出现次数

let teams = vec![String::from("Blue"), String::from("Yello")];
let initial_scores = vec![10, 30];
let scores: HashMap<_,_> = teams.iter.zip(initial_scores.iter()).collect();
//`collect`可能返回很多不同的数据结构,需要显式指定`scores`
//的类型`HashMap<_,_>`

let team_name = String::from("Blue");
let team_score = scores.get(&team_name);

for (key, val) in &scores{
println!("{}:{}", key, val);
}

智能指针

  • 指针pointer:包含内存地址的变量,这个地址引用(指向) 其他数据
  • 智能指针smart pointer:一类数据结构,表现类似于指针, 拥有额外的元数据和功能

Rust中最常见的指针是引用reference,除了引用数据没有其他 特殊功能,也没有任何额外开销。

智能指针通常由结构体实现,区别于常规结构体的特在于实现了 DerefDroptrait

事实上,StringVec<T>也是智能指针

Dereftrait、DerefMuttrait

  • Dereftrait:重载解不可变引用运算符*
  • DerefMuttrait:重载解可变引用引用运算符*

允许智能指针结构体实例表现得像引用,可以让代码兼容智能指针 和引用

1
2
3
4
5
6
7
8
fn main(){
let x = 5;
let y = &x;
let z = Box::new(5);

assert_eq!(5, x);
assert_eq!(5, *y);
assert_eq!(5, *z);

自定义类型实现Dereftrait

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
struct MyBox<T>(T);
//`Box<T>`从本质上被定义为包含一个元素的元组结构体
//类似定义自定义类型`MyBox`
impl<T> MyBox<T>{
fn new(x: T) -> MyBox<T>{
MyBox(X)
}
}

use::std::Deref;
impl<T> Deref for MyBox<T>{
type Target = T;

fn deref(&self) -> &T{
&sell.0
}
//`deref`返回引用,因为大部分使用解引用时不希望获取
//`MyBox`内部值的所有权
}
//为`MyBox<T>`实现`Deref`trait

fn main(){
let x = 5;
let y = MyBox::new(x);

println!("y = {}", *y);
//对于`*y`Rust实际在底层`*(y.deref())`
}

DerefMuttrait类似

隐式解引用强制多态Deref Coercions

将实现了Dereftrait或DerefMuttrait类型的引用转换为其他 类型的引用,通过多次隐式转换使得实参和型参类型 一致,(这些解析发生在编译时,没有运行时损失)避免多次使用 &* 引用和解引用,也使得代码更容易兼容智能指针和引用。

1
2
3
4
5
6
7
fn hello(name: &str){
println!("hello, {}", name);
}
fn main(){
let m = MyBox::new(String::from("Rust"));
hello(&m);
}

实参类型T和型参类型U满足(间接)

  • T: Deref<Target = U>&T转换为&U
  • T: Deref<Target = U>&mut T转换为&U
  • T: DerefMut<Target = U>&mut T转换为&mut U

相当于在引用外面添加任意层&(*_)&mut(*_),直到实参类型 和型参类型一致

Droptrait

Droptrait要求实现drop方法(析构函数destructor),获取 &mut self可变引用,智能指针离开作用域时运行drop方法中的 代码,用于释放类似于文件或网络连接的资源,编译器会自动插入 这些代码。

  • Droptrait会自动清理代码
  • 所有权系统确drop只会在值不再使用时被调用一次

todo:获取&mut self,那么之前不能获取可变引用了?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct CustomSmartPointer{
data: String,
}

impl Drop for CustomSmartPointer{
fn drop(&mut self){
println!("Dropping CustomSmartPointer with data `{}`!", self.data);
}
}

fn main(){
let c = CustomSmartPointer{ data: String::from("pointer1")};
let d = CustomSmartPointer{ data: String::from("pointer2")};
println!("CustomSmartPointer created!");
}

输出顺序如下,变量以被创建时相反的顺序丢弃

1
2
3
CustomSmartPointer created!
Dropping CustomSmartPointer with data `pointer1`!
Dropping CustomSmartPointer with data `pointer2`!

Rust不允许显示调用drop函数,因为Rust仍然会在值离开作用域时 调用drop函数导致double free的错误。如果需要提早清理, 可以使用std::mem::drop函数(已经位于prelude中)。

1
2
3
4
5
6
fn man(){
let c = CustomSmartPointer{ data: String::from("some data")};
println!("CumstomSmartPointer Created");
drop(c);
//调用`std::mem::drop`函数,不是`c.drop()`方法
println!("CustomSmartPointer dropped before the end of main");

Box<T>

在堆上存储数据,而栈上存放指向堆数据的指针,常用于

  • 类型编译时大小未知,而想要在确切大小的上下文中使用
  • 大量数据希望在拷贝时不转移所有权
  • 只关心数据是否实现某个trait而不是其具体的类型 (trait对像)
1
2
3
4
let b = Box::new(5);
println!("b = {}", b);
//可以像数据存储在栈上一样访问数据
//box离开作用域时,栈上和指向的堆上的数据都被释放

创建递归类型

Rust需要在编译时知道类型占用的空间,而递归类型(recursive type)中值的一部分可以时相同类型的另一个值,所以Rust无法知道 递归类型占用的空间。而box大小已知,可以在递归类型中插入box 创建递归类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
enum List{
Cons(i32, Box<list>),
Nil,
//代表递归终止条件的规范名称,表示列表的终止
//不同于`null`或`nil`
}

use List::{Cons, Nil};

fn main(){
let list = Cons(1,
Box::new(Cons(2,
Box::new(Cons(3,
Box::new(Nil))))));
}

Rc<T>

Rc:引用计数reference counting,记录一个值引用的数量判断 这个值是否仍然被使用,如果值只有0个引用,表示没有任何有效 引用,可以被清理。

Rc<T>允许多个不可变引用,让值有多个所有者共享数据, 引用计数确保任何所有者存在时值有效。用于在堆上分配内存供 程序多个部分读取,且在无法在编译时确定哪部分最后结束使用 (否则令其为所以者即可)

Rc<T>只适合单线程场景

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
enum List{
Cons(i32, Rc<List>),
Nil,
}
//使用`Rc<T>`代替`Box<T>`,可以构造共享List

use List::{Cons, Nil};
use std::rc::Rc;

fn main(){
let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
println!("count after creating a = {}", Rc::strong_count(&a));
//1

let b = Cons::(3, Rc::clone(&a));
//`Rc::clone`并不像大多数`clone`方法一样对所有数据
//进行深拷贝,只会增加引用计数,允许`a`和`b`**共享**
//`Rc`中数据的所有权
//这里可以调用`a.clone()`代替`Rc::clone(&a)`,但是
//习惯上使用`Rc::cloen(&a)`
println!("count after creating b = {}", Rc::strong_count(&a));
//2

{
let c = Cons::(4, Rc::clone(&a));
println!("count after creating c = {}", Rc::strong_count(&a));
//3
}

println!("count after c goes out of scope = {}", Rc::strong_count(&a));
//2
}

RefCell<T>

RefCell<T>是一个遵守内部可变性模式的类型,允许通过 不可变引用更改T值。实际上仍然是通过可变引用更改值, 只是获得的T的可变引用在RefCell<T>内部。

RefCell<T>同样只能应用于单线程场景

可以理解为,将Rust静态引用改成时分复用引用,Rust在 运行时进时引用检查,只要保证在运行时任意时刻满足引用规则 即可。

内部可变性interior mutability

Rust中的一个设计模式,允许在有不可变引用时改变数据,这违反 了引用规则,因此在该模式中使用unsafe代码模糊Rust通常的 可变性和引用规则。但是引用规则依然适用,只是在运行时检查, 会带来一定运行时损失。

在确保代码运行时遵守借用规则,即使编译器不能保证,可以选择 使用运用内部可变性模式的类型,涉及的unsafe代码被封装进 安全的API中,外部类型依然不可变

RefRefMut

  • Ref = RefCell<T>.borrow():获取T的不可变引用
  • RefMut = RefCell<T>.borrow_mut():获取T的一个可变引用

RefRefMut均是实现Dereftrait的智能指针,RefCell<T> 记录当前活动的RefRefMut指针,调用borrow时,不可变 引用计数加1,Ref离开作用域时不可变引用计数减1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
pub trait Messenger{
fn send(&self, msg: &str);
}

pub struct LimitTracker<'a, T:'a + Messenger>{
Messenger:&'a T,
value: usize,
max : usize,
}
//这个结构体有`‘a`和`T`两个泛型参数,且`T`还以生命周期
//注解作为trait bound

impl<'a, T> LimitTracker<a', T>
where T: Messenger{
//这里的`T`就没有加上`‘a`作为trait bound
pub fn new(messenger: &T, max: usize) -> LimitTracker<T>{
LimitTracker{
messenger,
value: 0,
max,
}
}

pub fn set_value(&mut self, value:usize){
self.value = value;
let percentage_of_max = self.max as f64 / self.max as f64;
if percentage_of_max >= 0.75{
self.messenger.send("Warning: over 75% of quota has been used!");
}
}

}

#[cfg(test)]
mod tests{
use supper::*;
use std::cell:RefCell;

struct MockMessenger{
sent_messages: RefCell<Vec<String>>,
}
impl MockMessenger{
fn new() -> MockMessenger{
MockMessenger{ sent_messages: RefCell<vec![]> }
}
}
impl Messenger for MockMessenger{
fn send(&self, message: &str){
self.sent_messages.borrow_mut().push(String::from(message));
}
}

#[test]
fn it_send_an_over_75_percent_warning_message(){
let mock_messenger = MockMessenger::new();
let mut limit_tracker = LimitTracker::new(&mock_messenger, 100);
limit_tracker.set_value(80);

assert_eq!(mock_messenger.sent_messages.borrow().len(), 1);
}
}

Rc<RefCell<T>>

T值可以修改,且可以被多个所有者拥有

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#[derive(Debug)]
enum List{
Cons(Rc<RefCell<i32>>, Rc<List>),
Nil
}

use List::{Cons, Nil};
use std::rc::Rc;
use std::cell::RefCell;

fn main(){
let value = Rc::new(RefCell::new(5));
let a = Rc::new(Cons(Rc::clone(&value), Rc::new(Nil)));
let b = Cons(Rc::new(RefCell::new(6)), Rc::clone(&a));
let c = Cons(Rc::new(RefCell::new(10)), Rc::clone(&a));

*value.borrow_value += 10;
println!("a after = {:?}", a);
println!("b after = {:?}", b);
println!("c after = {:?}", c);
}

RefCell<Rc<T>>

T值不能改变,但是Rc<T>整体可以改变,此时可能出现引用循环 ,导致内存泄露。引用循环是程序逻辑上的bug,Rust无法捕获。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
use List::{Cons, Nil};
use std::rc::Rc;
use std::cell::RefCell;
enum List{
Cons(i32, RefCell<Rc<list>>),
Nil,
}
impl List{
fn tail(&self) -> Option<&RefCell<R<List>>>{
match *self{
Cons(_, ref item) => Some(item),
Nil => None,
}
}
}

fn main(){
let a = Rc::new(Cons(5, RefCell::new(Rc::New(Nil))));
println!("a initial rc count ={}", Rc::strong_count(&a));
//1
println!("a next item = {:?}", a.tail());

let b = Rc::new(Cons(10, RefCell::new(Rc::clone(&a))));
println!("a rc count after b creating = {}", Rc::strong_count(&a));
//2
println!("b initial rc count = {}", Rc::strong_count(&b));
//1
println!("b next item = {:?}"<, b.tail());

if let Some(link) = a.tail(){
*link.borrow_mut() = Rc::clone(&b);
//此时`a`、`b`循环引用,离开作用域时,两个值的
//引用计数因为`a`、`b`被丢弃而减1,但是它们互相
//引用,引用计数保持在1,在堆上不会被丢弃
}
println!("b rc count after changing a = {}", Rc::strong_count(&b));
//2
println!("a rc count after changing a = {}", Rc::strong_count(&a));
//2
}

Weak<T>

强引用Rc<T>代表共享Rc实例的引用,代表所有权关系, 而弱引用Weak<T>不代表所有权关系,不同于Rc<T>使用 strong_count计数,Weak<T>使用weak_count计数,即使 weak_count无需为0,Rc实例也会被清理(只要strong_count 为0)

  • Weak<T>指向的值可能已丢弃,不能像Rc<T>一样直接解引用 ,需要调用upgrade方法返回Option<Rc<T>>
  • Weak<T>避免Rc<T>可能导致的引用循环
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
use std::rc::{Rc, Weak};
use std::cell::RefCell;

#[derive(Debug)]
struct Node{
value: i32,
parent: RefCell<Weak<Node>>,
Children: RefCell<Vec<Rc<Node>>>,
}

fn main(){
let leaf = Rc::new(Node{
value: 3,
parent: RefCell::new(Weak::new()),
children: RefCell::new(vec![]),
});

println!(
"leaf strong = {}, weak = {}",
Rc::strong_count(&leaf),
Rc::weak_count(&leaf),
);
//strong = 1, weak = 0

{
let branch = Rc::new(Node{
value: 5,
parent: RefCell::new(Weak::new()),
children: RefCell::new(vec![Rc::clone(&leaf)),
});
*leaf.parent.borrow_mut() = Rc::downgrade(&branch);
//`downgrade`返回`Weak<T>`

println!(
"branch strong = {}, weak = {}",
Rc::strong_count(&branch),
Rc::weak_count(&branch),
);
//strong = 1, weak = 1

println!(
"leaf strong = {}, weak = {}",
Rc::strong_count(&leaf),
Rc::weak_count(&leaf),
);
//strong = 2, weak = 0
}

println!("leaf parent = {:?}", leaf.parent.borrow().upgrade());
//`upgrade`返回`Option<Rc<T>>`,此例中因为`branch`
//离开作用域已经丢弃,这里返回`None`
println!(
"leaf strong = {}, weak = {}",
Rc::strong_count(&leaf),
Rc::weak_count(&leaf),
);
//strong = 1, weak = 0
//如此不会造成引用循环导致内存泄露
}

比较

智能指针 数据拥有者 引用检查 多线程
Box<T> 单一所有者 编译时执行可变(不可变)引用检查
Rc<T> 多个所有者 编译时执行不可变引用检查
RefCell<T> 单一所有者 运行时执行不可变(可变)引用检查
Weak<T> 不拥有数据 编译时执行可变(不可变)检查

常用枚举类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Option<T>{
Some<T>,
None,
}

some = Some(9);
some.take();
//`take`获取`some`的值所有权作为返回值,并设置`some`为
//`None`

Result<T, U>{
Ok<T>,
Err<U>,
}

Rust 自定义数据类型

结构体struct

rust不允许只将特定字段标记为可变(很正常,因为结构体应当 作为一个整体考虑)

  • 定义结构体时字段不能添加mut
  • 声明结构体时,语法上也难以做到,字段不是单独声明

结构体中若有字段是引用类型,需要添加生命周期

普通结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct stct{
field1: i32,
field2: String,
}

let field1 = 1;
let stct={
field1,
field2: String::from("fy"),
}
//变量字段同名时字段初始化简略写法

let struct1 = stct{
field1: 1,
..struct2
}
//结构体更新语法

元组结构体

结构体名称提供的含义,但只有字段类型没有字段名,用于命名 元组、指定类型,区别于其他相同(结构)的元组

1
struct tuple_stct=(i32, i32, i32)

类单元结构体unit-like struct

不定义任何字段,类似于()()一般用于泛型中占位,表示 当前类型为空,比如T表示返回值泛型参数,无返回值就可以使用 ()代替,因为Rust中类似于typedef用于自定义类型),常用于 在某个类型上实现trait,但不需要在 类型内存储数据时发挥作用

枚举enum

rust枚举更像C语言中enum+struct

  • enum:定义了新的枚举类型,取值范围有限
  • struct:枚举成员可以关联数据类型,且可以定义方法

枚举类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
enum IpArr{
V4,
V6,
}
//基础版本

enum IpArr{
V4(u8, u8, u8, u8},
V6(String),
}
//附加数据版本

enum Message{
Quit,
Move{x: i32, y:i32},
Write(String),
ChangeColor(i32, i32, i32),
}
//含有匿名结构体版本

标准库中的枚举

处理null

1
2
3
4
enum Option<T>{
Some<T>,
None,
}

Option被包含在prelude中,包括其成员,rust标准库中唯一 支持创建任何类型枚举值的枚举类型。rust不允许像有效的T类型 数据一样处理Option<T>类型数据,要求在使用之前处理为None 的情况,此即能够保证在可能为空的值会被处理

处理潜在panic

1
2
3
4
enum Result<T, E>{
Ok<T>,
Err<E>,
}

方法、关联函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
impl Message{

fn new() -> Message{
}
//关联函数associated functions,没有`self`作为参数

fn fn1(&self) -> ret_type{
}
//在结构体(枚举、trait对像)的上下文中定义
//第一个参数总是`self`,代表调用方法的结构体实例

fn fn2(mut self) -> ret_type{
}
}

方法 Methods

  • 定义方法的好处主要在于组织性,将某类型实例能做的事均放入 impl

  • 方法签名中self会由rust根据impl关键字后的“替换”为 相应类型(运行过程中是当前实例)

  • 方法可以获取self(当前实例)所有权,常用于将self转换 为其他实例,防止调用者转换之后仍使用原始实例

  • 方法是rust中少数几个可以“自动引用和解引用”的地方,因为 方法中self类型是明确的(调用者类型也明确),rust可以 根据方法签名自动为对象添加&&mut*以适应方法签名, 所以rust调用方法只有.,没有->

关联函数 Associated Functions

与结构体相关联,不作用于一个结构体实例,常被用于返回一个 结构体新实例的构造函数

Trait

将方法(关联函数)签名(可以有默认实现)组合起来、定义实现 某些目的所必需的行为的集合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
pub trait Summarizable{

// 无默认实现
fn author_summary() -> String;

// 有默认实现
fn summary(&self) -> String{
String::from("Read more...{}", self.author_summary())
}
}
//定义trait

impl Summarizable for Message{

fn author_summary(&self){
}

fn summary(&self) -> String{
}

}
//为类型实现trait,之后就可以和普通非trait方法一样调用

默认实现

  • trait中有默认实现的方法可以不重载,实现trait就可直接 调用,没有默认实现的方法则需要全部实现

  • 默认实现重载之后不可能被调用

  • 默认实现可以调用同trait中的其他方法,包括没有默认 实现的方法,如此trait可以实现很多功能而只需要实现少部分

    • 同trait:trait之间本就应该保持独立,这个是trait的 意义

    • 因为实现trait一定要实现所有没有默认实现的方法,所以 默认实现总是“可以调用”

孤儿规则 Orphan Rule

orphan rule:父类型不存在

trait类型位于(之一)本地crate才能实现trait, 如果没有此限制,可能出现两个crate同时对相同类型实现同一trait ,出现冲突

Box<trait> Trait对像

trait对像指向一个实现了指定trait的类型实例,Rust类型系统在 编译时会确保,任何在此上下文中使用的值会实现其trait对像的 trait,如此无需在编译时知晓所有可能类型。

Trait对象、泛型Trait Bound对比

  • trait对像在运行时替代多种具体类型

    • 编译时都是同质的Box<trait>类型
    • 只关心值反映的信息而不是其具体类型,类似于动态语言中 鸭子类型
    • 编译器无法知晓所有可能用于trait对象的类型,因此也 不知道应该调用哪个类型的哪个方法,因此Rust必须使用 动态分发
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    pub trait Draw{
    fn draw(&self);
    }
    pub struct Screen{
    pub components: Vec<Box<Draw>>,
    //`Box<Draw>`就是trait对像,可以代替任何实现了
    //`Draw`trait的值
    }
    impl Screen{
    pub fn run(&self){
    for component in self.components.iter(){
    component.draw();
    }
    }
    }
    pub struct Button{
    pub width: u32,
    pub height: u32,
    pub label: String,
    }
    impl Draw for Button{
    fn Draw{
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    // 外部crate使用时
    extern crate rust::gui;
    use rust_gui::{Screen, Button, Draw};

    struct SelectBox{
    width: u32,
    height: u32,
    options: Vec<String>,
    }
    //此类型对于`Screen`是未知的,但是`components`中仍然能够
    //包含此类型
    impl Draw for SelectBox{
    fn draw(&self){
    }
    }
    fn main(){
    let screen = Screen{
    components: vec![
    Box::new(SelectBox{
    width: 75,
    height: 10,
    option: vec![
    String::from("yes"),
    String::from("maybe"),
    ],
    }),
    Box::new(Button{
    width: 50,
    height: 10,
    label: String::from("OK"),
    }),
    ],
    };

    screen.run();
    }
  • trait bound泛型类型参数结构体在编译时单态化

    • 一次只能替代一个具体类型,多个类型之间不同质
    • 单态化产生的代码进行静态分发
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    pub struct Screen<T: Draw>{
    pub components: Vec<T>,
    //trait bound泛型参数`T`只能替代一种类型
    //不同的实现`Draw`trait类型不能放在同一个vector中
    }
    impl<T> Screen<T>
    where T: Draw{
    pub fn run(&self){
    for component in self.components.iter(){
    component.draw();
    }
    }
    }
  • 鸭子类型:如果它走起来像一只鸭子,叫起来像一只鸭子,那么 它就是一直鸭子

  • 静态分发:编译器知晓调用何种方法

  • 动态分发:编译器在编译时不知晓调用何种方法,生成在运行时 确定调用某种方法的代码。动态分发阻止编译器有选择的内联 方法代码,这会禁用部分优化,但获得了额外的灵活性

对象安全

trait对象要求对象安全,只有对象安全的trait才能组成trait 对象,这有一些复杂的规则,但是实践中只涉及

  • 返回值类型不为Self:如果trait中的方法返回Self类型, 而使用trait对象后就不再知晓具体的类型,那方法就不可能 使用已经忘却的原始具体类型(Clonetrait不是对象安全)
  • 方法没有任何泛型类型参数:具体类型实现trait时会放入具体 类型单态化,但是使用trait对象时无法得知具体类型

状态模式(面向对象设计)

  • 值某些内部状态,其行为随着内部状态而改变
  • 内部状态由一系列集成了共享功能的对象表现,每个状态对象 负责自身行为和需要转变为另一个状态时的规则
  • 值对不同状态的行为、何时状态转移不知情,需求改变时无需 改变值持有的状态、值实现代码,只需更新某个状态对象代码 或者是增加更多状态对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
pub struct Post{
state: Option<Box<State>>,
content: String,
}
impl Post{

pub fn add_text(&mut self, text: &str){
self.content.push_str(&str);
}

pub fn request_review(&mut self){
if let Some(s) = self.state.take(){
//`Option<T>.take()`返回值,并设置为`None`
self.state = Some(s.request_review())
}
}

pub fn approve(&mut self){
if let Some(s) = self.state.take(){
self.state = Some(s.approve())
}
}

pub fn content(&self) -> &str{
self.state.as_ref().unwrap().content(&self)
//`Option<T>.as_ref()`返回`Option<&T>`,因为参数
//是`&self`,只能获取不可变引用
}
}

trait State{
fn request_review(self: Box<Self>) -> Box<State>;
//`self: Box<Self>`意味着这个方法调用只对`Self`
//类型的`Box`指针有效,这里`Self`表示值类型,因为
//值的类型到struct实现trait的时候才能确定,编译时
//应该会替换成具体类型

//这个方法会获取对象的所有权(消费)

//返回值`Box<State>`是trait对象

fn approve(self: Box<Self>) -> Box<State>;

fn content<'a>(&self, post:&'a Post) -> &'a str{
""
}
}

struct Draft{}

impl State for Draft{
fn request_review(self: Box<Self>) -> Box<State>{
Box::new(PendingReview{})
}

fn approve(self: Box<Self>) -> Box<State>{
self
}
}

struct PendingReview{}

impl State for PendingReview{
fn request_review(self: Box<Self>) -> Box<State>{
self
}

fn approve(self: Box<Self>) -> Box<State>{
Box::new(Published{})
}
}

struct Published{}

impl State for Published{
fn request_review(self: Box<Self>) -> Box<State>{
self
}

fn approve(self: Box<Self>) -> Box<State>{
self
}

fn content<'a>(&self , post:&'a Post) -> &'a str{
&post.content
}
}

高级trait

Associated Type

关联类型:将类型占位符和trait相关联的方式

  • 可在trait方法中使用这些占位符类型
  • 实现trait时需要指定为具体类型
1
2
3
4
5
6
pub trait Iterator{
type Item;
//关联类型`Item`,实现时需要指定具体类型
fn next(&mut self) -> Option<Self::Item>;
//trait方法(签名)中使用关联类型
}

关联类型可以看作时trait中“泛型”(弱化版)。只能实现一次 trait,因此关联类型也只能指定一次,保证了一定的抽象

默认泛型类型参数

使用泛型类型参数时,可为泛型指定默认类型 <PlaceholderType = ConcreteType>

  • 扩展类型而不破坏现有代码(普通trait改为泛型trait不需要 改变之前实现trait的代码)
  • 在特殊情况下自定义trait及其中的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
use std::ops::Add;
#[derive(Debug, PartialEq)]
struct Point{
x: i32,
y: i32,
}
impl Add for Point{
//`Add`是`+`运算符对应的trait
//`Add`有默认类型参数,此时未指定泛型参数的具体类型,
//`RHS`将是默认类型
type Output = Point;

fn add(self, other: Point) -> Point{
Point{
x: self.x + other.x,
y: self.x + other.y,
}
}
}

trait Add<RHS=Self>{
//`Add`trait定义,包含有泛型参数,但是在实现该trait之前
//应该必须要为泛型指定具体类型
//`RHS=Self`就是*默认类型参数*语法,泛型参数`RHS`默认为
//`Self`类型(`+`左值类型)
//RHS:right hand side
type Output;
fn add(self, rhs: RHS) -> Self:Output;
}

运算符重载

Rust不允许创建自定义运算符、重载任意运算符,不过 std::ops中的运算符、相应的trait可以通过实现相关trait重载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
use std::ops::Add;
#[derive(Debug, PartialEq)]
struct Millimeters(u32);
struct Meters(u32);

impl Add<Meters> for Millimeters{
//`Add`trait中`RHS`不是默认类型`Self`,`Add<Meters>`
//设置`RHS`为`Meters`
//此运算符重载允许`Millmeters`类型和`Meters`类型能够
//直接相加
type Output = Millimeters;
fn add(self, other: Meters) -> Millimeters{
Millimters(self.0 + (other.0 * 1000))
}
}

消歧义

Rust无法避免两个trait具有相同名称的方法,也无法阻止某类型 同时实现两个这样的trait(或者是类型已经实现同名方法),此时 需要明确指定使用哪个方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
trait Pilot{
fn fly(&self);
}
trait Wizard{
fn fly(&self);
}
struct Human;

impl Pilot for Human{
fn fly(&self){
println!("this is your captain speaking");
}
}
impl Wizard for Human{
fn fly(&self){
println!("up!");
}
}
impl Human{
fn fly(&self){
println!("waving arms furiously!");
}
}

fn main(){
let person = Human;
Pilot::fly(&person);
//`Pilot`trait中方法的消歧义写法
Wizard::fly(&person);
person.fly();
//默认调用直接实现在**类型**上的方法
Person::fly(&person);
//`Person`类型中方法消歧义写法,一般不使用
}

Fully Qualified Syntax

方法获取self参数

  • 不同类型、同方法名,Rust根据self类型可以判断调用何函数
  • 同类型、同方法名,消歧义语法可以指定调用何函数

而对于关联函数,没有self参数,某类型有同名的两个关联函数 时,无法使用消歧义语法指定调用何函数,需要使用完全限定语法 <Type as Trait>::function(receiver_if_method), next_args, ...)

当然,完全限定语法可以用于所有trait方法、关联函数场合,其中 recevier_if_method即表示方法中self参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
trati Animal{
fn baby_name() -> String;
}
struct Dog;
impl Dog{
fn baby_name() -> String{
String::from("Spot")
}
}
impl Animal for Dog{
fn baby_name() -> String{
String::from("puppy")
}
}
fn main(){
println!("A baby dog is called a {}", Dog::baby_name());
//调用`Dog`的关联函数
println!("A baby dog-animal is called a {}", <Dog as Animal>::baby_name());
//完全限定语法
}

Super Trait

有时某个trait可能需要使用另一个trait的功能,要求某类型实现 该trait之前实现被依赖的trait,此所需的trait就是超(父)trait

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
trait OutlinePrint: fmt::Display{
//`OutlinePrint`trait依赖于`fmt::Display`trait
//在实现`OutlinePrint`之前,需要实现`fmt::Display`
fn outline_print(&self){
let output = self.to_string();
let len = output.len();
println!("{}", "*".repeat(len + 4));
println!("* {} *", output);
println!("{}", "*".repeat(len+4));
}
}
struct Point{
x: i32,
y: i32,
}
impl fmt::Display for Point{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result{
write!(f, "({}, {})", self,x, self.y)
}
}
impl OutlinePrint for Point{}
//`OutlinePrint`中所有方法均有默认实现

高级类型

Newtype Pattern

孤儿规则限制了只有trait、类型其中只有位于当前crate时,才能对 类型实现trait,使用newtype模式可以“绕过”该限制,即创建新的 元组结构体类型,其中只包含该类型一个成员,此时封装类型对于 crate是本地的。newtype概念源自于Haskell,此模式没有运行时 性能损失,封装类型在编译器时已经省略了

1
2
3
4
5
6
7
8
9
10
11
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")]);
prinlnt!("w = {}", w);
}

但是Newtype模式中Wrapper是一个新类型,其上没有定义方法, 需要手动实现self.0的所有方法。或者,为Wrapper实现 Dereftrait,并返回self.0,但是此方式下Wrapper会具有 所有self.0的所有方法,如果需要限制封装类型行为,只能自行 实现所需的方法。

Type创建类型别名

type关键字可以给予现有类型别名

  • type不是创建新、单独类型,而是创建别名,而newtype模式 则是真正创建了新类型,也因此无法像newtype模式一样进行 类型检查

    1
    2
    3
    4
    5
    6
    7
    type Kilometers = i32;
    //`type`不是创建新、单独的类型,而是赋予别名,两个类型
    //将得到相同的对待
    let x: i32 = 5;
    let y: Kilometers = 10;
    println!("x + y = {}", x + y);
    //`Kilometers`类型和`i32`类型完全相同,直接进行运算
  • 类型别名主要用途是避免重复

    1
    2
    3
    4
    5
    6
    type Thunk = Box::<Fn() + Send + `static>;
    let f: Thunk = Box::new(|| println!("hi"));
    fn takes_long_type(f: Thunk){
    }
    fn returns_long_type() -> Thunk{
    }

Never Type

Rust中有一个特殊的类型!,被称为empty type(never type)

  • !的正式说法:never type可以强转为其他任何类型
  • 无法被创建
  • 用于发散函数(diverging functions,从不返回的函数)的 返回值

    todo 这个和无返回值函数有何区别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
let guess: u32 = metch guess.trim().parse(){
Ok(num) => num,
Err(_) => continue,
//`continue`的值即为`!`,`match`分支的返回值必须相同
//而`!`没有值,因此确定`match`的返回值类型为`u32`
};

impl<T> Option<T>{
pub fn unwrap(self) -> T{
// `Option::unwrap`定义
match self{
Some(val) => val,
None => panic!("called `Option::unwrap()` on a `None` value"),
// `panic!`是`!`类型,不返回值而是终止程序
}
}
}

println!("forever");
loop{
// 循环永不结束,表达式值是`!`
// 如果加上`break`就不再如此
println!("for ever");
}

Dynamically Sized Types

动态大小类型:“DST”或者“uniszed type”,这些类型允许处理 在运行时才知道大小的类型。Rust需要知道特定类型值需要分配的 内存空间,同类型的值必须使用相同数量的内存,因此必须 将动态大小类型的值至于某种指针之后(此即动态大小类型的 黄金规则),并且使用某些额外的元信息存储动态信息的大小。

str就是动态大小类型,&str则是两个值:str的地址和长度, 这样&str就有了一个在编译时可以知道的大小,并且str可以和 所有类型的指针结合Box<str>Rc<str>。同样的,trait也是 动态大小类型,为了使用trait对象,必须将将其放入指针之后。

Sized trait

Rust自动为编译器在编译时就知道大小的类型实现Sized trait, 且Rust隐式的为每个泛型增加了Sized bound

1
2
3
4
5
6
7
8
9
10
11
12
fn generic<T>(t: T){
}
fn generic<T: Sized>(t: T){
//实际上按照此函数头处理
//即默认情况下,泛型参数不能是DST
}

fn generic<T: ?Sized>(t: &T){
//`?Sized`是特殊的语法,只能用于`Sized` trait不能用于
//其他trait,表示泛型`T`可能不是`Sized`的,此时参数类型
//不能是`T`,必须是指针类型的
}

泛型(generic)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
fn largest<T>(list: &[T]) -> T {}
//函数签名中泛型

struct Point<T>{
x: T,
y: T,
}
struct Point<T, U>{
x: T,
y: U,
}
//结构体定义中泛型

enum Option<T>{
Some(T),
None,
}
enum Result<T, E>{
Ok(T),
Err(E),
}
//枚举定义中泛型

方法实现中泛型

  • impl后声明泛型impl<T>表示Point<T>中的T是泛型而 不是具体类型,是对所有的泛型结构体实现

    1
    2
    3
    4
    impl<T> Point<T>{
    fn x(&self) -> &T{
    }
    }
  • impl后不声明泛型,则表示Point<T>T为具体类型, 此时仅对Point<T>类型实现方法

    1
    2
    3
    4
    5
    impl Point<f32>{
    fn x(&self) -> f32{
    }
    }
    //仅`Point<f32>`实现此方法,其他`T`类型没有
  • 结构体定义中的泛型和方法签名中泛型不一定一致

    1
    2
    3
    4
    5
    6
    7
    8
    impl<T, U> Point<T, U>{
    fn mixup<V,W>(self, other:Point<V,W>) -> Point<T,W>{
    Point{
    x: self.x,
    y: other.y,
    }
    }
    }

trait实现中的泛型

1
2
3
4
5
impl<T:Display> ToString for T{
}
// 这个时标准库中的一个例子,使用了trait bounds
// 不使用trait bounds,那感觉有些恐怖。。。

trait定义中的没有泛型,但是其中可以包含泛型方法,同普通 函数

泛型代码的性能

rust在编译时将代码单态化(monomorphization)保证效率,所以 rust使用泛型代码相比具体类型没有任何性能损失

单态化:将泛型代码转变为实际放入的具体类型

1
2
3
4
5
6
7
8
9
10
11
12
13
let integer = Some(5);
let float = Some(5.0);
//单态化
enum Option_i32{
Some(i32),
None,
}
enum Option_f64{
Some(f64),
None,
}
let integer = Option_i32::Some(5);
let float = Option_f64::Some(5.0);

Trait Bounds

指定泛型的trait bounds:限制泛型不再适合任何类型,编译器 确保其被限制为实现特定trait的类型

  • 指定函数泛型trait bounds限制参数类型

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    pub fn notify<T: Summarizable>(item:T){}
    // 一个trait bound

    pub fn some_fn<T: Display+Clone, U: Debug+Clone>(t:T, u:U) -> 32{}
    // 多个trait bounds

    pub fn some_fn<T, U>(t:T, u:U) -> 32
    where T:Display + Clone,
    U: Debug + Clone{
    }
    // where从句写法
  • 指定方法泛型trait bounds有条件的为某些类型实现

    1
    2
    3
    4
    impl<T: Display+PartialOrd> Point<T>{
    fn cmp_display(&self){
    }
    }

trait和泛型的比较

trait和泛型都是抽象方法

  • trait从方法角度抽象

    • 定义一组公共“行为”
    • “标记(trait bounds)”特定类型(泛型)
  • 泛型从类型的角度抽象

    • 一组(trait bounds)类型定义“项”structenum
    • 一组(trait bounds)类型实现函数、trait
  • trait的定义中不应该出现泛型

    • trait本意应该是定义一组“行为”,需要特定类型实现其方法 (当然有的方法有默认实现),其对应的“对象”不是类型而 是方法,与泛型用途无关
    • trait中定义泛型无意义,trait只是一个“包裹”,真正实现 的是其中的方法,如有必要,定义含有泛型参数的方法即可
    • 若trait中可以使用泛型,则有可能对不同的泛型具体 类型实现“相同”(函数签名没有泛型参数)函数 (在trait中有关联类型提供略弱与泛型的功能)

Rust 程序设计笔记

参数、环境变量

  • std::env::args():返回所有参数的一个迭代器,第一参数 是可执行文件,包含任何无效unicode字符将panic!

    args: Vec = std::env::args().collect()

  • std::env::var("env_var"):获取设置的环境变量值,返回 一个Result

    • 环境变量设置时返回包含其的Ok成员
    • 未设置时返回Err成员

    设置“环境变量”并执行程序:$>ENV_NAME=val cargo run