生于忧患,咸鱼安乐
Toggle navigation
Home
About Me
Archives
Tags
Rust Digest -- 所有权、引用、借用
Rust
2021-04-14 13:15:05
410
0
0
squarefong
Rust
# 所有权 所有运行的程序都必须管理其使用计算机内存的方式。 有些语言具有垃圾回收机制,在程序运行时不断地寻找不再使用的内存(比如Java); 有些语言中,程序员必须亲自分配和释放内存(C/C++)。 Rust 则选择了第三种方式:通过所有权系统管理内存,编译器在编译时会根据一系列的规则进行检查。在运行时,所有权系统的任何功能都不会减慢程序。 ## 所有权规则 首先,让我们看一下所有权的规则。当我们通过举例说明时,请谨记这些规则: 1. Rust 中的每一个值都有一个被称为其 **所有者(owner)**的变量。 2. 值在**任一时刻**有且**只有一个所有者**。 3. 当**所有者(变量)离开作用域**,这个**值将被丢弃**。 ## 变量作用域 - 当 变量 **进入作用域** 时,它就是有效的。 - 这一直持续到它 **离开作用域** 为止。 ## 内存与分配 ```rust #![allow(unused)] fn main() { { let s = String::from("hello"); //从此处起,s 是有效的 // 使用 s } // 此作用域已结束, // s 不再有效 } ``` 对于 String 类型,为了支持一个可变,可增长的文本片段,需要在堆上分配一块在编译时未知大小的内存来存放内容。这意味着: - 必须在运行时向操作系统请求内存。 需要一个当我们处理完 String - 时将内存返回给操作系统的方法。 第一部分由我们完成:当调用 String::from 时,它的实现 (implementation) 请求其所需的内存。这在编程语言中是非常通用的。 第二部分 Rust 采取了一个不同的策略:内存在拥有它的变量离开作用域后就被自动释放。当变量离开作用域,`Rust` 为我们调用一个特殊的函数。这个函数叫做 `drop`,在这里 `String` 的作者可以放置释放内存的代码。Rust 在结尾的 `}` 处自动调用 `drop`。 > 注意:在 C++ 中,这种 item 在生命周期结束时释放资源的模式有时被称作 资源获取即初始化(Resource Acquisition Is Initialization (RAII))。如果你使用过 RAII 模式的话应该对 Rust 的 drop 函数并不陌生。 *个人理解:drop相当于C++里面的析构函数。* ## 变量与数据交互的方式 ```rust #![allow(unused)] fn main() { let x = 5; let y = x; } ``` 对于已知固定大小的简单值,行为是拷贝,现在有了两个变量,x 和 y,都等于 5。 ### 移动 ```rust #![allow(unused)] fn main() { let s1 = String::from("hello"); let s2 = s1; println!("{}, world!", s1); } ``` 你会得到一个类似如下的错误,因为 Rust 禁止你使用无效的引用。 error[E0382]: use of moved value: `s1` --> src/main.rs:5:28 | 3 | let s2 = s1; | -- value moved here 4 | 5 | println!("{}, world!", s1); | ^^ value used here after move | = note: move occurs because `s1` has type `std::string::String`, which does not implement the `Copy` trait String 由三部分组成:一个指向存放字符串内容内存的指针,一个长度,和一个容量。这一组数据存储在栈上。指针指向堆上存放内容的内存。 用C++表示就是: ```C++ class String { char* content; uint32 len; uint32 capacity; } ``` 当执行`let s2 = s1;`时,Rust则认为s1不再有效,s2的指针直接指向s1的堆上内存。 这个行为很像C++中的 浅拷贝(shallow copy)。**不过因为 Rust 同时使第一个变量无效**了,这个操作被称为 **移动(move)**,而不是浅拷贝。上面的例子可以解读为 s1 被 移动 到了 s2 中。 ![](/api/file/getImage?fileId=606ec62f2fc01e1b6d000cd4) > Rust 永远也不会自动创建数据的 “深拷贝”。因此,任何 自动 的复制可以被认为对运行时性能影响较小。 ### 克隆 如果我们 确实 需要深度复制 String 中堆上的数据,而不仅仅是栈上的数据,可以使用一个叫做 **clone** 的通用函数。 ```rust #![allow(unused)] fn main() { let s1 = String::from("hello"); let s2 = s1.clone(); println!("s1 = {}, s2 = {}", s1, s2); } ``` # 引用 ## 引用与借用 ```rust 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 是对 String 的引用 s.len() } // 这里,s 离开了作用域。但因为它并不拥有引用值的所有权, // 所以什么也不会发生 ``` `&` 符号就是 引用,它们允许你使用值但不获取其所有权。 与使用 `&` 引用相反的操作是 解引用(dereferencing),它使用解引用运算符,`*`。 变量 s 有效的作用域与函数参数的作用域一样,不过当引用离开作用域后并不丢弃它指向的数据,因为我们没有所有权。 *我们将获取引用作为函数参数称为 **借用**(borrowing)* ## 可变引用 ```rust 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 s` 和接受一个可变引用 `some_string: &mut String` 不过可变引用有一个很大的限制:**在特定作用域中的特定数据*只能有一个可变引用*, 也*不能*在拥有不可变引用的同时拥有可变引用。** ```rust let mut s = String::from("hello"); let r1 = &mut s; let r2 = &mut s; println!("{}, {}", r1, r2); ``` 编译将报错: ``` error[E0499]: cannot borrow `s` as mutable more than once at a time --> src/main.rs:5:14 | 4 | let r1 = &mut s; | ------ first mutable borrow occurs here 5 | let r2 = &mut s; | ^^^^^^ second mutable borrow occurs here 6 | 7 | println!("{}, {}", r1, r2); | -- first borrow later used here ``` ## 悬垂引用(Dangling References) ```rust fn main() { let reference_to_nothing = dangle(); } fn dangle() -> &String { // dangle 返回一个字符串的引用 let s = String::from("hello"); // s 是一个新字符串 &s // 返回字符串 s 的引用 } // 这里 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 不会允许我们这么做。在 Rust 中编译器确保引用永远也不会变成悬垂状态:**当你拥有一些数据的引用,编译器确保数据不会在其引用之前离开作用域** > 让我们概括一下之前对引用的讨论: 1. 在任意给定时间,要么 只能有一个可变引用,要么 只能有多个不可变引用。 2. 引用必须总是有效的。
Pre:
Rust Digest -- 常见集合
Next:
Rust Digest -- 变量、常量、数据类型
0
likes
410
Weibo
Wechat
Tencent Weibo
QQ Zone
RenRen
Please enable JavaScript to view the
comments powered by Disqus.
comments powered by
Disqus
Table of content