0%

Rust学习随笔

常见概念

类型

  • 类型推导与标注

      1. Rust是静态类型语言
      1. 编译器可以根据变量的值和上下文的使用方式自动推导出变量的类型
      1. 在无法推导出变量类型的情况下需要手动标注类型
  • 基本类型

    • 最小化原子类型(无法被结构)

    • 分类

      • 数值类型

        • 整数类型

          • 整数是没有小数部分的数字

          • 类型定义的形式统一为:有无符号 + 类型大小(位数)。无符号数表示数字只能取正数和0,而有符号则表示数字可以取正数、负数还有0。

          • 有符号数以补码形式存储

          • 整形字面量可以用下表的形式书写

          • 默认使用i32

          • 整数溢出

            • 当在 debug 模式编译时,Rust 会检查整型溢出,若存在这些问题,则使程序在编译时 panic(崩溃,Rust 使用这个术语来表明程序因错误而退出)

            • 在当使用 –release 参数进行 release 模式构建时,Rust 不检测溢出。相反,当检测到整型溢出时,Rust 会按照补码循环溢出(two’s complement wrapping)的规则处理。简而言之,大于该类型最大值的数值会被补码转换成该类型能够支持的对应数字的最小值。

            • 要显式处理可能的溢出,可以使用标准库针对原始数字类型提供的这些方法:

              • 使用 wrapping_* 方法在所有模式下都按照补码循环溢出规则处理,例如 wrapping_add

              • 如果使用 checked_* 方法时发生溢出,则返回 None 值

              • 使用 overflowing_* 方法返回该值和一个指示是否存在溢出的布尔值

              • 使用 saturating_* 方法,可以限定计算后的结果不超过目标类型的最大值或低于最小值

        • 浮点类型

          • 默认浮点类型是 f64,在现代的 CPU 中它的速度与 f32 几乎相同,但精度更高

          • 浮点数根据 IEEE-754 标准实现。f32 类型是单精度浮点型,f64 为双精度

          • 避免在浮点数上测试相等性

          • 当结果在数学上可能存在未定义时,需要格外的小心

          • 对于数学上未定义的结果,Rust 的浮点数类型使用 NaN (not a number)来处理这些情况

            • 可以使用 is_nan() 等方法,可以用来判断一个数值是否是 NaN
        • 序列

          • 用来生成连续的数值

          • 只允许用于数字或字符类型,原因是:它们可以连续,同时编译器在编译期可以检查该序列是否为空,字符和数字值是 Rust 中仅有的可以用于判断是否为空的类型

        • 运算

          • 数字运算

            • +

            • -

            • *

            • /

            • %

          • 位运算

      • 布尔类型

        • 有两个可能的值:true 和 false

        • 布尔值占用内存的大小为 1 个字节

      • 字符类型

        • Unicode编码 占4Byte
      • 单元类型,即(),唯一的值也是()

        • main 函数返回这个单元类型 ()

        • 没有返回值的函数在 Rust 中是有单独的定义的:发散函数( diverge function )

        • 内存占用为0!

      • 语句

        • 执行一些操作但是不会返回一个值

        • ;结尾

      • 表达式

        • 求值后返回一个值

        • 表达式不能包含分号

        • 表达式如果不返回任何值,会隐式地返回一个()

        • Rust函数就是表达式

      • 函数

        • 函数名和变量名使用蛇形命名法(snake case),例如 fn add_two() -> {}

        • 函数的位置可以随便放,Rust 不关心我们在哪里定义了函数,只要有定义即可

        • 每个函数参数都需要标注类型

          • Rust 是强类型语言,因此需要你为每一个函数参数都标识出它的具体类型
        • 返回

          • 函数的返回值就是函数体最后一条表达式的返回值,也可以使用 return 提前返回
        • 发散函数:返回值为!,表示永不返回

      • 方法

        • 使用impl关键字定义

        • Rust 的对象定义和方法定义是分离的

        • Self

          • self 表示 Rectangle 的所有权转移到该方法中,这种形式用的较少

          • &self 表示该方法对 Rectangle 的不可变借用

          • &mut self 表示可变借用

          • &self 其实是 self: &Self 的简写

        • 使用方法代替函数的优点

          • 不用在函数签名中重复书写 self 对应的类型

          • 代码的组织性和内聚性更强,对于代码维护和阅读来说,好处巨大

        • 关联函数

          • 定义在 impl 中且没有 self 的函数被称之为关联函数
  • 复合类型

    • 字符串

      字符串是由字符组成的连续集合
      Rust 中的字符是 Unicode 类型,因此每个字符占据 4 个字节内存空间,但是在字符串中不一样,字符串是 UTF-8 编码,也就是字符串中的字符所占的字节数是变化的(1 - 4),这样有助于大幅降低字符串所占用的内存空间。

      • String

      • &str

      • String&str 的转换

        • String&str

          • &s

          • &s[..]

          • s.as_str()

        • &strString

          • String::from(“hello,world”)

          • “hello,world”.to_string()

      • 底层实现

        • 类型为u8类型的字节数组

        • 使用UTF-8编码,为了防止奇怪的返回值,不允许对字符串使用索引

        • 使用切片时要额外注意,不要切出没有意义的切片,并且会导致程序崩溃

      • 常用方法

        • push 追加字符char

        • push_str追加字符串字面量

        • insert插入单个字符char

        • insert_str插入字符串字面量

        • replace_range接收两个参数,第一个参数是要替换字符串的范围(Range),第二个参数是新的字符串

        • pop删除并返回字符串的最后一个字符
          存在返回值,其返回值是一个 Option 类型,如果字符串为空,则返回 None

        • remove 删除并返回字符串中指定位置的字符
          存在返回值,其返回值是删除位置的字符串
          按照字节来处理字符串的,如果参数所给的位置不是合法的字符边界,则会发生错误

        • truncate删除字符串中从指定位置开始到结尾的全部字符
          无返回值
          按照字节来处理字符串的,如果参数所给的位置不是合法的字符边界,则会发生错误

        • clear清空字符串

        • replace适用于 String 和 &str 类型。replace() 方法接收两个参数,第一个参数是要被替换的字符串,第二个参数是新的字符串。该方法会替换所有匹配到的字符串

        • replacen该方法可适用于 String 和 &str 类型。replacen() 方法接收三个参数,前两个参数与 replace() 方法一样,第三个参数则表示替换的个数。

        • 使用 + 或者 += 连接字符串,要求右边的参数必须为字符串的切片引用(Slice)类型

        • 使用 format! 连接字符串
          适用于 String 和 &str
          用法见格式化输出

      • 转义

        • 可以通过转义的方式 \ 输出 ASCII 和 Unicode 字符
      • 操作UTF-8字符串

        • 遍历字符

        • 遍历字节

    • 元组

      • 长度固定

      • 顺序固定

      • 可以使用模式匹配或者 . 操作符来获取元组中的值

        • 模式匹配

        • .运算符

      • 创建

    • 结构体

      • 组成

        • 通过关键字 struct 定义

        • 一个清晰明确的结构体 名称

        • 几个有名字的结构体 字段

      • 创建实例

        • 初始化实例时,每个字段都需要进行初始化

        • 初始化时的字段顺序不需要和结构体定义时的顺序一致

        • 简化创建:当函数参数和结构体字段同名时,可以直接使用缩略的方式进行初始化

      • 访问字段

        • 通过 . 操作符即可访问结构体实例内部的字段值,也可以修改它们

        • 必须要将结构体实例声明为可变的,才能修改其中的字段

        • 不支持将某个结构体某个字段标记为可变

      • 结构体更新语法..old_val

        • 必须在结构体的尾部使用

      • 内存布局

      • 元组结构体

        • 结构体必须要有名称,但是结构体的字段可以没有名称,这种结构体长得很像元组,因此被称为元组结构体
      • 单元结构体

        • 没有字段和属性

        • 不关心属性只关心行为

      • 结构体数据的所有权

        • 想要结构体拥有自己的数据而为借用别人的数据

        • 在借用数据时需要声明生命周期

      • 使用 #[derive(Debug)] 来打印结构体的信息

        • 使用格式化输出{:?}需要实现Display trait
      • 使用dbg!

    • 枚举

      • 概念

        • 允许通过列举可能的成员来定义一个枚举类型

        • 枚举类型是一个类型,它会包含所有可能的枚举成员, 而枚举值是该类型中的具体某个成员的实例。

      • 枚举值

        • 通过::操作符来访问枚举类下的具体成员

        • 枚举的成员默认是从 0 开始递增的,每个成员的值都是前一个成员值加 1

      • 数据关联到枚举成员

      • `Option枚举

        • 用于处理空值
    • 数组

      • array

        • 速度很快但是长度固定

        • 三要素

          • 长度固定

          • 元素必须有相同的类型

          • 依次线性排列

        • 声明

          • 数组的元素类型要统一,长度要固定

          • 初始化一个某个值重复出现 N 次的数组

        • 访问数组元素

          • 因为数组是连续存放元素的,因此可以通过索引的方式来访问存放其中的元素

          • 越界访问

            • 会导致panic
        • 数组元素为非基础类型

          • 需要类型实现std::marker::Copy trait

          • 或者使用std::array::from_fn

        • 数组切片

          • 切片的长度可以与数组不同,并不是固定的,而是取决于你使用时指定的起始和结束位置

          • 创建切片的代价非常小,因为切片只是针对底层数组的一个引用

          • 切片类型[T]拥有不固定的大小,而切片引用类型&[T]则具有固定的大小,因为 Rust 很多时候都需要固定大小数据类型,因此&[T]更有用,&str字符串切片也同理

      • Vector

        • 可动态增长的但是有性能损耗
  • 集合类型

    • String

    • Vector

      • 创建

        • Vec::new

        • vec![]

        • Vec::from

      • 更新

        • push
      • 读取

        • 通过下标索引访问。

        • 使用 get 方法。

      • 迭代

      • 插入

        • insert

        • append

      • 删除

        • pop

        • remove

        • drain

      • 截断

        • truncate
      • 保留指定条件的元素

        • retain
      • 存储不同类型的元素

        • 通过特征对象或枚举实现
      • 排序

        • 稳定的排序

          • sort

          • sort_by

        • 非稳定排序

          • sort_unstable

          • sort_unstable_by

    • HashMap

      • 导入

        • use std::collections::HashMap;
      • 创建

        • new

        • 使用迭代器和 collect 方法创建

      • 插入

        • insert
      • 查询

        • get
      • 更新

      • 所有权

        • HashMap 的所有权规则与其它 Rust 类型没有区别:

        • 若类型实现 Copy 特征,该类型会被复制进 HashMap,因此无所谓所有权

        • 若没实现 Copy 特征,所有权将被转移给 HashMap 中

      • 如果使用引用类型放入 HashMap 中,请确保该引用的生命周期至少跟 HashMap 活得一样久

      • f32 和 f64 浮点数,没有实现 std::cmp::Eq 特征,因此不可以用作 HashMap 的 Key

切片

  • 允许引用集合中部分连续的元素序列,而不是引用整个集合。

  • 创建切片的语法,使用方括号包括的一个序列:[开始索引..终止索引]

    • 开始索引是切片中第一个元素的索引位置

    • 终止索引是最后一个元素后面的索引位置

流程控制

  • 分支控制

    • if else

      • if语句是表达式 可以通过if表达式的返回值进行变量绑定(赋值

      • 可以使用else if实现更复杂的条件分支判断

    • match

  • 循环控制

    • for

      • 往往使用集合的引用形式

      • 不使用引用的话,所有权会被转移(move)到 for 语句块中,后面就无法再使用这个集合

      • 对于实现了copy trait的数组会对元素进行拷贝

      • 如果想在循环中,修改该元素,可以使用 mut 关键字

      • 获取元素索引:使用迭代器

    • while

    • loop

      • 简单的无限循环

      • loop 是一个表达式,因此可以返回一个值

    • 使用 continue 可以跳过当前当次的循环,开始下次的循环

    • 使用 break 可以直接跳出当前整个循环

      • break 可以单独使用,也可以带一个返回值,有些类似 return

模式匹配

match

  • match 的匹配必须要穷举出所有可能,因此这里用 _ 来代表未列出的所有可能性

  • match 的每一个分支都必须是一个表达式,且所有分支的表达式最终返回值的类型必须相同

  • X | Y,类似逻辑运算符 或,代表该分支可以匹配 X 也可以匹配 Y,只要满足一个即可

  • match 本身也是一个表达式,因此可以用它来赋值

if let

  • 只关心某一个值是否存在

  • 只要匹配一个条件,且忽略其他条件时就用 if let ,否则都用 match

matches

  • 可以将一个表达式跟模式进行匹配,然后返回匹配的结果 true or false

模式绑定

  • 模式匹配的另外一个重要功能是从模式中取出绑定的值

  • @ 操作符可以让我们将一个与模式相匹配的值绑定到新的变量上

穷尽匹配

  • 使用_ 通配符处理不需要匹配的值

解构Option

模式

模式是 Rust 中的特殊语法,它用来匹配类型中的结构和数据,它往往和 match 表达式联用,以实现强大的模式匹配能力。

  • 组成

    • 字面值

    • 解构的数组、枚举、结构体或者元组

    • 变量

    • 通配符

    • 占位符

  • 使用情景

    • match

    • if let

    • while let

    • for

    • let

    • 函数参数

全模式列表

变量绑定与解构

变量绑定

可变性

  • 变量默认不可变

  • 使用mut关键字声明可变

  • 常量:不仅默认不可变并且自始至终不可变

    • 使用const标注

    • 必须标明类型

使用_可以使编译器忽略未被使用的变量

解构

  • let表达式可以从一个相对复杂的变量中匹配出该变量的一部分内容

解构式赋值

  • 在 Rust 1.59 版本后,可以在赋值语句的左式中使用元组、切片和结构体模式了

变量遮蔽

  • Rust允许声明相同的变量名,在后面声明的变量会遮蔽掉前面声明的

所有权与借用

所有权

  • Rust 中每一个值都被一个变量所拥有,该变量被称为值的所有者

  • 一个值同时只能被一个变量所拥有,或者说一个值只能拥有一个所有者

  • 当所有者(变量)离开作用域范围时,这个值将被丢弃(drop)

  • 当把一个变量赋值(?)给另一个变量时,如果没有实现copy trait,则称为移动(move),会将所有权转移给后一个变量。

    • Rust 永远也不会自动创建数据的 “深拷贝”

    • 任何基本类型的组合可以 Copy ,不需要分配内存或某种形式资源的类型是可以 Copy 的

    • 部分move

      • 当解构一个变量时,可以同时使用 move 和引用模式绑定的方式。当这么做时,部分 move 就会发生:变量中一部分的所有权被转移给其它变量,而另一部分我们获取了它的引用。

      • 在这种情况下,原变量将无法再被使用,但是它没有转移所有权的那一部分依然可以使用,也就是之前被引用的那部分。

引用与借用

  • 引用与解引用

    • 常规引用是一个指针类型,指向了对象存储的内存地址。使用&创建引用

    • 使用*解引用

    • 引用的两种类型

      • 可变引用

        • 使用&mut创建引用

        • 只能同时存在一个

      • 不可变引用

        • 使用&创建不可变引用

        • 可变引用与不可变引用不能同时存在

    • 悬垂引用(Dangling References)

      悬垂引用也叫做悬垂指针,意思为指针指向某个值后,这个值被释放掉了,而指针仍然存在,其指向的内存可能不存在任何值或已被其它变量重新使用。在 Rust 中编译器可以确保引用永远也不会变成悬垂状态:当你获取数据的引用后,编译器可以确保数据不会在引用结束前被释放,要想释放数据,必须先停止其引用的使用。

  • 获取变量的引用,称之为借用(borrowing)

函数式编程

闭包

  • 概念

    • 是可以保存在一个变量中或作为参数传递给其他函数的匿名函数

    • 可以在一个地方创建闭包,然后在不同的上下文中执行闭包运算。

    • 不同于函数,闭包允许捕获被定义时所在作用域中的值。

  • 闭包会捕获定义其所处环境中的值并使用

  • 闭包关联于小范围的上下文,通常情况下可以自动推断值的类型,也可以手动标注

  • 捕获引用或者移动所有权

    闭包可以通过三种方式捕获其环境,它们直接对应到函数获取参数的三种方式:不可变借用,可变借用和获取所有权。闭包会根据函数体中如何使用被捕获的值决定用哪种方式捕获。

    • 不可变借用

    • 可变借用

    • 获取所有权

      即使闭包体不严格需要所有权,如果希望强制闭包获取它用到的环境中值的所有权,可以在参数列表前使用 move 关键字

  • 将被捕获的值移出闭包和 Fn trait

    一旦闭包捕获了定义它的环境中一个值的引用或者所有权(也就影响了什么会被移 进 闭包,如有),闭包体中的代码定义了稍后在闭包计算时对引用或值如何操作(也就影响了什么会被移 出 闭包,如有)。闭包体可以做以下任何事:将一个捕获的值移出闭包,修改捕获的值,既不移动也不修改值,或者一开始就不从环境中捕获值。闭包捕获和处理环境中的值的方式影响闭包实现的 trait。Trait 是函数和结构体指定它们能用的闭包的类型的方式。取决于闭包体如何处理值,闭包自动、渐进地实现一个、两个或三个 Fn trait。

    • FnOnce 适用于能被调用一次的闭包,所有闭包都至少实现了这个 trait,因为所有闭包都能被调用。一个会将捕获的值移出闭包体的闭包只实现 FnOnce trait,这是因为它只能被调用一次。

    • FnMut 适用于不会将捕获的值移出闭包体的闭包,但它可能会修改被捕获的值。这类闭包可以被调用多次。

    • Fn 适用于既不将被捕获的值移出闭包体也不修改被捕获的值的闭包,当然也包括不从环境中捕获值的闭包。这类闭包可以被调用多次而不改变它们的环境,这在会多次并发调用闭包的场景中十分重要。

迭代器

迭代器模式允许你对一个序列的项进行某些处理。迭代器(iterator)负责遍历序列中的每一项和决定序列何时结束的逻辑。当使用迭代器时,我们无需重新实现这些逻辑。

在 Rust 中,迭代器是 惰性的(lazy),这意味着在调用方法使用迭代器之前它都不会有效果。

  • 创建

  • 迭代器都实现了一个叫做 Iterator 的定义于标准库的 trait

    next 是 Iterator 实现者被要求定义的唯一方法。next 一次返回迭代器中的一个项,封装在 Some 中,当迭代器结束时,它返回 None。

    从 next 调用中得到的值是 vector 的不可变引用。iter 方法生成一个不可变引用的迭代器。如果我们需要一个获取 v1 所有权并返回拥有所有权的迭代器,则可以调用 into_iter 而不是 iter。类似的,如果我们希望迭代可变引用,则可以调用 iter_mut 而不是 iter。

  • 消费迭代器的方法

    调用 next 方法的方法被称为 消费适配器(consuming adaptors),因为调用它们会消耗迭代器。

      1. collect()collect() 方法用于消费迭代器并将其转换为一个集合类型,例如 VecHashMapString。它会遍历整个迭代器,并将每个元素收集到一个集合中。
      1. count()count() 方法用于消费迭代器并计算其中元素的数量。它返回一个 usize 类型的值,表示迭代器中元素的个数。
      1. nth()nth() 方法用于消费迭代器并获取其中的第 n 个元素。它返回一个 Option 类型的值,表示可能存在的第 n 个元素。
      1. last()last() 方法用于消费迭代器并获取其中的最后一个元素。它返回一个 Option 类型的值,表示可能存在的最后一个元素。
      1. max() / min()max()min() 方法分别用于消费迭代器并获取其中的最大值和最小值。它们返回一个 Option 类型的值,表示可能存在的最大或最小值。
      1. sum() / product()sum()product() 方法分别用于消费迭代器并计算其中元素的和或乘积。它们返回一个值,表示迭代器中所有元素的和或乘积。
      1. all() / any()all()any() 方法用于消费迭代器并检查其中的元素是否满足某种条件。all() 方法要求所有元素都满足条件,而 any() 方法只要求至少有一个元素满足条件。它们返回一个布尔值,表示是否所有或任意元素满足条件。
      1. find() / position()find()position() 方法用于消费迭代器并查找满足某种条件的元素。find() 方法返回第一个满足条件的元素,而 position() 方法返回满足条件的第一个元素的索引。它们都返回一个 Option 类型的值,表示可能存在的满足条件的元素或索引。
  • 产生其他迭代器的方法

    Iterator trait 中定义了另一类方法,被称为 迭代器适配器(iterator adaptors),它们允许我们将当前迭代器变为不同类型的迭代器。可以链式调用多个迭代器适配器。不过因为所有的迭代器都是惰性的,必须调用一个消费适配器方法以便获取迭代器适配器调用的结果。

    • map():map() 方法用于将迭代器中的每个元素应用一个函数,并将结果转换为另一种类型。它返回一个新的迭代器,其中包含了应用函数后的结果。

    • filter():filter() 方法用于过滤迭代器中的元素,只保留符合特定条件的元素。它接受一个闭包作为参数,该闭包返回一个布尔值,用于判断元素是否应该被保留。

    • take():take() 方法用于从迭代器中获取指定数量的元素,并返回一个新的迭代器。它接受一个usize类型的参数,表示要获取的元素数量。

    • skip():skip() 方法用于跳过迭代器中的前几个元素,并返回一个新的迭代器。它接受一个usize类型的参数,表示要跳过的元素数量。

    • enumerate():enumerate() 方法用于在迭代器中同时追踪元素的索引和值。它返回一个元组 (index, value) 的迭代器,其中 index 是索引,value 是元素的值。

    • zip():zip() 方法用于将两个迭代器的元素一一配对,并返回一个新的迭代器。它将两个迭代器的对应位置的元素组合成元组 (value1, value2)。

    • fold():fold() 方法用于将迭代器中的所有元素折叠(或归约)为一个单一的值。它接受一个初始值和一个闭包作为参数,该闭包将累积器和迭代器中的每个元素作为输入,并返回一个新的累积器。

    • flat_map():flat_map() 方法用于将每个元素转换为另一个迭代器,并将所有的结果展平成一个单一的迭代器。它的功能类似于 map(),但是它额外地将结果展平。

常见宏

  • panic!

  • todo!

  • unimplemented

智能指针

常用的智能指针

  • Box,用于在堆上分配值

    • 有单一所有者

    • 允许在编译时执行不可变或可变借用检查

  • Rc,一个引用计数类型,其数据可以有多个所有者

    • 允许相同数据有多个所有者

    • 仅允许在编译时执行不可变借用检查

  • Ref 和 RefMut,通过 RefCell 访问。( RefCell 是一个在运行时而不是在编译时执行借用规则的类型)。

    • 有单一所有者

    • 允许在运行时执行不可变或可变借用检查

  • Arc<T> 原子引用计数类型,用于多线程

  • Cow<T> Clone on Write

    写入时克隆的智能指针。

    Cow 类型是一种智能指针,具有写入时克隆的功能:它可以封装借用数据并提供不可变的访问权限,在需要突变或所有权时,可以懒惰地克隆数据。通过 “借用 “特性,该类型可与一般借用数据一起使用。

    Cow 实现了 Deref,这意味着你可以直接在它所包含的数据上调用非变异方法。如果需要进行变异,to_mut 将获取一个可变异的引用,并在必要时克隆该引用。

    如果需要引用计数指针,请注意 Rc::make_mut 和 Arc::make_mut 也可以提供写入时克隆功能。

智能指针的trait

  • Deref trait

    实现 Deref trait 允许我们重载 解引用运算符(dereference operator)*(不要与乘法运算符或通配符相混淆)。通过这种方式实现 Deref trait 的智能指针可以被当作常规引用来对待,可以编写操作引用的代码并用于智能指针。
    Deref trait,由标准库提供,要求实现名为 deref 的方法,其借用 self 并返回一个内部数据的引用

  • DerefMut trait

    用于重载可变引用的 * 运算符。

  • Drop trait

并发

Thread

  • 使用spawn创建新线程

    • 返回JoinHandle,JoinHandle 是一个拥有所有权的值,当对其调用 join 方法时,它会等待其线程结束。
  • 使用join阻塞线程

  • 使用move关键字来将闭包捕获的值的所有权传递给另一个线程

线程通信

  • 使用mpsc::channel信道进行通信

    • 发送者tx

      • send

        • 可以clone
    • 接收者rx

      • recv

      • try_recv

    • mpsc:mutiple producer single consumer

  • 共享内存

    • mutex并发原语

      • 使用lock获取锁

      • 使用Arc原子引用计数类型

内嵌的并发概念

  • Send trait

  • Sync

面向对象

四大特征

  • 封装

  • 抽象

  • 继承

    Rust中不存在继承
    选择继承有两个主要的原因。其一是代码复用:您可以为一种类型实现特定的行为,继承可将其复用到不同的类型上。在 Rust 代码中可以使用默认 trait 方法实现来进行有限的代码复用,就像示例 10-14 中在 Summary trait 上增加的 summarize 方法的默认实现。任何实现了 Summary trait 的类型都可以使用 summarize 方法而无须进一步实现。这类似于父类有一个方法的实现,继承的子类也拥有这个方法的实现。当实现 Summary trait 时也可以选择覆盖 summarize 的默认实现,这类似于子类覆盖从父类继承方法的实现。

  • 多态

    作为一种语言设计的解决方案,继承在许多新的编程语言中逐渐不被青睐,因为它经常有分享过多代码的风险。子类不应总是共享父类的所有特征,但是继承始终如此。它还引入了在子类上调用方法的可能性,这些方法可能没有意义,或因为方法不适用于子类而导致错误。此外,一些语言只允许单一继承(意味着子类只能从一个类继承),进一步限制了程序设计的灵活性。

trait对象

泛型

在函数中使用泛型

在结构体中使用泛型

在枚举中使用泛型

方法中使用泛型

const 泛型 针对值的泛型

默认泛型类型参数

  • 当使用泛型类型参数时,可以为其指定一个默认的具体类型,例如标准库中的 std::ops::Add 特征

  • 减少实现的样板代码

  • 扩展类型但是无需大幅修改现有的代码

Trait

定义

  • 定义了一组可以被共享的行为,只要实现了特征,你就能使用这组行为

  • 如果不同的类型具有相同的行为,那么我们就可以定义一个特征,然后为这些类型实现该特征。定义特征是把一些方法组合在一起,目的是定义一个实现某些目标所必需的行为的集合。

定义特征

  • 使用trait关键字

实现特征

默认实现

  • 可以在特征中定义具有默认实现的方法,这样其它类型无需再实现该方法,或者也可以选择重载该方法

使用特征作为函数参数

特征约束(trait bound)

Where 约束

函数返回中的 impl Trait

  • 可以通过 impl Trait 来说明一个函数返回了一个类型,该类型实现了某个特征

  • 只能有一个具体的类型

通过 derive 派生特征

  • 形如 #[derive(Debug)] 的代码已经出现了很多次,这种是一种特征派生语法,被 derive 标记的对象会自动实现对应的默认特征代码,继承相应的功能

Trait 对象

  • 特征对象指向实现了 特征的类型的实例,这种映射关系是存储在一张表中,可以在运行时通过特征对象找到具体调用的类型方法。

  • 动态分发

    • 当使用特征对象时,Rust 必须使用动态分发。编译器无法知晓所有可能用于特征对象代码的类型,所以它也不知道应该调用哪个类型的哪个方法实现。
  • 特征对象的限制

    • 不是所有特征都能拥有特征对象,只有对象安全的特征才行。当一个特征的所有方法都有如下属性时,它的对象才是安全的

      • 方法的返回类型不能是 Self

      • 方法没有任何泛型参数

关联类型

  • 关联类型是在特征定义的语句块中,申明一个自定义类型,这样就可以在特征的方法签名中使用该类型

生命周期

生命周期是引用的有效作用域

主要作用是避免悬垂指针

为了保证 Rust 的所有权和借用的正确性,Rust 使用了一个借用检查器(Borrow checker),来检查我们程序的借用正确性

在存在多个引用时,编译器有时会无法自动推导生命周期,此时就需要我们手动去标注,通过为参数标注合适的生命周期来帮助编译器进行借用检查的分析

unsafe

作用

  • 解引用裸指针

  • 调用一个 unsafe 或外部的函数

  • 访问或修改一个可变的静态变量

  • 实现一个 unsafe 特征

  • 访问 union 中的字段

raw pointer

  • 可以绕过 Rust 的借用规则,可以同时拥有一个数据的可变、不可变指针,甚至还能拥有多个可变的指针

  • 并不能保证指向合法的内存

  • 可以是 null

  • 没有实现任何自动的回收 (drop)

  • 创建裸指针是安全的行为,而解引用裸指针才是不安全的行为

  • 创建裸指针

    • 基于值的引用

    • 基于地址

    • 基于智能指针创建裸指针

调用 unsafe 函数或方法

用安全抽象包裹 unsafe 代码

使用 extern 函数调用外部代码

  • 原理:Foreign Function Interface (FFI)

访问或修改可变静态变量

  • 在Rust中 全局变量就是静态变量

  • 静态变量的引用的生命周期只能是’static

  • 有绝对地址

实现不安全 trait

访问联合体中的字段