跳至主要內容

Rust所有权

LincZero大约 9 分钟

Rust所有权

所有权 (ownership)

因为变量要负责释放它们拥有的资源,所以资源只能拥有一个所有者。这也防止了资源的重复释放注意并非所有变量都拥有资源(例如引用)。

所有权就是值一个东西归属谁。Rust 中一个变量对应一个值,变量就称为这个值得所有者。

let name = "从0到Go语言微服务架构师";

这句话的意思就是,”从 0 到 Go 语言微服务架构师” 这个值所在内存块由变量 name 所有

Rust 中,只能由一个所有者,不允许两个同时指向同一块内存区域。变量必须指向不同的内存区域

所有权与堆栈

内存分为两大类:

  • 栈(stack),先进先出。类型大小是固定的,如i32.
  • 堆(heap),编译时大小未知或不确定大小,用户自己管理,增加内存溢出风险

它是一种 后进先出 的机制。

类似我们日常的落盘子,只能一个一个向上方,然后从最上面拿一个盘子。一个变量要放到栈上,那么它的大小在编译时就要明确。

Rust 中可以放到栈上的数据类型,他们的大小都是固定的

用于编译时大小未知或不确定的,只有运行时才能确定的数据。

在堆上存储一些动态类型的数据。堆是不受系统管理的,是用户自己管理的,也增加了内存溢出的风险

栈堆的对比、所有权

    • 大小:运行时才能确定
    • 数据类型:字符串,在运行时才会赋值的变量,在编译期的时候大小是未知或不确定的。所以字符串类型存储在堆上。
    • 所有权:所有权只会发生在堆上分配的数据,每次只能有一个变量对堆上数据有所有权
    • 大小:固定

    • 数据类型:基础数据类型 (整型,浮点型,布尔,字符)

    • 所有权:栈中数据赋值等没有所有权的移动 (赋值时是值拷贝,不存在资源移动)

      基础数据类型没有所有权的概念,基础类型可以认为是值拷贝。

      在内存上另外的地方,存储和复制来的数据,然后让新的变量指向它。

      let a = 88;
      let b = a;
      println!("a {}, and b {}", a, b);
      

补充

这里的栈和堆的概念特指 rust 中的栈堆,其他语言不一定。

笔者不知道这个举例是否恰当:像Cpp类实例这种,大小是固定的,但是是需要动态分配一块内存的,也是创建在堆上。

转让所有权 (移动所有权) (move)

概念

在进行赋值(let a = b)或通过值来传递函数参数(foo(a))的时候, 资源的所有权(ownership)会发生转移。按照 Rust 的规范,这被称为资源的移动(move)。

在移动资源之后,原来的所有者不能再被使用(这点不同于其他语言,其他语言是可以继续使用的),这可避免悬挂指针(dangling pointer)的产生。

类似我们人类把一个东西送人或丢弃。

三种方式

以下几种方式转让所有权

变量赋值给另一个变量

fn main() {
	// 栈上数据
    let a = 88;	     						// 栈分配的整型
    let b = a;			 					// 将 `a` *复制*到 `b`——不存在资源移动
    println!("a {}, and b {}", a, b);		// 两个值各自都可以使用

    // 堆上数据
    let v1 = vec!["Go语言极简一本通","Go语言微服务架构核心22讲"];  // v1 拥有堆上数据的所有权。(每次只能有一个变量对堆上数据有所有权)
    let v2 =v1;												// `v2=v1` 后,v2 拥有了堆上数据的所有权,v1 已经没有对数据的所有权了
    println!("{:?}",v1);									// 所以再使用 v1 会报错
    														// 如果 Rust 检查到 2 个变量同时拥有堆上内存的所有权。会报错如下
}

// 报错如下:
error[E0382]: borrow of moved value: `v1`
let v1 = vec!["Go语言极简一本通","Go语言微服务架构核心22讲"];
| -- move occurs because `v1` has type `Vec<&str>`, which does not implement the `Copy` trait
9 | let v2 =v1;
| -- value moved here
10 | println!("{:?}",v1);
| ^^ value borrowed here after move
|

变量传递给函数参数

fn show(v:Vec<&str>) {
    println!("v {:?}", v)
}

fn main() {
    let studyList = vec!["Go语言极简一本通"];		// studyList 拥有堆上数据管理权
    let studyList2 = studyList;		    		// studyList 将所有权转义给了 studyList2
    show(studyList2);							// studyList2 将所有权转让给参数 v,studyList2 不再可用
    println!("studyList2 {:?}",studyList2);	    // //studyList2 已经不可用,再使用会出现下面的报错
}

// 报错如下:
error[E0382]: borrow of moved value: `studyList2`
| let studyList2 = studyList; // studyList 将所有权转义给了 studyList2
| ---------- move occurs because `studyList2` has type `Vec<&str>`, which does not implement the `Copy` trait
| show(studyList2); // studyList2 将所有权转让给参数 v,studyList2 不再可用。
| ---------- value moved here
| println!("studyList2 {:?}",studyList2);//studyList2 已经不可用。
| ^^^^^^^^^^ value borrowed here after move

函数中的返回值

fn show2(v:Vec<&str>) -> Vec<&str> {
    println!("v {:?}",v);
    return v;
}

fn main() {
    let studyList3 = vec!["Go语言极简一本通"];
    let studyList4 = studyList3;
    let result = show2(studyList4);
    println!("result {:?}",result);	    // 输出 result ["Go语言极简一本通"]
}

