什么是所有权?
Rust
和其它开发语言一样,内存分为栈和堆。而不一样的是为了安排,做了很多工作。
栈
在Rust
中,内存管理有一个叫作frame
的概念,先理解为框架。每个框架有对应的变量,框架释放时,对应的变量被释放。与其他开发语言如c++
栈变量的概念一样。
堆
重点在于堆上的变量,Rust
使用指针的概念,在堆上的空间叫作box
, 盒子,或者装箱?使用Box::new()
来在堆中创建空间,指针指向对应Box
创建的内存。到这一步,好像跟c++/c# 没什么区别。
Rust
不需要释放内存,这么一看,似乎跟c#
又没区别,然而,Rust
的堆内存,与指针变量绑定!!很重要。有些类似于c++的智能指针,随指针而创建,随指针的析构而释放。
更重要的是,Rust
的指针所有存,会进行移动,move
,而非 copy
,这与 unique_ptr
类似。
let x = Box::new(0); // 指针x
let y = x; // 指针y,
println!("x是{}", x); // 错误!! x是谁?x已经没用了
与unique_ptr
不同的是,编译器会自动报错,而不是到了运行时才知道,前面的指针已经失效。这也很重要,这是rust
保证效率的安全性的重要手段。
<span style='color:#eb3b5a'>所以总结: 指针变量拥有一块堆内存所有权,同一时间只有一个指针拥有一块堆内存(Box)的所有权。所有权可以被转移,转移后,原来的指针不可使用。指针被释放时,同时释放拥有所有权的堆内存。</span>
引用和借取
这节内容有点多,并且不容易理解。主要是两个概念:引用和借用。
之前说过指针
,指向一块堆上的内存,类型是Box<type>
,那么引用,就类似于指针的指针,使用 &
表示。而&mut
表示可以修改的指针。
之前说过,指针的所有权会转移,但是引用不会。指针的指针嘛。所以冲突来了:指针析构时会释放内存。这里有更严格的权限管控,不仅仅是释放内存。
在Rust
中,引用不会转移所有权,但并不意味着所有权没有发生变化,也不仅仅是所有权。Rust
将变量所有权分为三级: Read
, Write
,Own
。当使用引用时,引用会从指针借用
权限,此时权限发生变化。Rust
有一个很重要的概念:借用和重构(修改)不会同一时间发生。当需要对指针进行修改时,编译器会要求收回所有借用,后续借用将失效,而这一步发生在编译时!这很重要。
// 测试引用
fn test_references(){
let x = Box::new(0); // 这是个指针
let y = &x; // 这是对x的引用
println!("x = {} and y = {}", x, y);
*x = 3;
println!("x = {} and y = {}", x, y);
}
测试以上代码,发生编译时错误:
error[E0506]: cannot assign to `*x` because it is borrowed
--> src\main.rs:13:5
|
9 | let y = &x; // 这是对x的引用
| -- `*x` is borrowed here
...
13 | *x = 3;
| ^^^^^^ `*x` is assigned to here but it was already borrowed
14 | println!("x = {} and y = {}", x, y);
| - borrow later used here
For more information about this error, try `rustc --explain E0506`.
error: could not compile `references` (bin "references") due to previous error
因为x
已经被借出,所以不能*x = 3
。把下面的打印修改一下
// 测试引用
fn test_references(){
let mut x = Box::new(0); // 这是个指针
let y = &x; // 这是对x的引用
println!("x = {} and y = {}", x, y);
*x = 3;
println!("x = {}", x);
}
再次尝试:
x = 0 and y = 0
x = 3
所以借出的收回,是由编译器来判断,而不需要开发者手动添加。但开发者需要非常明确:对指针进行修改后,原来的引用已经失效了!
对于vector
,
// 测试vector引用
fn test_vector_references(){
let mut x = vec![1, 2, 3];
let y = &x[0];
println!("x = {:?} and y = {}", x , y);
x.push(4)
println!("x = {:?} and y = {}", x , y);
}
可以从输出来看:
error[E0502]: cannot borrow `x` as mutable because it is also borrowed as immutable
--> src\main.rs:27:5
|
23 | let y = &x[0];
| - immutable borrow occurs here
...
27 | x.push(4);
| ^^^^^^^^^ mutable borrow occurs here
28 | println!("x = {:?} and y = {}", x , y);
| - immutable borrow later used here
For more information about this error, try `rustc --explain E0502`.
error: could not compile `references` (bin "references") due to previous error
对vector成员的引用也会被收回。
另外,从函数返回引用时,也必须明确返回哪个引用。
ps: 这一节非常复杂,先挑理解的写一下,回头如果使用
rust
,需要多读几遍。
纠错
在Rust
中,很多未定义行为会导致错误。有些安全问题也会导致错误!这一节给了些例子:
1. 返回引用
fn return_a_string() -> &String {
let s = String::from("Hello world");
&s
}
这明显是个错误,s会在退出函数后释放。
可以的用法:
1. 直接将所有权move出去
fn retuan_a_string() -> String{
let s = ...;
s
}
2. 返回静态字符串
fn return_a_string() -> &'static' str{
"Hello world"
}
3. 用`Rc`,还不太理解
fn return_a_string() -> Rc<String> {
let s = Rc::new(String::from("Hello world"));
Rc::clone(&s)
}
2. 权限不足问题
对于代码
fn stringify_name_with_title(name: &Vec<String>) -> String{
name.push(String::from("Esq."));
let full = name.join(" ");
full
}
这里明显有问题,name
是不允许修改的指针,对它进行操作必然不行。根据场景不同有几种改法:
- 将name 改为可以赋值的指针
fn stringify_name_with_title(name: &mut Vec<String>) -> String{
但这样也不太对,对传入的数组进行了修改。
- 将name复制一份
fn stringify_name_with_title(name: Vec<String>) -> String{
name2 = name.clone();
name2.push(...);
full = name2.join(" ");
full
}
问题是需要进行数组复制,效率较低。
- 不操作数组
本质上只是想把数组边起来,加上后缀。因此:
fn stringify_name_with_title(name: &Vec<String>) -> String{
let mut full = name.join(" ");
full.push_str(" Esq.");
full
}
3. 别名和内存重构
对于代码:
fn add_big_strings(dst: &mut Vec<String>, src: &[String]) {
let largest: &String =
dst.iter().max_by_key(|s|s.len()).unwrap();
for s in src {
if s.len() > largest.len() {
dst.push(s.clone());
}
}
}
现在还不知道 iter
是什么,怎么用的。但是largest
是dst
的成员指针。由此,已经从dst
借出了权限。后面dst
添加元素时,会导致largest
失效。
因为修改
fn add_big_strings(dst: &mut Vec<String>, src: &[String]) {
let largest_len: &String =
dst.iter().max_by_key(|s|s.len()).unwrap().len();
for s in src {
if s.len() > largest_len() {
dst.push(s.clone());
}
}
}
或者教程还给出了另一种做法:先找出要修改哪些,再修改(此时largest
已经失效)
fn add_big_strings(dst: &mut Vec<String>, src: &[String]) {
let largest: &String =
dst.iter().max_by_key(|s|s.len()).unwrap();
let to_add: Vec<String> =
src.iter().filter(|s|s.len() > largest.len()).clone().collect();
dst.extend(to_add);
}
4. 修复数组成员拷贝或移动的错误
看代码:
let v: Vec<i32> = vec![0, 1, 2];
let n_ref: &i32 = &v[0];
let n: i32 = *nref;
和代码:
let v: Vec<String> = vec![String::from("Hello world")];
let n_ref: &String = &v[0];
let n: String = *nref;
两段代码看起来一样,只有一个区别: 类型是i32
还是String
。
但是第一个可以正常编译和运行,第二个编译会报错。原因是对 String
类型,内在在堆上,不能直接赋值。
所以,要不使用 &String
指针类型,要不使用clone()
进行复制。
5. 对 Tuple
的不同字段进行修改
看代码:
let mut name = (
String::from("张"),
String::from("三")
);
let first = &name.0;
name.1.push_str("小");
println!("{first} {}", name.1)
由于修改name.1
,first
应该失效,所以导致错误。
看另一个代码:
fn get_first(name: &(String, String)) ->&String {
&name.0
}
fn main() {
let mut name = (
String::from("张"),
String::from("三")
);
let first = get_first(&name);
name.1.push_str("小");
println!("{first} {}", name.1)
}
仍然报错。
同理,对数组的不同成员进行类似操作,也会报错!
这意味着不能持有结构的不同成员进行修改,或者说,在对数据进行修改时,它的不同引用,包括成员引用,都会失效!这很重要!
切片
Rust
对集合类型有Slice
切片结构,用来指定局部集合。在文档中描述为近似胖指针
,包括一个位置指针和一个长度。切片属于局部引用,所以和其他引用一样的权限。
切片形式为[start..end]
,其中start
和end
如果是头尾,可以省略:[..]
。
对于字符串,切片类型为 &str
,对所有字符串传递的函数,可以将参数和返回值写为 &str
来统一方便地操作。传参时,程序会自动转换。
- 打赏
- 分享
- 微信
- 支付宝