github地址:https://github.com/bradyjoestar/rustnotes(欢迎star!)
pdf下载链接:https://github.com/bradyjoestar/rustnotes/blob/master/Rust%E8%AF%AD%E8%A8%80%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0.pdf
参考:
https://rustcc.gitbooks.io/rustprimer/content/ 《RustPrimer》
https://kaisery.github.io/trpl-zh-cn/ 《Rust程序设计语言-简体中文版》
6.1 函数式编程
6.1.1 闭包
闭包的定义有两种:
闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。
有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例。
一个简单实用闭包的例子:
pub fn closure_test1(){
println!("closure_test1");
let mut a = 200;
let plus_two = |mut x| {
let mut result: i32 = x;
result += 1;
result += 1;
x += 100;
println!("{}",x);
result
};
println!("{}",plus_two(a));
a = 100;
println!("{}",a);
}
计算结果:
closure_test1
300
202
100
rust闭包的简化写法:
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 ;
第一行展示了一个函数定义,而第二行展示了一个完整标注的闭包定义。第三行闭包定义中省略了类型注解,而第四行去掉了可选的大括号,因为闭包体只有一行。这些都是有效的闭包定义,并在调用时产生相同的行为。
之所以把它称为“闭包”是因为它们“包含在环境中”(close over their environment)。这看起来像:
let num = 5;
let plus_num = |x: i32| x + num;
assert_eq!(10, plus_num(5));
6.1.2 闭包捕获周围环境的方式
闭包可以通过三种方式捕获其环境,他们直接对应函数的三种获取参数的方式:获取所有权,可变借用和不可变借用。这三种捕获值的方式被编码为如下三个 Fn trait:
1.FnOnce:消费从周围作用域捕获的变量,闭包周围的作用域被称为其 环境,environment。为了消费捕获到的变量,闭包必须获取其所有权并在定义闭包时将其移动进闭包。其名称的 Once 部分代表了闭包不能多次获取相同变量的所有权的事实,所以它只能被调用一次。
2.FnMut:获取可变的借用值所以可以改变其环境
3.Fn:从其环境获取不可变的借用值
Fn获取&self,FnMut获取&mut self,而FnOnce获取self。这包含了所有3种通过通常函数调用语法的self。
pub fn trait_test_four_main() {
println!("trait_test_four");
let vec = vec![1, 2, 3, 4];
anonymous_fnonce();
anonymous_fnonce_callback();
anonymous_fnmut();
anonymous_fnmut_callback();
anonymous_fn();
anonymous_fn_callback();
}
// 匿名函数中的FnOnce/FnMut/Fn
// 首先 FnOnce/FnMut/Fn 这三个东西被称为 Trait,
// 默认情况下它们是交给rust编译器去推理的, 大致的推理原则是:
// FnOnce: 当指定这个Trait时, 匿名函数内访问的外部变量必须拥有所有权.
// FnMut: 当指定这个Trait时, 匿名函数可以改变外部变量的值.
// Fn: 当指定这个Trait时, 匿名函数只能读取(borrow value immutably)变量值.
// FnOnce inline way
// 以获取所有权的方式来获取其所在的环境的所有变量.
fn anonymous_fnonce() {
let fn_name = "anonymous_fnonce";
let mut b = String::from("hello");
// 通过使用 move 的方式, 把所有权转移进来, rust 编译器
// 会自动推理出这是一个 FnOnce Trait 匿名函数.
let pushed_data = move || {
// 由于所有权转移进来, 因此 b 已经被移除掉.
// 因此这个匿名函数不可能在被执行第二遍.
b.push_str(" world!");
b
};
println!("{}: {}", fn_name, pushed_data()); // 这里只能运行一次.
// println!("{}: {}", fn_name, pushed_data()); // 再次运行会报错.
}
// FnOnce callback way
fn anonymous_fnonce_callback() {
let fn_name = "anonymous_fnonce_callback";
fn calculate<T>(callback: T) -> i32
where
T: FnOnce() -> String,
{
let data = callback();
data.len() as i32
}
let x = " world!";
let mut y = String::from("hello");
let result = calculate(|| {
y.push_str(x);
y
});
println!("{}: {}", fn_name, result);
}
// FnMut inline way
// 以mutable的方式获取其所在的环境的所有变量.
fn anonymous_fnmut() {
let fn_name = "anonymous_fnmut";
let mut b = String::from("hello");
// rust 自动检测到 pushed_data 这个匿名函数要修改其外部的环境变量.
// 因此自动推理出 pushed_data 是一个 FnMut 匿名函数.
let pushed_data = || {
b.push_str(" world!");
// 由于rust的 mutable 原则是, 只允许一个mut引用, 因此 变量 b 不能
// 再被其他代码引用, 所以这里要返回更改后的结果.
b
};
let c = pushed_data();
println!("{}: {}", fn_name, c);
//error[E0382]: borrow of moved value: `b`
// --> src/trait_test_four.rs:77:42
// |
//62 | let mut b = String::from("hello");
// | ----- move occurs because `b` has type `std::string::String`, which does not implement the `Copy` trait
//...
//66 | let pushed_data = || {
// | -- value moved into closure here
//67 | b.push_str(" world!");
// | - variable moved due to use in closure
//...
//77 | println!("b is borrowed as inmut {}",b);
// | ^ value borrowed here after move
//compile error
//println!("b is borrowed as inmut {}",b);
}
// FnMut callback way.
fn anonymous_fnmut_callback() {
let fn_name = "anonymous_fnmut_callback";
fn calculate<T>(mut callback: T)
where
T: FnMut(),
{
callback()
}
let mut b = String::from("hello");
calculate(|| {
b.push_str(" world!");
});
println!("{}: {}", fn_name, b);
}
// Fn inline way
// 以immutable的方式获取其所在的环境的所有变量.
fn anonymous_fn() {
let fn_name = "anonymous_fn";
let mut a = String::from("hello");
let b = String::from(" world!");
let pushed_data = |x: &mut String| {
// b 再这里被引用, 但是最后还能被打印, 证明它是被immutable引用.
x.push_str(&*b);
println!("{}: {}", fn_name, x);
println!("b is borrowed as inmut {}",b);
};
pushed_data(&mut a);
println!("{}: {}", fn_name, b);
}
// Fn callback way
fn anonymous_fn_callback() {
let fn_name = "anonymous_fn_callback";
fn calculate<T>(callback: T)
where
T: Fn(),
{
callback();
}
let a = String::from("hello");
let b = String::from(" world!");
calculate(|| {
let joined = format!("{}{}", &*a, &*b);
println!("{}: {}", fn_name, joined)
})
}
上述例子中包含以callback为函数参数,实现功能。
6.1.3 函数指针
一个函数指针有点像一个没有环境的闭包。因此,你可以传递一个函数指针给任何函数除了作为闭包参数,下面的代码可以工作:
fn call_with_one(some_closure: &Fn(i32) -> i32) -> i32 {
some_closure(1)
}
fn add_one(i: i32) -> i32 {
i + 1
}
let f = add_one;
let answer = call_with_one(&f);
assert_eq!(2, answer);
在这个例子中,我们并不是严格的需要这个中间变量f,函数的名字就可以了:
let answer = call_with_one(&add_one);
此外闭包的用法还有返回闭包,返回闭包使用价值不大,反而会引起困惑。直接返回函数就可以了。
6.2 unsafe与原始指针
首先介绍原始指针(即裸指针)
6.2.1 裸指针
Rust中的裸指针和C++中的裸指针不同。
const T和mut T在Rust中被称为“裸指针”。它允许别名,允许用来写共享所有权的类型,甚至是内存安全的共享内存类型如:Rc<T>和Arc<T>,但是赋予你更多权利的同时意味着你需要担当更多的责任:
1.不能保证指向有效的内存,甚至不能保证是非空的
2.没有任何自动清除,所以需要手动管理资源
3.是普通旧式类型,也就是说,它不移动所有权,因此Rust编译器不能保证不出像释放后使用这种bug
4.缺少任何形式的生命周期,不像&,因此编译器不能判断出悬垂指针
5.除了不允许直接通过*const T改变外,没有别名或可变性的保障
从上面可以看到,使用裸指针基本和rust的静态类型检查告别。
几个比较好的例子可以说明用法。
let a = 1;
let b = &a as *const i32;
let mut x = 2;
let y = &mut x as *mut i32;
例子二:
let a = 1;
let b = &a as *const i32;
let c = unsafe { *b };
println!("{}", c);
例子三:
let a: Box<i32> = Box::new(10);
// 我们需要先解引用a,再隐式把 & 转换成 *
let b: *const i32 = &*a;
// 使用 into_raw 方法
let c: *const i32 = Box::into_raw(a);
例子四:
struct Student {
name: i32,
}
fn main() {
{
let mut x = 5;
let raw = &mut x as *mut i32;
let mut points_at = unsafe { *raw };
points_at = 12;
x = 1234;
println!("{}", x);
println!("raw points at {}", points_at);
}
{
let a: Box<Vec<i32>> = Box::new(vec![1, 2, 3, 4]);
// 我们需要先解引用a,再隐式把 & 转换成 *
let b: *const Vec<i32> = &*a;
// 使用 into_raw 方法
let c: *const Vec<i32> = Box::into_raw(a);
}
{
let mut x = 2;
let y = &mut x as *mut i32;
let mut c = unsafe { *y };
c = 100;
//copy
println!("{}", c);
println!("{}", x);
//output
//100
//2
}
//一般理解,*v 操作,是 &v 的反向操作,
//即试图由资源的引用获取到资源的拷贝(如果资源类型实现了 Copy),或所有权(资源类型没有实现 Copy)。
{
let mut x = String::from("hello");
let y = &mut x as *mut String;
let mut c = unsafe { &mut *y };
c.push('c');
println!("{}", c);
println!("{}", x);
let z = &mut x as *mut String;
let mut d = unsafe { &mut *z };
d.push('d');
println!("{}", c);
println!("{}", d);
println!("{}", x);
//get the ownership
//helloc
//helloc
//hellocd
//hellocd
//hellocd
}
}
6.2.2 unsafe
Rust的内存安全依赖于强大的类型系统和编译时检测,不过它并不能适应所有的场景。 首先,所有的编程语言都需要跟外部的“不安全”接口打交道,调用外部库等,在“安全”的Rust下是无法实现的; 其次,“安全”的Rust无法高效表示复杂的数据结构,特别是数据结构内部有各种指针互相引用的时候;再次, 事实上还存在着一些操作,这些操作是安全的,但不能通过编译器的验证。
因此在安全的Rust背后,还需要unsafe的支持。
unsafe块能允许程序员做的额外事情有:
1.解引用一个裸指针const T和mut T
let x = 5;
let raw = &x as *const i32;
let points_at = unsafe { *raw };
println!("raw points at {}", points_at);
2.读写一个可变的静态变量static mut
static mut N: i32 = 5;
unsafe {
N += 1;
println!("N: {}", N);
}
3.调用一个不安全函数(FFI)
unsafe fn foo() {
//实现
}
fn main() {
unsafe {
foo();
}
}
6.2.3 Safe!= no bug
对于Rust来说禁止你做任何不安全的事是它的本职,不过有些是编写代码时的bug,它们并不属于“内存安全”的范畴:
· 死锁
· 内存或其他资源溢出
· 退出未调用析构函数
· 整型溢出
使用unsafe时需要注意一些特殊情形:
· 数据竞争
· 解引用空裸指针和悬垂裸指针
· 读取未初始化的内存
· 使用裸指针打破指针重叠规则
· &mut T和&T遵循LLVM范围的noalias模型,除了如果&T包含一个UnsafeCell<U>的话。不安全代码必须不能违反这些重叠(aliasing)保证
· 不使用UnsafeCell<U>改变一个不可变值/引用
· 通过编译器固有功能调用未定义行为:
o 使用std::ptr::offset(offset功能)来索引超过对象边界的值,除了允许的末位超出一个字节
o 在重叠(overlapping)缓冲区上使用std::ptr::copy_nonoverlapping_memory(memcpy32/memcpy64功能)
· 原生类型的无效值,即使是在私有字段/本地变量中:
o 空/悬垂引用或装箱
o bool中一个不是false(0)或true(1)的值
o enum中一个并不包含在类型定义中判别式
o char中一个代理字(surrogate)或超过char::MAX的值
o str中非UTF-8字节序列
· 在外部代码中使用Rust或在Rust中使用外部语言。
6.3 FFI(Foreign Function Interface)
FFI(Foreign Function Interface)是用来与其它语言交互的接口,通常有两种类型:调用其他语言和供其他语言调用。
在rust中一般常见的主要是和C打交道。调用C语言写的代码和编程库供C调用。
使用rust调用C语言相对于使用于C语言调用Rust稍微复杂一些。
这和cgo正好相反。很大部分原因在于rust没有运行时。
github上有一个非常好的例子:
https://github.com/alexcrichton/rust-ffi-examples
有简单的rust和各种语言交互的例子。
6.3.1 rust调用ffi函数
rust调用ffi稍微复杂一些。主要原因在于rust编程意义上的数据结构和传递给c语言的数据结构是不能通用的。
因此需要引入和C通信的crate:libc。
由于cffi的数据类型与rust不完全相同,我们需要引入libc库来表达对应ffi函数中的类型。
在Cargo.toml中添加以下行:
[dependencies]
libc = "0.2.9"
主要有以下几步:
6.3.1.1 声明你的ffi函数
就像c语言需要#include声明了对应函数的头文件一样,rust中调用ffi也需要对对应函数进行声明。
use libc::c_int;
use libc::c_void;
use libc::size_t;
[link(name = "yourlib")]
extern {
fn your_func(arg1: c_int, arg2: *mut c_void) -> size_t; // 声明ffi函数
fn your_func2(arg1: c_int, arg2: *mut c_void) -> size_t;
static ffi_global: c_int; // 声明ffi全局变量
}
声明一个ffi库需要一个标记有#[link(name = "yourlib")]的extern块。name为对应的库(so/dll/dylib/a)的名字。 如:如果你需要snappy库(libsnappy.so/libsnappy.dll/libsnappy.dylib/libsnappy.a), 则对应的name为snappy。 在一个extern块中你可以声明任意多的函数和变量。
在有的程序中#[link(name = "yourlib")]不是必须的,只需保证可以最终link上即可。
6.3.1.2 调用ffi函数
声明完成后就可以进行调用了。 由于此函数来自外部的c库,所以rust并不能保证该函数的安全性。因此,调用任何一个ffi函数需要一个unsafe块。
let result: size_t = unsafe {
your_func(1 as c_int, Box::into_raw(Box::new(3)) as *mut c_void)
};
6.3.1.3 封装unsafe,暴露安全接口
作为一个库作者,对外暴露不安全接口是一种非常不合格的做法。在做c库的rust binding时,我们做的最多的将是将不安全的c接口封装成一个安全接口。 通常做法是:在一个叫ffi.rs之类的文件中写上所有的extern块用以声明ffi函数。在一个叫wrapper.rs之类的文件中进行包装:
// ffi.rs
#[link(name = "yourlib")]
extern {
fn your_func(arg1: c_int, arg2: *mut c_void) -> size_t;
}
与
// wrapper.rs
fn your_func_wrapper(arg1: i32, arg2: &mut i32) -> isize {
unsafe { your_func(1 as c_int, Box::into_raw(Box::new(3)) as *mut c_void) } as isize
}
对外暴露(pub use) your_func_wrapper函数即可。
6.3.1.4 数据结构等映射
libc为我们提供了很多原始数据类型,比如c_int, c_float等,但是对于自定义类型,如结构体,则需要我们自行定义。
数据结构是传递给C语言使用,并从C语言获取结果返回。
细节比较多,具体去查使用例子。这块相对而言还是比较复杂的。
6.3.1.5 静态库/动态库
前面提到了声明一个外部库的方式--#[link]标记,此标记默认为动态库。但如果是静态库,可以使用#[link(name = "foo", kind = "static")]来标记。 此外,对于osx的一种特殊库--framework, 还可以这样标记#[link(name = "CoreFoundation", kind = "framework")].
6.3.1.6 bind-gen
是不是觉得把一个个函数和全局变量在extern块中去声明,对应的数据结构去手动创建特别麻烦?没关系,rust-bindgen来帮你搞定。 rust-bindgen是一个能从对应c头文件自动生成函数声明和数据结构的工具。创建一个绑定只需要./bindgen [options] input.h即可。
例子:
typedef struct Doggo {
int many;
char wow;
} Doggo;
void eleven_out_of_ten_majestic_af(Doggo* pupper);
转换后的结果:
/* automatically generated by rust-bindgen */
#[repr(C)]
pub struct Doggo {
pub many: ::std::os::raw::c_int,
pub wow: ::std::os::raw::c_char,
}
extern "C" {
pub fn eleven_out_of_ten_majestic_af(pupper: *mut Doggo);
}
6.3.1.7 一个简单的例子
build.rs(通过cc生成相应库文件)
extern crate cc;
fn main() {
cc::Build::new()
.file("src/double.c")
.compile("libdouble.a");
}
Cargo.toml
[package]
name = "rust-to-c"
version = "0.1.0"
authors = ["Alex Crichton <alex@alexcrichton.com>"]
build = "build.rs"
[dependencies]
libc = "0.2"
[build-dependencies]
cc = "1.0"
double.c
int double_input(int input) {
return input * 2;
}
main.rs
extern crate libc;
extern {
fn double_input(input: libc::c_int) -> libc::c_int;
}
fn main() {
let input = 4;
let output = unsafe { double_input(input) };
println!("{} * 2 = {}", input, output);
}
6.3.2 将rust编译成库
这一小节主要说明如何把rust编译成库让别的语言通过cffi调用。
6.3.2.1 调用约定和Mangle
为了能让rust的函数通过ffi被调用,需要加上extern "C"对函数进行修饰。
但由于rust支持重载,所以函数名会被编译器进行混淆,就像c++一样。因此当你的函数被编译完毕后,函数名会带上一串表明函数签名的字符串。
比如:fn test() {}会变成_ZN4test20hf06ae59e934e5641haaE. 这样的函数名为ffi调用带来了困难,因此,rust提供了#[no_mangle]属性为函数修饰。 对于带有#[no_mangle]属性的函数,rust编译器不会为它进行函数名混淆。如:
#[no_mangle]
extern "C" fn test() {}
在nm中观察到为
...
00000000001a7820 T test
...
至此,test函数将能够被正常的由cffi调用。
6.3.2.2 指定Crate类型
rustc默认编译产生rust自用的rlib格式库,要让rustc产生动态链接库或者静态链接库,需要显式指定。
方法1: 在文件中指定。 在文件头加上#![crate_type = "foo"], 其中foo的可选类型有bin, lib, rlib, dylib, staticlib.分别对应可执行文件, 默认(将由rustc自己决定), rlib格式,动态链接库,静态链接库。
方法2: 编译时给rustc 传--crate-type参数。参数内容同上。
方法3: 使用cargo,指定crate-type = ["foo"], foo可选类型同1。
6.3.2.3 反射的使用
由于在跨越ffi过程中,rust类型信息会丢失,比如当用rust提供一个OpaqueStruct给别的语言时:
use std::mem::transmute;
#[derive(Debug)]
struct Foo<T> {
t: T
}
#[no_mangle]
extern "C" fn new_foo_vec() -> *const c_void {
Box::into_raw(Box::new(Foo {t: vec![1,2,3]})) as *const c_void
}
#[no_mangle]
extern "C" fn new_foo_int() -> *const c_void {
Box::into_raw(Box::new(Foo {t: 1})) as *const c_void
}
fn push_foo_element(t: &mut Foo<Vec<i32>>) {
t.t.push(1);
}
#[no_mangle]
extern "C" fn push_foo_element_c(foo: *mut c_void){
let foo2 = unsafe {
&mut *(foo as *mut Foo<Vec<i32>>) // 这么确定是Foo<Vec<i32>>? 万一foo是Foo<i32>怎么办?
};
push_foo_element(foo3);
}
以上代码中完全不知道foo是一个什么东西。安全也无从说起了,只能靠文档。 因此在ffi调用时往往会丧失掉rust类型系统带来的方便和安全。在这里提供一个小技巧:使用Box<Box<Any>>来包装你的类型。
rust的Any类型为rust带来了运行时反射的能力,使用Any跨越ffi边界将极大提高程序安全性。
use std::any::Any;
#[derive(Debug)]
struct Foo<T> {
t: T
}
#[no_mangle]
extern "C" fn new_foo_vec() -> *const c_void {
Box::into_raw(Box::new(Box::new(Foo {t: vec![1,2,3]}) as Box<Any>)) as *const c_void
}
#[no_mangle]
extern "C" fn new_foo_int() -> *const c_void {
Box::into_raw(Box::new(Box::new(Foo {t: 1}) as Box<Any>)) as *const c_void
}
fn push_foo_element(t: &mut Foo<Vec<i32>>) {
t.t.push(1);
}
#[no_mangle]
extern "C" fn push_foo_element_c(foo: *mut c_void){
let foo2 = unsafe {
&mut *(foo as *mut Box<Any>)
};
let foo3: Option<&mut Foo<Vec<i32>>> = foo2.downcast_mut(); // 如果foo2不是*const Box<Foo<Vec<i32>>>, 则foo3将会是None
if let Some(value) = foo3 {
push_foo_element(value);
}
}
上面的例子中可以接受调用方的*mut c_void类型,然后进行运行时反射,对输入数据进行处理。
6.3.2.4一个简单的例子
链接地址:
https://github.com/alexcrichton/rust-ffi-examples/tree/master/c-to-rust
Makefile
ifeq ($(shell uname),Darwin)
LDFLAGS := -Wl,-dead_strip
else
LDFLAGS := -Wl,--gc-sections -lpthread -ldl
endif
all: target/double
target/double
target:
mkdir -p $@
target/double: target/main.o target/debug/libdouble_input.a
$(CC) -o $@ $^ $(LDFLAGS)
target/debug/libdouble_input.a: src/lib.rs Cargo.toml
cargo build
target/main.o: src/main.c | target
$(CC) -o $@ -c $<
clean:
rm -rf target
Cargo.toml
[package]
name = "c-to-rust"
version = "0.1.0"
authors = ["Alex Crichton <alex@alexcrichton.com>"]
[lib]
name = "double_input"
crate-type = ["staticlib"]
lib.rs
#![crate_type = "staticlib"]
#[no_mangle]
pub extern fn double_input(input: i32) -> i32 {
input * 2
}
main.c
#include <stdint.h>
#include <stdio.h>
extern int32_t double_input(int32_t input);
int main() {
int input = 4;
int output = double_input(input);
printf("%d * 2 = %d\n", input, output);
return 0;
}
Makefile 中会去链接rust生成的lib,看起来这么简单的原因是rust内部提供的就是一个运算口子,捕获C传递的参数,执行相应的函数功能,并返回相应的c可以理解的结果。这些功能都内置了。开发上大大节省时间。
6.4 堆,栈,BOX
6.4.1 堆和栈
一般而言,在编译期间不能确定大小的数据类型都需要使用堆上内存,因为编译器无法在栈上分配 编译期未知大小 的内存,所以诸如 String, Vec 这些类型的内存其实是被分配在堆上的。换句话说,我们可以很轻松的将一个 Vec move 出作用域而不必担心消耗,因为数据实际上不会被复制。
堆和栈区别:
栈内存从高位地址向下增长,且栈内存分配是连续的,一般操作系统对栈内存大小是有限制的,Linux/Unix 类系统上面可以通过 ulimit 设置最大栈空间大小,所以 C 语言中无法创建任意长度的数组。在Rust里,函数调用时会创建一个临时栈空间,调用结束后 Rust 会让这个栈空间里的对象自动进入 Drop 流程,最后栈顶指针自动移动到上一个调用栈顶,无需程序员手动干预,因而栈内存申请和释放是非常高效的。
相对地,堆上内存则是从低位地址向上增长,堆内存通常只受物理内存限制,而且通常是不连续的,一般由程序员手动申请和释放的,如果想申请一块连续内存,则操作系统需要在堆中查找一块未使用的满足大小的连续内存空间,故其效率比栈要低很多,尤其是堆上如果有大量不连续内存时。另外内存使用完也必须由程序员手动释放,不然就会出现内存泄漏,内存泄漏对需要长时间运行的程序(例如守护进程)影响非常大。
rust资源的管理比较复杂,资源和标志符要分开来看。
目前一个只包含i32的结构体可能放在栈上,一个包含字符串的结构体放在那里?不太确定,可以确定的是字符串肯定放在堆上,栈上数据不支持动态扩容。
6.4.2 BOX
Rust可以强制把某些数据放到堆上。例如:
fn main() {
let b = Box::new(5);
println!("b = {}", b);
}
此外,除了数据被储存在堆上而不是栈上之外,box 没有性能损失。不过也没有很多额外的功能。它们多用于如下场景:
• 当有一个在编译时未知大小的类型,而又想要在需要确切大小的上下文中使用这个类型值的时候
• 当有大量数据并希望在确保数据不被拷贝的情况下转移所有权的时候
• 当希望拥有一个值并只关心它的类型是否实现了特定 trait 而不是其具体类型的时候(Box<Trait>,后来有了impl trait进行替换)
6.4.2.1 box允许创建递归类型
#[derive(Debug)]
enum List {
Cons(i32, Box<List>),
Nil,
}
use List::{Cons, Nil};
struct BigStruct {
one: i32,
two: i32,
// etc
one_hundred: i32,
}
fn foo(x: Box<BigStruct>) -> BigStruct {
*x
}
pub fn box_test_1() {
println!("box test");
let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil))))));
println!("{:?}", list);
let bs = BigStruct {
one: 1,
two: 2,
one_hundred: 100,
};
let mut x = Box::new(bs);
println!("{}", x.one_hundred);
println!("{}", &x.one);
x.one_hundred = 200;
println!("{}", &x.one_hundred);
let m = &x;
let z = &mut x;
z.one_hundred = 500;
//error[E0502]: cannot borrow `x` as mutable because it is also borrowed as immutable
// --> src/box_test.rs:41:13
// |
//39 | let m = &x;
// | -- immutable borrow occurs here
//40 |
//41 | let z = &mut x;
// | ^^^^^^ mutable borrow occurs here
//...
//44 | println!("{}",m.one_hundred);
// | ------------- immutable borrow later used here
//compile error
//println!("{}",m.one_hundred);
let y = foo(x);
println!("{}", y.one);
println!("{}", y.one_hundred);
}
上面例子包含了递归类型list。如果按照
enum List {
Cons(i32, List),
Nil,
}
去定义,则会报如下错误:
error[E0072]: recursive type `List` has infinite size
--> src/main.rs:1:1
|
1 | enum List {
| ^^^^^^^^^ recursive type has infinite size
2 | Cons(i32, List),
| ----- recursive without indirection
|
= help: insert indirection (e.g., a `Box`, `Rc`, or `&`) at some point to
make `List` representable
这个错误表明这个类型 “有无限的大小”。其原因是 List 的一个成员被定义为是递归的:它直接存放了另一个相同类型的值。这意味着 Rust 无法计算为了存放 List 值到底需要多少空间。让我们一点一点来看:首先了解一下 Rust 如何决定需要多少空间来存放一个非递归类型。
box指针和下面例子的b有点类似。
pub fn rust_ptr_test() {
println!("rust_ptr_test");
let mut a = vec![1, 2, 3, 4];
println!("{:?}", a);
let mut b = &mut a;
println!("{:?}", b);
let c = &b;
println!("{:?}", c);
let d = &mut b;
println!("{:?}", d);
d.push(100);
println!("{:?}", d);
//error[E0502]: cannot borrow `b` as mutable because it is also borrowed as immutable
// --> src/rust_ptr_test.rs:15:13
// |
//12 | let c = &b;
// | -- immutable borrow occurs here
//...
//15 | let d = &mut b;
// | ^^^^^^ mutable borrow occurs here
//...
//21 | println!("{:?}",c);
// | - immutable borrow later used here
//compile error
//println!("{:?}",c);
}
6.5 智能指针
6.5.1 Rc与Arc
6.5.1.1 Rc与Rc Weak
Rc 用于同一线程内部,通过 use std::rc::Rc 来引入。它有以下几个特点:
1.用 Rc 包装起来的类型对象,是 immutable 的,即 不可变的。即你无法修改 Rc<T> 中的 T 对象,只能读(除非使用RefCell。);
2.一旦最后一个拥有者消失,则资源会被自动回收,这个生命周期是在编译期就确定下来的;
3.Rc 只能用于同一线程内部,不能用于线程之间的对象共享(不能跨线程传递);
4.Rc 实际上是一个指针,它不影响包裹对象的方法调用形式(即不存在先解开包裹再调用值这一说)。和Box有点类似。
使用例子:
use std::rc::Rc;
let five = Rc::new(5);
let five2 = five.clone();
let five3 = five.clone();
Weak 通过 use std::rc::Weak 来引入。
Rc 是一个引用计数指针,而 Weak 是一个指针,但不增加引用计数,是 Rc 的 weak 版。它有以下几个特点:
1.可访问,但不拥有。不增加引用计数,因此,不会对资源回收管理造成影响;
2.可由 Rc<T> 调用 downgrade 方法而转换成 Weak<T>;
3.Weak<T> 可以使用 upgrade 方法转换成 Option<Rc<T>>,如果资源已经被释放,则 Option 值为 None;
4.常用于解决循环引用的问题。
在一个线程中,Rc和RcWeak可以同时存在,例如:
{
let six = Rc::new(vec![1,2,3,4]);
let six2 = six.clone();
let six3 = six.clone();
let six3 = Rc::downgrade(&six3);
//output
//[1, 2, 3, 4]
//(Weak)
//six3 is (Weak)
println!("{:?}",six);
println!("{:?}",six3);
drop(six);
drop(six2);
//no compile error
println!("six3 is {:?}",six3);
}
可以看到,即使six和six2释放,six3存在,编译时没有仍然没有报错。并且打印结果是six3 is (Weak)。
6.5.1.2 Arc与Arc Weak
首先引出Arc前先罗列一个例子。搭配mutex进行使用,对共享内存中的内容进行修改。
use std::sync::{Mutex, Arc};
use std::thread;
fn main() {
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];
for (i,j) in (0..20).enumerate() {
let counter = Arc::clone(&counter);
let handle = thread::spawn(move || {
let mut num = counter.lock().unwrap();
println!("index is {}",i);
*num += 1;
println!("index is {} ended",i)
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("Result: {}", *counter.lock().unwrap());
}
输出结果:
index is 0
index is 0 ended
index is 1
index is 1 ended
index is 2
index is 2 ended
index is 3
index is 3 ended
index is 5
index is 5 ended
index is 4
index is 4 ended
index is 6
index is 6 ended
index is 8
index is 8 ended
index is 9
index is 9 ended
index is 10
index is 10 ended
index is 12
index is 12 ended
index is 13
index is 13 ended
index is 11
index is 11 ended
index is 14
index is 14 ended
index is 7
index is 7 ended
index is 16
index is 16 ended
index is 17
index is 17 ended
index is 19
index is 19 ended
index is 18
index is 18 ended
index is 15
index is 15 ended
Result: 20
Arc
Arc 是原子引用计数,是 Rc 的多线程版本。Arc 通过 std::sync::Arc 引入。
它的特点:
1.Arc 可跨线程传递,用于跨线程共享一个对象;
2.用 Arc 包裹起来的类型对象,对可变性没有要求,可以搭配mutex或者RWLock进行修改;
3.一旦最后一个拥有者消失,则资源会被自动回收,这个生命周期是在编译期就确定下来的(如果搭配了锁的使用怎么确定?引入mutex可能会造成死锁。);
4.Arc 实际上是一个指针,它不影响包裹对象的方法调用形式(即不存在先解开包裹再调用值这一说);
Arc 对于多线程的共享状态几乎是必须的(减少复制,提高性能)
另外一个例子,多线程读:
use std::sync::Arc;
use std::thread;
fn main() {
let numbers: Vec<_> = (0..100u32).collect();
let shared_numbers = Arc::new(numbers);
for _ in 0..10 {
let child_numbers = shared_numbers.clone();
thread::spawn(move || {
let local_numbers = &child_numbers[..];
// Work with the local numbers
});
}
}
Arc Weak
与 Rc 类似,Arc 也有一个对应的 Weak 类型,从 std::sync::Weak 引入。
意义与用法与 Rc Weak 基本一致,不同的点是这是多线程的版本。故不再赘述。
给出一个例子,单线程中如何多个对象同时引用另外一个对象:
use std::rc::Rc;
struct Owner {
name: String
}
struct Gadget {
id: i32,
owner: Rc<Owner>
}
fn main() {
// Create a reference counted Owner.
let gadget_owner : Rc<Owner> = Rc::new(
Owner { name: String::from("Gadget Man") }
);
// Create Gadgets belonging to gadget_owner. To increment the reference
// count we clone the `Rc<T>` object.
let gadget1 = Gadget { id: 1, owner: gadget_owner.clone() };
let gadget2 = Gadget { id: 2, owner: gadget_owner.clone() };
drop(gadget_owner);
// Despite dropping gadget_owner, we're still able to print out the name
// of the Owner of the Gadgets. This is because we've only dropped the
// reference count object, not the Owner it wraps. As long as there are
// other `Rc<T>` objects pointing at the same Owner, it will remain
// allocated. Notice that the `Rc<T>` wrapper around Gadget.owner gets
// automatically dereferenced for us.
println!("Gadget {} owned by {}", gadget1.id, gadget1.owner.name);
println!("Gadget {} owned by {}", gadget2.id, gadget2.owner.name);
// At the end of the method, gadget1 and gadget2 get destroyed, and with
// them the last counted references to our Owner. Gadget Man now gets
// destroyed as well.
}
6.5.2 Mutex与RwLock
6.5.2.1 Mutex
Mutex 意为互斥对象,用来保护共享数据。Mutex 有下面几个特征:
1.Mutex 会等待获取锁令牌(token),在等待过程中,会阻塞线程。直到锁令牌得到。同时只有一个线程的 Mutex 对象获取到锁;
2.Mutex 通过 .lock() 或 .try_lock() 来尝试得到锁令牌,被保护的对象,必须通过这两个方法返回的 RAII 守卫来调用,不能直接操作;
3.当 RAII 守卫作用域(MutexGuard)结束后,锁会自动解开;
4.在多线程中,Mutex 一般和 Arc 配合使用。
例子如下:
use std::sync::mpsc::channel;
use std::sync::{Arc, Mutex, MutexGuard};
use std::thread;
const N: usize = 10;
pub fn main() {
// Spawn a few threads to increment a shared variable (non-atomically), and
// let the main thread know once all increments are done.
//
// Here we're using an Arc to share memory among threads, and the data inside
// the Arc is protected with a mutex.
let data = Arc::new(Mutex::new(0));
let (tx, rx) = channel();
for _ in 0..10 {
let (data, tx) = (data.clone(), tx.clone());
thread::spawn(move || {
// The shared state can only be accessed once the lock is held.
// Our non-atomic increment is safe because we're the only thread
// which can access the shared state when the lock is held.
//
// We unwrap() the return value to assert that we are not expecting
// threads to ever fail while holding the lock.
let mut data: MutexGuard<usize> = data.lock().unwrap();
*data += 1;
if *data == N {
tx.send(*data).unwrap();
}
println!("{}", *data);
// the lock is unlocked here when `data` goes out of scope.
});
}
let result = rx.recv().unwrap();
println!("result is {:?}", result);
}
mutex的lock 与 try_lock 的区别
.lock() 方法,会等待锁令牌,等待的时候,会阻塞当前线程。而 .try_lock() 方法,只是做一次尝试操作,不会阻塞当前线程。
当 .try_lock() 没有获取到锁令牌时会返回 Err。因此,如果要使用 .try_lock(),需要对返回值做仔细处理(比如,在一个循环检查中)。
6.5.2.2 RwLock
RwLock 翻译成 读写锁。它的特点是:
1.同时允许多个读,最多只能有一个写;
2.读和写不能同时存在;
例子如下:
use std::sync::mpsc::channel;
use std::sync::{Arc, Mutex, MutexGuard, RwLock};
use std::thread;
const N: usize = 10;
pub fn main() {
// Spawn a few threads to increment a shared variable (non-atomically), and
// let the main thread know once all increments are done.
//
// Here we're using an Arc to share memory among threads, and the data inside
// the Arc is protected with a mutex.
let data = Arc::new(RwLock::new(0));
let (tx, rx) = channel();
for _ in 0..10 {
let (data, tx) = (data.clone(), tx.clone());
thread::spawn(move || {
// The shared state can only be accessed once the lock is held.
// Our non-atomic increment is safe because we're the only thread
// which can access the shared state when the lock is held.
//
// We unwrap() the return value to assert that we are not expecting
// threads to ever fail while holding the lock.
let mut data= data.write().unwrap();
*data += 1;
if *data == N {
tx.send(*data).unwrap();
}
println!("{}", *data);
// the lock is unlocked here when `data` goes out of scope.
});
}
let result = rx.recv().unwrap();
println!("result is {:?}", result);
}
所以,如果我们要对一个数据进行安全的多线程使用,最通用的做法就是使用 Arc<Mutex<T>> 或者 Arc<RwLock<T>>进行封装使用。
6.5.3 Cell与RefCell
Rust 通过其所有权机制,严格控制拥有和借用关系,来保证程序的安全,并且这种安全是在编译期可计算、可预测的。但是这种严格的控制,有时也会带来灵活性的丧失,有的场景下甚至还满足不了需求。
因此,Rust 标准库中,设计了这样一个系统的组件:Cell, RefCell,它们弥补了 Rust 所有权机制在灵活性上和某些场景下的不足。同时,又没有打破 Rust 的核心设计。它们的出现,使得 Rust 革命性的语言理论设计更加完整,更加实用。
具体是因为,它们提供了 内部可变性(相对于标准的 继承可变性 来讲的)。
通常,我们要修改一个对象,必须
1.成为它的拥有者,并且声明 mut;
2.或 以 &mut 的形式,借用;
而通过 Cell, RefCell,我们可以在需要的时候,就可以修改里面的对象。而不受编译期静态借用规则束缚。
6.5.3.1 Cell
Cell 有如下特点:
1.Cell<T> 只能用于 T 实现了 Copy 的情况;
use std::cell::Cell;
let c = Cell::new(5);
let five = c.get();
和
use std::cell::Cell;
let c = Cell::new(5);
c.set(10);
6.5.3.2 RefCell
相对于 Cell 只能包裹实现了 Copy 的类型,RefCell 用于更普遍的情况(其它情况都用 RefCell)。
相对于标准情况的 静态借用,RefCell 实现了 运行时借用,这个借用是临时的。这意味着,编译器对 RefCell 中的内容,不会做静态借用检查,也意味着,出了什么问题,用户自己负责。
可能会编译通过而运行时panic掉。
RefCell 的特点:
1.在不确定一个对象是否实现了 Copy 时,直接选 RefCell;
2.如果被包裹对象,同时被可变借用了两次,则会导致线程崩溃。所以需要用户自行判断;
3.RefCell 只能用于线程内部,不能跨线程;
4.RefCell 常常与 Rc 配合使用(都是单线程内部使用),搭配Rc又有些不同,Rc本身就是获取同一资源同时有多个所有权拥有者;
例子:
use std::collections::HashMap;
use std::cell::RefCell;
use std::rc::Rc;
use std::thread;
fn main() {
{
let shared_map: Rc<RefCell<_>> = Rc::new(RefCell::new(HashMap::new()));
let shared_map2 = shared_map.clone();
shared_map.borrow_mut().insert("africa", 92388);
shared_map.borrow_mut().insert("kyoto", 11837);
shared_map2.borrow_mut().insert("amy",23456);
shared_map.borrow_mut().insert("piccadilly", 11826);
shared_map.borrow_mut().insert("marbles", 38);
println!("{}",shared_map.borrow().get("amy").unwrap());
//output:23456
}
{
let shared_map: Rc<RefCell<_>> = Rc::new(RefCell::new(HashMap::new()));
shared_map.borrow_mut().insert("africa", 92388);
shared_map.borrow_mut().insert("kyoto", 11837);
println!("{}",shared_map.borrow().get("kyoto").unwrap());
shared_map.borrow_mut().insert("piccadilly", 11826);
shared_map.borrow_mut().insert("marbles", 38);
//output:11837
}
{
let result = thread::spawn(move || {
let c = RefCell::new(5);
let m = c.borrow_mut();
let b = c.borrow(); // this causes a panic
}).join();
//runtime error
//thread '<unnamed>' panicked at 'already mutably borrowed: BorrowError', src/libcore/result.rs:997:5
}
}
.borrow()
不可变借用被包裹值。同时可存在多个不可变借用。
下面例子会崩溃:
use std::cell::RefCell;
use std::thread;
let result = thread::spawn(move || {
let c = RefCell::new(5);
let m = c.borrow_mut();
let b = c.borrow(); // this causes a panic
}).join();
assert!(result.is_err());
.borrow_mut()
可变借用被包裹值。同时只能有一个可变借用。
下面例子会崩溃:
use std::cell::RefCell;
use std::thread;
let result = thread::spawn(move || {
let c = RefCell::new(5);
let m = c.borrow();
let b = c.borrow_mut(); // this causes a panic
}).join();
assert!(result.is_err());
.into_inner()
取出包裹值。
use std::cell::RefCell;
let c = RefCell::new(5);
let five = c.into_inner();
6.5.4综合例子
rust是一门支持循环引用的语言,例如下面的owner和Gadget的关系。
use std::rc::Rc;
use std::rc::Weak;
use std::cell::RefCell;
struct Owner {
name: String,
gadgets: RefCell<Vec<Weak<Gadget>>>,
// 其他字段
}
struct Gadget {
id: i32,
owner: Rc<Owner>,
// 其他字段
}
fn main() {
// 创建一个可计数的Owner。
// 注意我们将gadgets赋给了Owner。
// 也就是在这个结构体里, gadget_owner包含gadets
let gadget_owner : Rc<Owner> = Rc::new(
Owner {
name: "Gadget Man".to_string(),
gadgets: RefCell::new(Vec::new()),
}
);
let gadget_onwer2 = gadget_owner.clone();
// 首先,我们创建两个gadget,他们分别持有 gadget_owner 的一个引用。
let gadget1 = Rc::new(Gadget{id: 1, owner: gadget_owner.clone()});
let gadget2 = Rc::new(Gadget{id: 2, owner: gadget_owner.clone()});
// 我们将从gadget_owner的gadgets字段中持有其可变引用
// 然后将两个gadget的Weak引用传给owner。
gadget_owner.gadgets.borrow_mut().push(Rc::downgrade(&gadget1));
// gadget_owner2 is the ref of the same resource as rc
// same pointer
gadget_onwer2.gadgets.borrow_mut().push(Rc::downgrade(&gadget2));
// 遍历 gadget_owner的gadgets字段
for gadget_opt in gadget_owner.gadgets.borrow().iter() {
// gadget_opt 是一个 Weak<Gadget> 。 因为 weak 指针不能保证他所引用的对象
// 仍然存在。所以我们需要显式的调用 upgrade() 来通过其返回值(Option<_>)来判
// 断其所指向的对象是否存在。
// 当然,这个Option为None的时候这个引用原对象就不存在了。
let gadget = gadget_opt.upgrade().unwrap();
println!("Gadget {} owned by {}", gadget.id, gadget.owner.name);
}
// 在main函数的最后, gadget_owner, gadget1和daget2都被销毁。
// 具体是,因为这几个结构体之间没有了强引用(`Rc<T>`),所以,当他们销毁的时候。
// 首先 gadget1和gadget2被销毁。
// 然后因为gadget_owner的引用数量为0,所以这个对象可以被销毁了。
// 循环引用问题也就避免了
}
6.6 类型系统中常见的trait
本章讲解 Rust 类型系统中的几个常见 trait。有 Into, From, AsRef, AsMut, Borrow, BorrowMut, ToOwned, Deref, Cow。
其中Into,From,Cow不是很常用,简单说明略过。Cow是借鉴linux系统中的写时复制。
6.6.1 From,Into,Cow
From:
对于类型为 U 的对象 foo,如果它实现了 From<T>,那么,可以通过 let foo = U::from(bar) 来生成自己。这里,bar 是类型为 T 的对象。
Into:
对于一个类型为 U: Into<T> 的对象 foo,Into 提供了一个函数:.into(self) -> T,调用 foo.into() 会消耗自己(转移资源所有权),生成类型为 T 的另一个新对象 bar。
例子:
struct Person {
name: String,
}
impl Person {
fn new<S: Into<String>>(name: S) -> Person {
Person { name: name.into() }
}
}
fn main() {
let person = Person::new("Herman");
let person = Person::new("Herman".to_string());
}
Into<String>使传参又能够接受 String 类型,又能够接受 &str 类型。
标准库中,提供了 Into<T> 来为其做约束,以便方便而高效地达到我们的目的。
参数类型为 S, 是一个泛型参数,表示可以接受不同的类型。S: Into<String> 表示 S 类型必须实现了 Into<String>(约束)。而 &str 类型,符合这个要求。因此 &str 类型可以直接传进来。
而 String 本身也是实现了 Into<String> 的。当然也可以直接传进来。
下面name: name.into() 这里也挺神秘的。它的作用是将 name 转换成 String 类型的另一个对象。当 name 是 &str 时,它会转换成 String 对象,会做一次字符串的拷贝(内存的申请、复制)。而当 name 本身是 String 类型时,name.into() 不会做任何转换,代价为零。
Cow: Cow 的设计目的是提高性能(减少复制)同时增加灵活性,因为大部分情况下,业务场景都是读多写少。利用 Cow,可以用统一,规范的形式实现,需要写的时候才做一次对象复制。这样就可能会大大减少复制的次数。
6.6.2 AsRef,AsMut
AsRef 提供了一个方法 .as_ref()。
对于一个类型为 T 的对象 foo,如果 T 实现了 AsRef<U>,那么,foo 可执行 .as_ref() 操作,即 foo.as_ref()。操作的结果,我们得到了一个类型为 &U 的新引用。
1.与 Into<T> 不同的是,AsRef<T> 只是类型转换,foo 对象本身没有被消耗;
2.T: AsRef<U> 中的 T,可以接受 资源拥有者(owned)类型,共享引用(shared referrence)类型 ,可变引用(mutable referrence)类型。
例子如下:
fn is_hello<T: AsRef<str>>(s: T) {
assert_eq!("hello", s.as_ref());
}
let s = "hello";
is_hello(s);
let s = "hello".to_string();
is_hello(s);
因为 String 和 &str 都实现了 AsRef<str>。
AsMut:
AsMut<T> 提供了一个方法 .as_mut()。它是 AsRef<T> 的可变(mutable)引用版本。
对于一个类型为 T 的对象 foo,如果 T 实现了 AsMut<U>,那么,foo 可执行 .as_mut() 操作,即 foo.as_mut()。操作的结果,我们得到了一个类型为 &mut U 的可变(mutable)引用。
注:在转换的过程中,foo 会被可变(mutable)借用。
6.6.3 Borrow,BorrowMut,ToOwned
6.6.3.1 Borrow
Borrow 提供了一个方法 .borrow()。
对于一个类型为 T 的值 foo,如果 T 实现了 Borrow<U>,那么,foo 可执行 .borrow() 操作,即 foo.borrow()。操作的结果,我们得到了一个类型为 &U 的新引用。
Borrow 可以认为是 AsRef 的严格版本,它对普适引用操作的前后类型之间附加了一些其它限制。
Borrow 的前后类型之间要求必须有内部等价性。不具有这个等价性的两个类型之间,不能实现 Borrow。
AsRef 更通用,更普遍,覆盖类型更多,是 Borrow 的超集。
6.6.3.2 BorrowMut
BorrowMut<T> 提供了一个方法 .borrow_mut()。它是 Borrow<T> 的可变(mutable)引用版本。
对于一个类型为 T 的值 foo,如果 T 实现了 BorrowMut<U>,那么,foo 可执行 .borrow_mut() 操作,即 foo.borrow_mut()。操作的结果我们得到类型为 &mut U 的一个可变(mutable)引用。
注:在转换的过程中,foo 会被可变(mutable)借用。
6.6.3.3 ToOwned
ToOwned 为 Clone 的普适版本。它提供了 .to_owned() 方法,用于类型转换。
有些实现了 Clone 的类型 T 可以从引用状态实例 &T 通过 .clone() 方法,生成具有所有权的 T 的实例。但是它只能由 &T 生成 T。而对于其它形式的引用,Clone 就无能为力了。
而 ToOwned trait 能够从任意引用类型实例,生成具有所有权的类型实例。
6.6.4 Deref
Deref 是 deref 操作符 * 的 trait,比如 v。
一般理解,v 操作,是 &v 的反向操作,即试图由资源的引用获取到资源的拷贝(如果资源类型实现了Copy),或所有权(资源类型没有实现 Copy)。
6.6.4.1 强制解引
这种隐式转换的规则为:
一个类型为 T 的对象 foo,如果 T: Deref<Target=U>,那么,相关 foo 的某个智能指针或引用(比如 &foo)在应用的时候会自动转换成 &U。
Rust 编译器会在做 *v 操作的时候,自动先把 v 做引用归一化操作,即转换成内部通用引用的形式 &v,整个表达式就变成 *&v。这里面有两种情况:
1.把其它类型的指针(比如在库中定义的,Box, Rc, Arc, Cow 等),转成内部标准形式 &v;
2.把多重 & (比如:&&&&&&&v),简化成 &v(通过插入足够数量的 * 进行解引)。
简单了解一下即可。