借用所有权 (Borrowing)

概念

生活中,我们对工具有所有权,但是也不妨碍我们可以把工具借给别人甚至租用给别人,别人用完了,要还给你的。

Rust 中,Borrowing(借用),就是一个函数中的变量传递给另外一个函数作为参数暂时使用。也会要求函数参数离开自己作用域的时候将所有权 还给当初传递给它的变量(好借好还,再借不难嘛!)。

使用

&变量名  //要把参数定义的时候这样定义。

示例:

fn show(v: &Vec<&str>){
    println!("v1:{:?}", v)
}

fn main() {
    let studyList = vec!["Go语言极简一本通","Go语言微服务架构核心22讲","从0到Go语言微服务架构师"];
    let studyList2 = studyList;
    show2(&studyList2);							// 借用给函数
    println!("v2:{:?}", studyList2);		// 我们看到,函数show使用完v2后,我们仍然可以继续使用
}

// 输出
v1:["Go语言极简一本通", "Go语言微服务架构核心22讲", "从0到Go语言微服务架构师"]
v2:["Go语言极简一本通", "Go语言微服务架构核心22讲", "从0到Go语言微服务架构师"]

可变的借用

上面我们的例子可以说都是只读的,我们看下面:

fn show2(v:&Vec<&str>){
    v[0]="第一个充电项目已完成";
    println!("v:{:?}",v)
}

// 报错如下:
error[E0596]: cannot borrow `*v` as mutable, as it is behind a `&` reference
| fn show2(v:&Vec<&str>){
| ---------- help: consider changing this to be a mutable reference: `&mut Vec<&str>`
| v[0]="第一个充电项目已完成";
| ^ `v` is a `&` reference, so the data it refers to cannot be borrowed as mutable

报错的原因:我们的这个借用不可以是可变的。那么 Rust 中,如果想要让一个变量是可变的,只能在前面加上 mut 关键字。

修改如下:

fn show2(v: &mut Vec<&str>){
    v[0]="第一个充电项目已完成";
    println!("v:{:?}",v)
}

fn main() {
    let mut studyList3 = vec!["Go语言极简一本通","Go语言微服务架构核心22讲","从0到Go语言微服务架构师"];
    println!("studyList3:{:?}", studyList3);
    show2(&mut studyList3);
    println!("调用后,studyList3:{:?}", studyList3);
}

 // 输出
studyList3:["Go语言极简一本通", "Go语言微服务架构核心22讲", "从0到Go语言微服务架构师"]
v:["第一阶段学习已完成", "Go语言微服务架构核心22讲", "从0到Go语言微服务架构师"]
调用后,studyList3:["第一阶段学习已完成", "Go语言微服务架构核心22讲", "从0到Go语言微服务架构师"]

如果我们要在Borrowing(借用)的时候改变其中的值:

  1. 变量要用mut关键字。
  2. 函数参数为可变的要用 &mut 关键字。
  3. 传递参数的时候,也要用 &mut 关键字

Slice (切片)

概念

切片是指向一段连续内存的指针

在 Rust 中,连续内存够区间存储的数据结构:数组(array)、字符串(string)、向量(vector)。

切片可以和它们一起使用,切片也使用数字索引访问数据。下标索引从0开始。slice 可以指向数组的一部分,越界的下标会引发致命错误(panic)。

切片运行时才能确定 (大小) 的,并不像数组那样编译时就已经确定了

有点类似python的切片操作,挺方便的

使用

let 切片值 = &变量[起始位置..结束位置] // 两个点
  • [起始位置..结束位置],这是一个左闭右开的区间。
  • 起始位置最小值是0。
  • 结束位置是数组、向量、字符串的长度。
fn main() {
   let mut v = Vec::new();
   v.push("Go语言极简一本通");
   v.push("Go语言微服务架构核心22讲");
   v.push("从0到Go语言微服务架构师");
   println!("len:{:?}", v.len());
   let s1 = &v[1..3];
   println!("s1:{:?}", s1);
}

// 输出
len:3
s1:["Go语言微服务架构核心22讲", "从0到Go语言微服务架构师"]

切片当参数

切片通过引用的方式传递给函数。

fn show_slice(s:&[&str]){
   println!("show_slice函数内:{:?}",s);
}
show_slice(s1);	// 把上面的s1传递给函数show_slice

// 输出:
show_slice函数内:["Go语言微服务架构核心22讲", "从0到Go语言微服务架构师"]

可变切片

如果我们声明的原数据是可变的,同时定义切片也有**&mut**关键字,就可以更改切片元素来更改元数据了。

fn modify_slice(s: &mut [&str]) {
   s[0] = "这个阶段已学习完毕";
   println!("modify_slice函数:{:?}", s);
}
let mut v2 = Vec::new();
v2.push("Go语言极简一本通");
v2.push("Go语言微服务架构核心22讲");
v2.push("从0到Go语言微服务架构师");
println!("modify_slice之前:{:?}", v2);

modify_slice(&mut v2[1..3]);
println!("modify_slice之后:{:?}", v2);

//输出
modify_slice之前:["Go语言极简一本通", "Go语言微服务架构核心22讲", "从0到Go语言微服务架构师"]
modify_slice函数:["这个阶段已学习完毕", "从0到Go语言微服务架构师"]
modify_slice之后:["Go语言极简一本通", "这个阶段已学习完毕", "从0到Go语言微服务架构师"]