Rust高级

无代码,只作知识点记住,详细可查官方wiki:doc

borrowing 借用与lifetime生命周期

borrowing(引用的函数式命名)

  • 引用:
    • 引用是一种变量的别名,通过&符号来创建。(非所有权)
    • 引用可以是不可变的(&T)或可变的(&mut T)
    • 引用允许在不传递所有权的情况下访问数据,它们是安全且低开销的。
  • 借用:
    • 借用是通过引用来借用数据,从而在一段时间内访问数据而不拥有它。
    • 借用分为可变借用与不可变借用。可变借用(&mut)允许修改数据,但在生命周期内只能有一个可变借用。不可变借用(&)允许多个同时存在,但不允许修改数据。

borrow checker的规则

  1. 不可变引用规则:
    1. 在任何给定的时间,要么有一个可变引用,要么有多个不可变引用,但不能同时存在可变引用与不可变引用。这确保了在同一时间只有一个地方对数据进行修改,或者有多个地方同时读取数据。
  2. 可变引用规则:
    1. 在任何给定的时间,只能有一个可变引用来访问数据。这防止了并发修改相同数据的问题,从而防止数据竞争。

生命周期规则:

  1. 引用的生命周期必须在被引用的数据有效的时间范围内。这防止了悬垂引用,即引用的数据已经被销毁,但引用仍然存在。
  2. 可变引用与不可变引用不互斥:
    1. 可以同时存在多个不可变引用,因为不可变引用不会修改数据,不会影响到其他引用。但不可变引用与可变引用之间是互斥的。

手动指定lifetime

生命周期参数在函数/结构体签名中指定:

  1. 一般情况下borrow checker会自行推断
  2. 在函数/结构体中使用生命周期参数允许函数声明引用的有效范围。

任何引用都是有生命周期

大多数情况下,生命周期是隐藏且被推断的

生命周期的主要目的是防止悬垂引用

生命周期与函数

编译器在没有显式注解的情况下,使用三个规则来推断这些生命周期

  1. 第一个规则是每个作为引用的参数都会得到它自己的生命周期参数。
  2. 第二个规则是,如果只有一个输入生命周期参数,那么该生命周期将被分配给所有输出生命周期参数(该生命周期将分配给返回值)。
  3. 第三个规则是,如果有多个输入生命周期参数,但其中一个是对self或不可变self的引用时。因为在这种情况下它是一个方法,所以self是生命周期被分配给所有输出生命周期参数

结构体中的引用

在结构体中的引用需要标注生命周期

结构体的方法(&self等)不需要标注生命周期

泛型

泛型是一种编译语言的特性,它允许在代码中使用参数化类型,以便在不同地方使用相同的代码逻辑处理多种数据类型,而无需为每种类型编写单独的代码。

作用:

  1. 提高代码的重用性
  2. 提高代码的可读性
  3. 提高代码的抽象度

泛型的应用类型

  1. 泛型定义结构体\枚举
  2. 泛型定义函数
  3. 泛型与特质

泛型与函数

在rust中,泛型也可以用于函数,使得函数能够处理多种类型的参数,提高代码的重用性与灵活性。

  1. 泛型与函数
  2. 泛型与结构体中的方法

Trait

在rust中,特质(trait)是一种定义方法签名的机制

特质允许定义一组方法的签名,但可以不提供具体的实现(也可以提供)。这些方法签名可以包括参数和返回类型,但可以不包括方法的实现代码。

任何类型都可以实现特质,只要它们提供了特质中宝的的所有方法。这使得你可以为不同类型提供相同的行为。

特点

  1. 内置常量:特质可以内置常量,特质中定义的常量在程序的整个生命周期内都是有效的。
  2. 默认实现:特质可以提供默认的方法实现。如果类型没有为特质中的某个方法提供自定义实现,将会使用默认实现。
  3. 多重实现:割开可以实现多个特质,这允许你将不同的行为组合在一起
  4. 特质边界:在泛型代码中,可以使用特质作为类型约束。这被称为特质边界,它限制了泛型类型必须实现的特质。
  5. Trait Alias:rust还支持trait alias,允许为复杂的trait组合创建简洁的别名,以便在代码中更轻松地引用。

trait object

  1. 在运行时动态分配的对象
    1. 运行时泛型
    2. 比泛型要灵活的多
  2. 可以在集合中混入不同的类型对象
    1. 更容易处理相似的数据
  3. 有一些小小的性能损耗

dyn关键字

dyn是rust中的关键字,用于声明特质对象的类型。特质对象是实现了特定特质的类型的实例,但其具体类型在编译时是未知的。因此,为了让编译器知道我们正在处理的是特质对象,我们需要在特质名称前面加上dyn 关键字。

dyn关键字的作用是指示编译器处理特质对象。

