跳到主要内容

所有权

因为变量负责释放自己的资源,所以资源只能有一个所有者。这可以防止资源被多次释放。请注意,并非所有变量都拥有资源(例如引用)。

当进行赋值( let x = y )或按值传递函数参数( foo(x) )时,资源的所有权将被转移。在 Rust 语言中,这被称为移动。

资源移动后,原所有者将无法再使用。这可以避免创建悬空指针。

// This function takes ownership of the heap allocated memory
fn destroy_box(c: Box<i32>) {
println!("Destroying a box that contains {}", c);

// `c` is destroyed and the memory freed
}

fn main() {
// _Stack_ allocated integer
let x = 5u32;

// *Copy* `x` into `y` - no resources are moved
let y = x;

// Both values can be independently used
println!("x is {}, and y is {}", x, y);

// `a` is a pointer to a _heap_ allocated integer
let a = Box::new(5i32);

println!("a contains: {}", a);

// *Move* `a` into `b`
let b = a;
// The pointer address of `a` is copied (not the data) into `b`.
// Both are now pointers to the same heap allocated data, but
// `b` now owns it.

// Error! `a` can no longer access the data, because it no longer owns the
// heap memory
//println!("a contains: {}", a);
// TODO ^ Try uncommenting this line

// This function takes ownership of the heap allocated memory from `b`
destroy_box(b);

// Since the heap memory has been freed at this point, this action would
// result in dereferencing freed memory, but it's forbidden by the compiler
// Error! Same reason as the previous Error
//println!("b contains: {}", b);
// TODO ^ Try uncommenting this line
}

当所有权转移时,数据的可变性可以改变。

fn main() {
let immutable_box = Box::new(5u32);

println!("immutable_box contains {}", immutable_box);

// Mutability error
//*immutable_box = 4;

// *Move* the box, changing the ownership (and mutability)
let mut mutable_box = immutable_box;

println!("mutable_box contains {}", mutable_box);

// Modify the contents of the box
*mutable_box = 4;

println!("mutable_box now contains {}", mutable_box);
}

部分动作

在单个变量的解构中,可以同时使用 by-move 和 by-reference 模式绑定。这样做将导致变量的部分移动,这意味着变量的一部分将被移动,而其他部分将保留。在这种情况下,父变量以后不能作为整体使用,但是仅被引用(而不是移动)的部分仍然可以使用。

fn main() {
#[derive(Debug)]
struct Person {
name: String,
age: Box<u8>,
}

let person = Person {
name: String::from("Alice"),
age: Box::new(20),
};

// `name` is moved out of person, but `age` is referenced
let Person { name, ref age } = person;

println!("The person's age is {}", age);

println!("The person's name is {}", name);

// Error! borrow of partially moved value: `person` partial move occurs
//println!("The person struct is {:?}", person);

// `person` cannot be used but `person.age` can be used as it is not moved
println!("The person's age from person struct is {}", person.age);
}

(在本例中,我们将 age 变量存储在堆上来说明部分移动:删除上述代码中的 ref 会产生错误,因为 person.age 。如果 Person.age 存储在堆栈上,则不需要 ref 作为 age 复制数据而不移动它。)

所有程序都需要管理自己运行时使用的内存空间。使用垃圾回收机制的语言会在运行时定期检查并回收那些没有被继续使用的内存:比如golangjava;还有一些语言是需要程序员去手动释放内存的,比如C/C++。但是Rust另辟蹊径:使用所有权系统来管理内存,这套规则允许编译器在编译过程中执行检查工作,而不产生任何的运行时的开销。

所有权规则

  1. Rust中每一个值都有一个对应的变量作为它的所有者。

  2. 在同一时间内,值有且仅有一个所有者。

  3. 当所有者离开自己的作用域时,它持有的值就会被释放掉。

堆栈和内存分配

当你希望将数据放入堆中时,你就可以请求特定大小的空间。操作系统会根据你的请求在堆中找到一块足够大的可用空间。将它们标记为已使用,并把指向这片空间地址的指针返回给我们。由于指针的大小是固定的且可以在编译期确定。所以可以将指针存储在栈中。当想要访问指针所指向的具体数据时,可以通过指针指向的地址来访问。

