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(通過插入足夠數量的 * 進行解引)。
簡單了解一下即可。