rust中数据传输的三种形式

  1. 不可变引用&dyn Trait
  2. 可变引用&mut dyn Trait
  3. move语义所有权转移:特质需要用Box`实现move,如果需要在函数调用之间传递特质的所有权,并用希望避免在栈上分配大量的内存,可以使用

特质与Box

创建trait object的三种方式

第一种

1
2
let o = Object{};
let o_obj: &dyn Object = &o;

第二种

1
let o_obj: &dyn Object = &Object{};

第三种

1
let o_obj: Box<dyn Object> = Box::new(Object{})

第一种与第二种都是创建不可变引用

第三种最常用也最灵活,一般来说会使用Box和特质来组成集合元素

泛型与impl不同的写法

  • fn call(item1: &impl Trait, item2: &ipmpl Trait);可以是不同的类型
  • fn call_generic<T: Trait>(item1: &T, item2: &T);可以是相同类型

Multiple Trait Bounds

  • fn call(item1: &(impl Trait+ AnotherTrait));
  • fn call_generic<T: Trait+ AnotherTrait>(item1: &T);

Rust重载操作符

只需要实现相应的特质

为结构体实现一个加号的例子

rust不支持面向对象

rust不支持传统的继承的概念,倡可以在特质中通过层级化来完成你的需求

rust选择了一种函数式的编程范式,即“组合和委托”而非“继承”

编程语言的大势也是组合优于继承

多态

rust中多态无处不在。

迭代器

循环

定义:循环是一种控制流结构,它会反复执行一组语句,直到满足某个条件。

控制条件:循环通常包含一个条件表达式,只有在条件为真时,循环体中的语句才会执行。

退出条件:循环执行直到条件不再满足,或者通过break语句旷工中断循环。

使用场景:适用于需要反复执行某个操作直到满足某个条件的情况。

迭代

定义:迭代是对序列中的元素进行逐个访问的过程。

控制条件:迭代通常使用迭代器来实现,迭代器提供了对序列元素的访问和操作。

退出条件:通常不需要显式的退出条件,迭代器会在处理完所有元素后自动停止。

使用场景:适用于需要遍历数据结构中的元素的情况,如数组,切片,集合等。

区别

  1. 循环是一种控制流结构,它反复执行一组语句。
  2. 迭代是对序列中的元素进行逐个访问的过程,通常使用迭代器实现。
  3. 循环可以是有限的或无限的
  4. 迭代器提供了一种更抽象的方式来处理序列,使得代码更具可读性与灵活性

在rust中,循环与迭代性能的差距可能会取决于具体的使用情况与编译器的优化。绝大多数情况下,rust的代码器是经过优化的,可以达到或接近手动编写循环的性能水平。

IntoIterator

IntoIterator是一个rust 特质,它定义了一种将类型转换为迭代器的能力。

该特质包含一个方法 into_iter,该方法返回了一个实现了Iterator Trait的迭代器。

通常,当有一个类型,希望能够对其进行迭代时,会实现 IntoIterator特质来提供将该类型转换为迭代器的方法。

Iterator

Iterator是rust标准库中的特质,定义了一种访问序列元素的方式。

包含了一系列方法,如nextmapfiltersum等,用于对序列进行不同类型的操作。

通过实现Iterator特质,可以创建自定义的迭代器,以定义如何迭代你的类型中的元素。

获取迭代器的三种方法iter()iter_mut()Into_iter()

iter()方法

该方法返回一个不可变引用的迭代器,用于只读访问集合的元素。

该方法适用于在希望不修改集合的情况下迭代元素的场景。

iter_mut方法

返回一个可变引用的迭代器,用于允许修改集合中的元素。

该方法适用于希望在迭代过程中修改集合元素的场景。

该方法性能非常差,尽量要避免。

into_iter()方法

该方法返回一个拥有所有权的迭代器,该迭代器会消耗集合本身,将所有权转移到迭代器。

该方法适用于希望在迭代过程中拥有集合的所有权,以便进行消耗性的操作,如移除元素。

closures闭包

闭包

闭包是一种可以捕获其环境中变量的匿名函数

闭包的简洁灵活,同时也具有强大的功能。闭包在rust中被广泛用于函数式编程,并发编程以及简化代码等方面。

定义一个闭包

  • 定义闭包的语法类似
    • ||内定义参数
    • 可选地指定参数,返回类型
    • {}内定义闭包体

可以将闭包分配给一个变量

然后使用该变量,就像它是一个函数名,来调用闭包。

获取外部参数

由rust编译器决定哪种方式获取外部参数

  1. 不可变引用Fn
  2. 可变引用FnMut
  3. 转移所有权(move)FnOnce

所有权转移Move

Rust 编译器判断capures by value,比如在闭包手动drop该参数

move关键字强制将所有权转移到闭包

闭包的工作方式

  1. Rust编译器将闭包放入一个结构体
  2. 结构体会声明一个call function,而闭包就是函数,call function会包含闭包的所有代码。
  3. 结构体会生产一些属性去捕获闭包外的参数
  4. 结构体会实现一些特质

FnOnce,FnMut,Fn的关系:

1
2
3
4
5
Fn : FnMut : FnOnce

FnOnce实现move所有权转移
FnMut实现可变引用,实现了FnOnce和FnMut
Fn实现不可变引用