许多系统编程语言都需要你记录代码中分配的堆空间。最小化堆上的冗余数据,并及时清理堆上的无用数据以避免消耗空间。而所有权概念则解决了这些问题。一旦熟练的掌握了所有权及其相关工具,就可以将这些问题交给Rust处理,减轻用于思考堆和栈的心智负担。不过,知晓如何使用和管理堆内存可以帮助我们理解所有权存在的意义及其背后的工作原理。

Copy trait

类似于整型的类型可以在编译时确定自己的大小,并且能够将自己的数据完整地存储在栈中,对于这些值的复制操作永远都是非常快速的。这也同样意味着,在创建变量y之后,我们没有任何理由去阻止变量x继续保持有效。换句话说,对于这些类型而言,深度拷贝与浅度拷贝没有任何区别,调用clone并不会与直接的浅度拷贝有任何行为上的区别。因此,我们完全不惜要在类似的场景中考虑上面的问题。

对于字符串字面量而言,由于我们在编译时就知道其内容,所以这部分硬编码的文本被直接嵌入到了最终的可执行文件中。这就是访问字符串字面量异常高效的原因,而这些性质完全得益于字符串字面量的不可变性。不幸的是,我们没有办法将那些未知大小的文本在编译期统统放入二进制文件中,更何况这些文本的大小还可能随着程序的运行而发生改变。

let x = 5;
let y = x;
println!("x = {},y = {}",x,y);

Rust提供了一个名为Copytrait,它可以用于整数这类完全存储在栈上的数据类型。一旦某种类型拥有了Copy这种trait,那么它的变量就可以在赋值给其他变量之后保存可用性。如果一种类型本身或这种类型的任意成员实现了实现了Drop这种trait,那么Rust就不允许其实现Copy这种trait。尝试给某个需要在离开作用域时执行特殊指令的类型实现Copy这种trait会导致编译时错误。一般来说,任何简单标量的组合类型都可以是Copy的,任何需要分配内存或某种资源的类型都不会是Copy的。下面是一些拥有Copy这种trait的类型:

  1. 所有的整数类型,诸如u32

  2. 仅拥有两种值(true 和 false)的布尔类型:bool

  3. 字符类型:char

  4. 所有的浮点类型,诸如f64

  5. 如果元组包含的所有字段的类型都是Copy的,那么这个元组也是Copy的。诸如(i32,i32)Copy的,但是(i32,String)则不是。

Drop trait

对于String类型而言,为了支持一个可变的,可增长的文本类型,我们需要在堆上分配一块在编译时未知大小的内存来存放数据。这同时也是意味着:

  1. 我们使用的内存是由操作系统在运行时动态分配出来的。

    1. 这里的第一步由我们,也就是程序的编写者,在调用String::from时完成,这个函数会请求自己需要的内存空间。在大部分编程语言中都有类似的设计:由程序员来发起堆内存的分配请求。
  2. 当使用完String时,我们需要通过某种方式来将这些内存归还给操作系统。

    1. 在某些拥有垃圾回收Garbage Collector ,GC机制的语言中,GC会代替程序员来负责记录并清除那些不再使用的内存。

    2. 对于那些没有GC的语言来说,识别不在使用的内存并调用代码显示释放的工作就依然需要由程序员去完成,正如我们请求分配时一样。按照以往的经验来看,正确地完成这些任务往往是十分困难的。假如我们忘记释放内存,那么就会造成内存泄漏:假如我们过早的释放内存,那么就会产生一个非法变量;假如我们重复释放同一块内存,那么就会产生无法预知的后果。为了程序的稳定运行,我们必须严格地将分配和释放操作一一对应起来。

    3. 与这些语言不同,Rust提供了另外一套解决方案:内存会自动地在拥有它的变量离开作用域后进行释放。下面的代码类似于示例中的代码,不过我们将字符串字面量换成了String类型:有一个很适合用来回收内存给操作系统的地方:变量s离开作用域的地方。Rust在变量离开作用域时,会调用一个叫drop的特殊函数。String类型的作者可以在这个函数中编写释放内存的代码。记住,Rust会在作用域结束的地方(即}处)自动调用drop函数。

    {
    let s = String::from("hello"); //从这里开始,变量s变得有效
    //execute operation with s;
    //作用域到这里结束,变量s失效
    }

