20.1 不安全 Rust
20.1.1 不安全的超能力
20.1.2 解引用裸指针
- 裸指针(raw pointers):类似于引用类型;和引用一样,裸指针是不可变或可变的,分别写作
*const T
和*mut T
,这里的星号不是解引用运算符,它是类型名称的一部分 - 在裸指针的上下文中,不可变意味着指针解引用之后不能直接赋值
- 直接从保证安全的引用来创建他们(比如使用
as
来强转为某类型),可以知道这些特定的裸指针是有效,但是不能对任何裸指针做出如此假设! - 裸指针与引用和智能指针的区别,如下 4 点
- 允许忽略借用规则:可以同时拥有不可变和可变的指针(若通过可变指针修改数据,则可能潜在造成数据竞争!),或多个指向相同位置的可变指针
- 不保证指向有效的内存
- 允许为空
- 不能实现任何自动清理功能
20.1.3 调用不安全函数或方法(一):基本概念
20.1.4 调用不安全函数或方法(二):创建不安全代码的安全抽象
slice::from_raw_parts_mut
函数是不安全的因为它获取一个裸指针,并必须确信这个指针是有效的- 裸指针上的
add
方法也是不安全的,因为其必须确信此地址偏移量也是有效的指针 - 注意无需将
split_at_mut
函数的结果标记为unsafe
,并可以在安全 Rust 中调用此函数。我们创建了一个不安全代码的安全抽象,其代码以一种安全的方式使用了unsafe
代码
20.1.5 调用不安全函数或方法(三):使用 extern 函数调用外部代码
20.1.6 访问或修改可变静态变量
- 常量:
- 不可变静态变量:静态变量中的值有一个固定的内存地址(使用这个值总是会访问相同的地址);访问不可变静态变量是安全的
- 可变静态变量:使用
mut
关键字来指定可变性;访问和修改可变静态变量都是 不安全 的,必须位于unsafe
模块内
20.1.7 实现不安全 trait
20.1.8 访问联合体中的字段
20.1.9 何时使用不安全代码
20.2 高级 trait
20.2.1 关联类型:在 trait 定义中指定占位符类型
- **关联类型(associated types)**是一个将类型占位符与 trait 相关联的方式,这样 trait 的方法签名中就可以使用这些占位符类型
- 关联类型也会成为 trait 契约的一部分:trait 的实现必须提供一个类型来替代关联类型占位符
- 作用:用于类型占位,不用重复多次仅修改类型来实现同一个功能
20.2.2 默认泛型类型参数和运算符重载
- 默认参数类型主要应用:1、扩展类型而不破坏现有代码;2、在大部分用户都不需要的特定情况进行自定义
20.2.3 完全限定语法与消歧义:调用相同名称的方法
- 同名问题:Rust 既不能避免一个 trait 与另一个 trait 拥有相同名称的方法,也不能阻止为同一类型同时实现这两个 trait,甚至直接在类型上实现开始已经有的同名方法也是可能的
- 对于方法(有
self
参数):编译器默认调用直接实现在类型上的方法 - 对于关联函数(没有
self
参数):直接调用定义于实现在类型中的关联函数 - 完全限定语法:
<Type as Trait>::function(receiver_if_method, next_arg, ...);
,唯一地指定出哪个对象类型(type)所实现的哪个 trait 方法
20.2.4 父 trait 用于在另一个 trait 中使用某 trait 的功能
- 父(超)trait(supertrait):在实现 trait 时通过
子模块: 父模块
指定其依赖的上一级父 trait,即可获得父 trait 的方法。比如:OutlinePrint: fmt::Display
- 注意:需要同时满足子、父 trait 的实现要求!
20.2.5 newtype 模式用以在外部类型上实现外部 trait
20.3 高级类型
20.3.1 为了类型安全和抽象而使用 newtype 模式
20.3.2 类型别名用来创建类型同义词
- 类型别名(type alias):使用
type
关键字来给予现有类型另一个名字,主要用途是减少重复
20.3.3 从不返回的 never type
!
空类型 / 不返回类型:在函数从不返回的时候充当返回值,可以强转为任何其他类型
20.3.4 动态大小类型和 Sized trait
- Rust 中动态大小类型的常规用法:他们有一些额外的元信息来储存动态信息的大小
- 动态大小类型的黄金规则:必须将动态大小类型的值置于某种指针之后
Sized
trait 来决定一个类型的大小是否在编译时可知;这个 trait 自动为编译器在编译时就知道大小的类型实现;另外,Rust 隐式的为每一个泛型函数增加了 Sized bound
20.4 高级函数与闭包
20.4.1 函数指针
- 函数指针(function pointer):通过函数指针允许使用函数作为另一个函数的参数;函数满足类型
fn
(小写的 f),不要与闭包 trait 的Fn
(大写)相混淆 - 不同于闭包,
fn
是一个类型而不是一个 trait,所以直接指定fn
作为参数而不是声明一个带有Fn
作为 trait bound 的泛型参数 - 函数指针实现了所有三个闭包 trait(
Fn
、FnMut
和FnOnce
),所以总是可以在调用期望闭包的函数时传递函数指针作为参数
20.4.2 返回闭包
20.5 宏
20.5.1 宏和函数的区别
- 从根本上来说,宏是一种为写其他代码而写代码的方式,即所谓的 元编程(metaprogramming)
- 宏与函数的区别,如下 3 点
- 宏能够接收不同数量的参数,但函数签名必须声明函数参数个数和类型
- 在一个文件里调用宏 之前 必须定义它,或将其引入作用域;而函数则可以在任何地方定义和调用
- 宏定义通常要比函数定义更难阅读、理解以及维护
20.5.2 macro_rules! :声明宏用于通用元编程
20.5.3 用于从属性生成代码的过程宏
20.5.4 如何编写自定义 derive 宏
20.5.5 类属性宏
- 自定义派生宏:
derive
属性生成代码,derive
只能用于结构体和枚举 - 类属性宏:类属性宏与自定义派生宏相似,能创建新的属性,属性还可以用于其它的项(比如:函数),也更为灵活
20.5.6 类函数宏
- 类函数(Function-like)宏:定义看起来像函数调用的宏,类似于
macro_rules!
,它们比函数更灵活(例如,可以接受未知数量的参数)