什么是所有权?

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, WriteOwn。当使用引用时,引用会从指针借用权限,此时权限发生变化。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是不允许修改的指针,对它进行操作必然不行。根据场景不同有几种改法:

  1. 将name 改为可以赋值的指针
fn stringify_name_with_title(name: &mut Vec<String>) -> String{

但这样也不太对,对传入的数组进行了修改。

  1. 将name复制一份
fn stringify_name_with_title(name: Vec<String>) -> String{
	name2 = name.clone();
	name2.push(...);
	full = name2.join(" ");
	full
}

问题是需要进行数组复制,效率较低。

  1. 不操作数组
    本质上只是想把数组边起来,加上后缀。因此:
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是什么,怎么用的。但是largestdst的成员指针。由此,已经从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.1first 应该失效,所以导致错误。

看另一个代码:

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],其中startend如果是头尾,可以省略:[..]

对于字符串,切片类型为 &str,对所有字符串传递的函数,可以将参数和返回值写为 &str 来统一方便地操作。传参时,程序会自动转换。

  • 打赏
  • 分享
分享到...
请选择打赏方式
  • 微信
  • 支付宝

By yhl

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注