移动move

Rust中的多个变量可以采用一种独特的方式与同一数据进行交互

let x = 5;
let y = x;
//x = y = 5 整数是已知固定大小的简单值,两个值5会同时被推入当前栈中。

let s1 = String::from("hello");
let s2 = s1;
  1. String的示例代码展示了String的内存布局,它实际上由3部分组成,如下图左侧所示:一个指向存放字符串内容的指针,一个长度及一个容量,这部分的数据存放在了栈中。长度字段被用来记录当前String的文本使用了多少字节的内存。而容量字段则被用来记录String向操作系统总共获取到的内存字节数量。长度和容量之间的区别十分重要,但我们先不去讨论这个问题,简单地忽略内容字段即可

  2. 当我们将s1赋值给s2时,便复制了一次String的数据,这意味着我们复制了它存储在栈上的指针,长度以及容量字段。但我们没有复制指针指向的堆数据。假如Rust依旧这样的模式去执行赋值,那么当堆上的数据足够大时,类似于s2=s1这样的指令就会造成相当可观的运行时的性能消耗。前面我们提到过,当一个变量离开当前的作用域时,当Rust会自动调节它的drop函数,并将变量使用的堆内存释放回收。不过,第二张图展示的内存布局里有两个指针指向了同一个地址,这就导致了一个问题:当s2s1离开自己作用域时,它们会尝试去重复释放相同的内存。这也就是我们之前提到过的内存的错误之一,臭名昭著的二次释放。重复释放内存可能会导致某些正在使用的数据发生损坏,进而产生潜在的安全隐患。

  3. 为了确保内存安全,同时避免了复制分配的内存,Rust在这种场景下会简单的将s1废弃,不再视其为一个有效的变量。因此,Rust也不需要在s1离开作用域后清理任何东西。试图在s2创建完毕后使用s1,如下所示会导致编译错误。这一语义完美的解决了我们的问题!既然只有s2有效,那么也就只有它会在离开自己的作用域时释放空间,所以再也没有二次释放的可能性了。另外,这里还隐含了另外一个设计原则:Rust永远不会自动地创建数据的深度拷贝。因此在Rust中,任何自动的赋值操作都可以被视为高效的。

let s1 = String::from("hello");
let s2 = s1;
println("{},world!",s1)
/**
error\[E0382\]: borrow of moved value: \`s1\`
--\> src/main.rs:4:26
|
2 | let s1 = String::from("hello");
| \-\- move occurs because \`s1\` has type \`std::string::String\`, which does not implement the \`Copy\` trait
3 | let s2 = s1;
| \-\- value moved here
4 | println!("{},world!",s1);
| ^^ value borrowed here after move
error: aborting due to previous error; 1 warning emitted
*/

克隆clone

要去深度拷贝String堆上的数据,而不仅仅是栈数据时,就可以使用一个名为clone的方法。

lets s1 = String::from("hello");
let s2 = s1.clone();
println!("s1 = {},s2 = {}",s1,s2)

这段代码在Rust中完全合法,在堆中重新克隆了一份数据。当你看到某处调用了clone时,你就应该知道某些特定的代码将会被执行,而且这些代码可能会相当的消耗资源。你可以很容易的在代码中觉察到一些不同寻常的事情正在发生。

所有权与函数

将值传递给函数在语义上类似于对变量进行赋值。将变量传递给函数将会触发移动或复制,就像是赋值语句一样。

fn main() {
let s = String::from("hello"); // 变量s进入作用域
takes_ownership(s); // s的值被移动进了函数,所以它从这里开始不再有效
let x = 5; // 变量x进入作用域
make_copy(x) //变量x同样被传递进了函数,但由于i32是Copy的,所以我们依然可以在这之后使用x
} // x首先离开作用域,随后是s。
// 但是由于s的值已经发生了移动,所有没有什么特别的事情会发生。
fn takes_ownership(some_string: String){ // some_string进入作用域
println!("{}",some_string);
} // some\_string在这里离开作用域,drop函数被自动调用,some\_string所占用的内存也就随之被释放了
fn make_copy(some_integer: i32){ // some_integer 进入作用域
println!("{}",some_integer);
} // some_integer在这里离开作用域,没有发生什么特别的事情

尝试在调用takes_ownership后使用变量s会导致编译时错误。这类静态检查可以使我们免于犯错。你可以尝试在main函数中使用s和x变量,来看一下所有权规则的约束下能够在哪些地方合法的使用他们。

返回值与作用域

函数在返回值的过程中也会发生所有权的转移。

fn main() {
let s1 = gives_ownership(); // gives_ownership将它的返回值移动至s1中
let s2 = String::from("hello"); // s2进入作用域
let s3 = take_and_gives_back(s2); // s2被移动进函数
// take\_and\_gives_back中,而这个函数的返回值又被移动到了变量s3上
} // s3在这里离开作用域并被销毁,由于s2已经移动了,所以它不会在离开作用域时发生任何事情。s1最后离开作用域并被销毁。
fn gives_ownership() -> String{ // gives_ownership会将它的返回值移动至调用它的函数内
let some_string = String::from("hello"); // some_string进入作用域
some_string // some_string作为返回值移动至调用函数
}
// take_and_gives_back将取得一个String的所有权并将它作为结果返回
fn take_and_gives_back(a_String: String) -> String {
// a_String进入作用域
a_String // a_String作为返回值移动至调用函数
}

变量所有权的转移总是遵循相同的模式:将一个值赋值给另一个变量时就会转移所有权。当一个持有堆数据的变量离开作用域时,它的数据就会被drop清理回收,除非这些数据的所有权移动到了另一个变量上。

在所有的函数中都要获取所有权并返回所有权显得有些繁琐。假如你希望在调用函数时保留参数的所有权,那么就不得不将传入的值作为结果返回。除了这些需要保留所有权的值,函数还可能会返回它们本身的结果。

当然你也可以利用元组来同时返回多个值。

fn main() {
let s1 = String::from("hello");
let (s2,len) = calculate_length(s1);
println!("The length of '{}' is {}.",s2,len);
}
fn calculate_length(s: String) -> (String,usize){
let length = s.len(); // len()会返回当前字符串的长度
(s,length)
}

但这种写法未免太过笨拙了,类似的概念在编程工作中相当常见。幸运的是,Rust针对这类场景提供了一个名为引用的功能。

引用与借用

借用是引用传递参数的这一种方法过程。

返回参数的所有权的示例代码中,由于调用了calculate_length会导致String移动到函数体内部,而我们又希望在调用完毕之后继续使用该String,所以我们不得不使用元组将String作为元素再次返回。

下面的示例重新定义了一个新的calculate_length函数。与之前不同的是,新的函数签名使用了String的引用作为参数而没有直接转移值的所有权。

fn main() {
let s1 = String::from("hello");
let len = calculate_length(&s1);
println!("The length of '{}' is {}.",s1,len);
}
fn calculate_length(s: &String) -> usize {
s.len()
}

首先需要注意的是,变量声明及函数返回值中的那些元组代码都消失了。我们调用calculate_length函数使用了&s1作为参数,且在该函数的定义中,我们使用&String替代了String

这些&代表的就是引用语义,它们允许你在不获取所有权的前提下使用值

与使用&进行引用相反的操作被称为解引用dereferencing,它使用*作为运算符。

let s1 = String::from("hello");
let len = calculate_length(&s1); //这里的&s1语法允许我们在不转移所有权的前提下,创建一个指向s1值的引用。由于引用不持有值的所有权,所以当引用离开当前的作用域时,它指向的值也不会被丢弃。

同理,函数签名中的&用来表明参数s的类型是一个引用。下面的注释给出了更详细的解释:

fn calculate_length(s: &String) -> usize{ // s是一个指向String的引用
s.len()
} // 到这里,s离开作用域。但是由于它并不持有自己所指向值的所有权,所以没有用什么特殊的事情会发生

此处,变量s的有效作用域与其他任何参数一样,唯一不同的是,它不会在离开自己的作用域时销毁其指向的数据,因为它并不拥有该数据的所有权。当一个函数使用引用而不是值本身作为参数时,我们便不需要为了归还所有权而特意去返回值,毕竟在这种情况下,我们根本没有获得所有权。

引用分为可变和不可变:

fn main() {
let s = String::from("hello");
change(&s);
}
fn change(some_string: &String){
some_string.push_str("world");
}
/**
error\[E0596\]: cannot borrow `*some_string` as mutable, as it is behind a `&` reference
--\> src/main.rs:7:5
|
6 | fn change(some_string: &String){
| \-\-\-\-\-\-\- help: consider changing this to be a mutable reference: `&mut std::string::String`
7 | some\_string.push\_str("world");
| ^^^^^^^^^^^ \`some_string\` is a `&` reference, so the data it refers to cannot be borrowed as mutable
*/
fn main() {
let mut s = String::from("hello");
change(&mut s);
}
fn change(some_string: &mut String){
some_string.push_str("world");
}

首先,我们需要将变量s声明为mut,即可变的。其实,我们使用&mut来给函数传入一个可变引用,并将函数签名修改为some_string: &mut String来使其可以接受一个可变引用作为参数。

单可变引用在使用上有一个很大的限制:对于特定作用域中的特定数据来说,一次只能声明一个可变引用。以下代码尝试违背这一限制,则会导致编译错误:

 let mut s = String::from("hello");
let r1 = &mut s;
let r2 = &mut s;
println!("{},{},{}",s,r1,r2);

/**
error\[E0499\]: cannot borrow \`s\` as mutable more than once at a time
--\> src/main.rs:4:14
|
3 | let r1 = &mut s;
| \-\-\-\-\-\- first mutable borrow occurs here
4 | let r2 = &mut s;
| ^^^^^^ second mutable borrow occurs here
5 | println!("{},{},{}",s,r1,r2);
| \-\- first borrow later used here
*/

但在另外一方面,在Rust中遵循这条限制性规则则可以帮助我们在编译中避免数据竞争。数据竞争与竞态条件十分类似,它会在指令满足以下3种情形下发生:

  • 两个或两个以上的指针同时访问同一空间

  • 其中至少有一个指针会向空间中写入数据

  • 没有同步数据访问的机制

数据竞争会导致未定义的行为,由于这些未定义的行为往往难以在运行时跟踪,也就使得出现的bug更加难以被诊断和修复。Rust则完全避免了这种情形,因为存在数据竞争的代码连编译检查都无法通过!

与大部分语言类似,我们可以通过或括号来创建一个新的作用域范围,这就使我们可以创建多个可变引用,当然,这些可变引用不会同时存在:

let mut s = String::from("hello");
{
let r1 = &mut s;
} // 由于r1在这里离开了作用域,所以我们可以合法地再创建一个可变引用。
let r2 = &mut s;

/**
error\[E0502\]: cannot borrow \`s\` as mutable because it is also borrowed as immutable
--\> src/main.rs:5:14
|
3 | let r1 = &s; // 没问题
| \-\- immutable borrow occurs here
4 | let r2 = &s; // 没问题
5 | let r3 = &mut s; // 错误
| ^^^^^^ mutable borrow occurs here
6 | println!("{},{},{}",r1,r2,r3);
| \-\- immutable borrow later used here
*/

可变引用和不可变引用不可以共存。我们不能在拥有不可变引用的同时创建可变引用。同时可变引用是唯一的。

尽管这些编译错误会让人感到沮丧,但是要牢记一点:Rust编译器可以为我们提早(在编译时而不是在运行时)暴露那些潜在的bug,并且明确指出出现问题的地方。你不再需要去追踪调试为何数据会在运行时发生了非预期的变化。

悬垂引用

是用拥有多指针概念的语言会非常容易错误地创建悬垂指针。这类指针指向曾经存在的某处内存地址,但该内存已经被释放掉甚至是被重新分配另作他用了。而在Rust语言中,编译器会确保引用永远不会进入这种悬垂状态。假如我们当前持有某一个数据的引用,那么编译器可以保证这个数据不会在引被销毁前离开自己的作用域。

我们来创建一个悬垂引用,看看Rust是如何在编译期发现这个错误的:

fn main() {
let reference_to_nothing = dangle();
}
fn dangle() -> &String {
let s = String::from("hello");
&s
}
/**
error[E0106]: missing lifetime specifier
--> main.rs:5:16
|
5 | fn dangle() -> &String {
| ^ expected lifetime parameter
|
= help: this function's return type contains a borrowed value, but there is
no value for it to be borrowed from
= help: consider giving it a 'static lifetime
*/

错误信息引用了一个我们还未介绍的功能:生命周期。

因为 s 是在 dangle 函数内创建的,当 dangle 的代码执行完毕后,s 将被释放。不过我们尝试返回它的引用。这意味着这个引用会指向一个无效的 String,这可不对!Rust 不会允许我们这么做。

这里的解决方法是直接返回 String

fn no_dangle() -> String {
let s = String::from("hello");
s
}

这样就没有任何错误了。所有权被移动出去,所以没有值被释放。

结构体所有权

如果是整个 struct 发生 move, 则 user1 不能再使用

  let user1 = User{
active:true,
username:String::from("aaa"),
id:1
};
let user2 = user1;
print!("{},{},{} \n", user1.active, user1.username, user1.id); // 报错

如果只是 struct 某个字段发生 move, user1 除了发生 move 的字段不能使用,其他字段还可以使用

  let user1 = User{
active:true,
username:String::from("aaa"),
id:1
};
let user3 = User{
active:user1.active,
username:user1.username, // 发生move
id:user1.id
};
print!("{},{} \n", user1.active,user1.id); // 正常

结构体字段如果需要使用引用类型,就必须加上生命周期,否则就会报错。

借用

大多数时候,我们希望访问数据而不取得数据的所有权。为了实现这一点,Rust 使用了借用机制。对象可以通过引用传递( &T ),而不是按值传递对象( T )。

编译器静态地保证(通过其借用检查器)引用始终指向有效的对象。也就是说,当存在对对象的引用时,该对象不能被销毁。

// This function takes ownership of a box and destroys it
fn eat_box_i32(boxed_i32: Box<i32>) {
println!("Destroying box that contains {}", boxed_i32);
}

// This function borrows an i32
fn borrow_i32(borrowed_i32: &i32) {
println!("This int is: {}", borrowed_i32);
}

fn main() {
// Create a boxed i32 in the heap, and a i32 on the stack
// Remember: numbers can have arbitrary underscores added for readability
// 5_i32 is the same as 5i32
let boxed_i32 = Box::new(5_i32);
let stacked_i32 = 6_i32;

// Borrow the contents of the box. Ownership is not taken,
// so the contents can be borrowed again.
borrow_i32(&boxed_i32);
borrow_i32(&stacked_i32);

{
// Take a reference to the data contained inside the box
let _ref_to_i32: &i32 = &boxed_i32;

// Error!
// Can't destroy `boxed_i32` while the inner value is borrowed later in scope.
eat_box_i32(boxed_i32);

// Attempt to borrow `_ref_to_i32` after inner value is destroyed
borrow_i32(_ref_to_i32);
// `_ref_to_i32` goes out of scope and is no longer borrowed.
}

// `boxed_i32` can now give up ownership to `eat_box` and be destroyed
eat_box_i32(boxed_i32);
}

可以使用 &mut T 可变地借用可变数据。这称为可变引用,并为借用者提供读/写访问权限。相比之下, &T 通过不可变引用借用数据,借用者可以读取数据但不能修改它:

#[allow(dead_code)]
#[derive(Clone, Copy)]
struct Book {
// `&'static str` is a reference to a string allocated in read only memory
author: &'static str,
title: &'static str,
year: u32,
}

// This function takes a reference to a book
fn borrow_book(book: &Book) {
println!("I immutably borrowed {} - {} edition", book.title, book.year);
}

// This function takes a reference to a mutable book and changes `year` to 2014
fn new_edition(book: &mut Book) {
book.year = 2014;
println!("I mutably borrowed {} - {} edition", book.title, book.year);
}

fn main() {
// Create an immutable Book named `immutabook`
let immutabook = Book {
// string literals have type `&'static str`
author: "Douglas Hofstadter",
title: "Gödel, Escher, Bach",
year: 1979,
};

// Create a mutable copy of `immutabook` and call it `mutabook`
let mut mutabook = immutabook;

// Immutably borrow an immutable object
borrow_book(&immutabook);

// Immutably borrow a mutable object
borrow_book(&mutabook);

// Borrow a mutable object as mutable
new_edition(&mut mutabook);

// Error! Cannot borrow an immutable object as mutable
new_edition(&mut immutabook);
}

aliasing

数据可以被不可变地借用任意次数,但是在不可变地借用的同时,原始数据不能被可变地借用。另一方面,一次只允许一次可变借用。只有最后一次使用可变引用后,才能再次借用原始数据。

struct Point { x: i32, y: i32, z: i32 }

fn main() {
let mut point = Point { x: 0, y: 0, z: 0 };

let borrowed_point = &point;
let another_borrow = &point;

// Data can be accessed via the references and the original owner
println!("Point has coordinates: ({}, {}, {})",
borrowed_point.x, another_borrow.y, point.z);

// Error! Can't borrow `point` as mutable because it's currently
// borrowed as immutable.
// let mutable_borrow = &mut point;
// TODO ^ Try uncommenting this line

// The borrowed values are used again here
println!("Point has coordinates: ({}, {}, {})",
borrowed_point.x, another_borrow.y, point.z);

// The immutable references are no longer used for the rest of the code so
// it is possible to reborrow with a mutable reference.
let mutable_borrow = &mut point;

// Change data via mutable reference
mutable_borrow.x = 5;
mutable_borrow.y = 2;
mutable_borrow.z = 1;

// Error! Can't borrow `point` as immutable because it's currently
// borrowed as mutable.
// let y = &point.y;
// TODO ^ Try uncommenting this line

// Error! Can't print because `println!` takes an immutable reference.
// println!("Point Z coordinate is {}", point.z);
// TODO ^ Try uncommenting this line

// Ok! Mutable references can be passed as immutable to `println!`
println!("Point has coordinates: ({}, {}, {})",
mutable_borrow.x, mutable_borrow.y, mutable_borrow.z);

// The mutable reference is no longer used for the rest of the code so it
// is possible to reborrow
let new_borrowed_point = &point;
println!("Point now has coordinates: ({}, {}, {})",
new_borrowed_point.x, new_borrowed_point.y, new_borrowed_point.z);
}

ref

通过 let 绑定进行模式匹配或解构时, ref 关键字可用于获取对结构/元组字段的引用。下面的示例显示了一些有用的实例:

#[derive(Clone, Copy)]
struct Point { x: i32, y: i32 }

fn main() {
let c = 'Q';

// A `ref` borrow on the left side of an assignment is equivalent to
// an `&` borrow on the right side.
let ref ref_c1 = c;
let ref_c2 = &c;

println!("ref_c1 equals ref_c2: {}", *ref_c1 == *ref_c2);

let point = Point { x: 0, y: 0 };

// `ref` is also valid when destructuring a struct.
let _copy_of_x = {
// `ref_to_x` is a reference to the `x` field of `point`.
let Point { x: ref ref_to_x, y: _ } = point;

// Return a copy of the `x` field of `point`.
*ref_to_x
};

// A mutable copy of `point`
let mut mutable_point = point;

{
// `ref` can be paired with `mut` to take mutable references.
let Point { x: _, y: ref mut mut_ref_to_y } = mutable_point;

// Mutate the `y` field of `mutable_point` via a mutable reference.
*mut_ref_to_y = 1;
}

println!("point is ({}, {})", point.x, point.y);
println!("mutable_point is ({}, {})", mutable_point.x, mutable_point.y);

// A mutable tuple that includes a pointer
let mut mutable_tuple = (Box::new(5u32), 3u32);

{
// Destructure `mutable_tuple` to change the value of `last`.
let (_, ref mut last) = mutable_tuple;
*last = 2u32;
}

println!("tuple is {:?}", mutable_tuple);
}
Loading Comments...