系列文章目录
Rust 语言是一种高效、可靠的通用高级语言,效率可以媲美 C / C++ 。本系列文件记录博主自学Rust的过程。欢迎大家一同学习。
Rust学习入门–【1】引言
Rust学习入门–【2】Rust 开发环境配置
Rust学习入门–【3】Cargo介绍
Rust学习入门–【4】Rust 输出到命令行
Rust学习入门–【5】源代码文件结构简介
Rust学习入门–【6】Rust 基础语法
Rust学习入门–【7】Rust 数据类型
Rust学习入门–【8】复合类型
Rust学习入门–【9】Rust 函数
Rust学习入门–【10】Rust 条件语句
Rust学习入门–【11】Rust 运算符
Rust学习入门–【12】Rust 循环
Rust学习入门–【13】Rust 字符串(上)
Rust学习入门–【14】Rust 字符串(下)
文章目录
- 系列文章目录
- Rust 所有权
- 1、所有权规则
- 2、内存分类
- 栈 stack
- 堆 heap
- 3、所有权的概念
- 4、转让所有权
- 4.1、把一个变量赋值给另一个变量
- 4.1、把变量传递给函数作为参数
- 4.3、函数中返回一个变量作为返回值
- 5、所有权和基本(原始)数据类型
- 注意
Rust 所有权
大多数的编程语言都有管理内存的功能:
-
C/C++ 语言主要通过手动方式管理内存,开发者需要手动的申请和释放内存资源。
-
Java 语言编写的程序在虚拟机(JVM)中运行,JVM 具备自动回收内存资源的功能。但这种方式常常会降低运行时效率,所以 JVM 会尽可能少的回收资源,这样也会使程序占用较大的内存资源。
所有权对大多数开发者而言是一个新颖的概念,它是 Rust 语言为高效使用内存而设计的语法机制。所有权是为了让 Rust 在编译阶段更有效地分析内存资源的有用性以实现内存管理而诞生的概念。
1、所有权规则
所有权有以下三条规则:
- Rust 中的每个值都有一个变量,称为其所有者。
- 一次只能有一个所有者。
- 当所有者不在程序运行范围时,该值将被删除。
2、内存分类
编程语言把内存分为两大类:
- 栈 stack
- 堆 heap
这两种分类并没有对实际的内存做什么,只是把系统分给应用程序的内存标识为上面的两大类而已。
栈 stack
栈 stack 是一种 后进先出 容器。就像我们的存储罐子,后面放进去的只能先拿出来(后面放进去的会放在上面)。
栈 stack 上存储的元素大小必须是已知的,也就是说如果一个变量或数据要放到栈上,那么它的大小在编译是就必须是明确的。
Rust 语言中所有的标量类型都可以存储到栈上,因为它们的大小都是固定的。
而对于字符串这种复合类型,它们在运行时才会赋值,在编译时的大小是未知的。它们不能存储在栈上,而只能存储在 堆 上。
堆 heap
堆 heap 用于存储那些在编译时大小未知的数据,也就是那些只有在运行时才能确定大小的数据。
我们一般在堆 heap 上存储那些动态类型的数据。一般在堆上存储那些可能在程序的整个生命周期中发生变化的数据。
堆 是不受系统管理的,由用户自己管理,使用不当,内存溢出的可能性就大大增加了。
3、所有权的概念
所有权就是一个东西属不属于你,你有没有权力处理它,如:修改和删除。
Rust 语言中每一值都有一个对应的变量,这个变量就成为这个值的 所有者。从某些方面说,定义一个变量就是为这个变量和它存储的数据定义一种所有者管理,声明这个值由这个变量所有。
任何东西只有一个所有者,Rust 中是不允许有共同所有者这个概念的。
-
Rust 中,任何特定时刻,一个数据只能有一个所有者。
-
Rust 中,不允许两个变量同时指向同一块内存区域。变量必须指向不同的内存区域。
4、转让所有权
Rust 语言中转让所有权的方式有以下几种:
-
把一个变量赋值给另一个变量。
-
把变量传递给函数作为参数。
-
函数中返回一个变量作为返回值。
4.1、把一个变量赋值给另一个变量
为了实现内存安全,Rust 严格控制谁可以使用内存和什么时候应该限制使用内存。
fn main(){
// 向量 v 拥有堆上数据的所有权
// 每次只能有一个变量对堆上的数据拥有所有权
let v = vec![1,2,3];
// 赋值会导致两个变量都对同一个数据拥有所有权
// 因为两个变量指向了相同的内存块
let v2 = v;
// Rust 会检查两个变量是否同时拥有堆上内存块的所有权。
// 如果发生所有权竞争,它会自动将所有权判给给新的变量
// 运行出错,因为 v 不再拥有数据的所有权
println!("{:?}",v);
}
上面的代码中我们首先声明了一个向量 v。所有权的概念是只有一个变量绑定到资源,v 绑定到资源或 v2 绑定到资源。
赋值操作 ( v2 = v )会将资源的所有权转移到了 v2,移动后 v 就会变得无效。
4.1、把变量传递给函数作为参数
将堆中的对象传递给闭包或函数时,值的所有权也会发生变更
fn main(){
let v = vec![1,2,3]; // 向量 v 拥有堆上数据的所有权
let v2 = v; // 向量 v 将所有权转让给 v2
display(v2); // v2 将所有权转让给函数参数 v3 ,v2 将变得不可用
println!("In main {:?}",v2); // v2 变得不可用
}
fn display(v3:Vec<i32>){
println!("inside display {:?}",v3);
}
4.3、函数中返回一个变量作为返回值
传递给函数的所有权将在函数执行完成时失效。
即函数的形参获得的所有权将在离开函数后就失效了。
为了解决所有权失效的问题,我们可以让函数将拥有的对象返回给调用者。
fn main(){
let v = vec![1,2,3]; // 向量 v 拥有堆上数据的所有权
let v2 = v; // 向量 v 将所有权转让给 v2
let v2_return = display(v2);
println!("In main {:?}",v2_return);
}
fn display(v3:Vec<i32>)-> Vec<i32> {
// 返回同一个向量
println!("inside display {:?}",v3);
return v3;
}
编译运行上面的 Rust 代码,输出结果如下
5、所有权和基本(原始)数据类型
对于所有的基本数据类型,把一个变量赋值给另一个变量,并不是所有权转让,而是把数据复制给另一个对象。
也就是在内存上重新开辟一个区域,存储复制来的数据,然后把新的变量指向它。
这样做的原因,是因为原始数据类型并不需要占用那么大的内存。
fn main(){
let num1 = 10;
let num2 = num1; // num1 只是将数据拷贝给 num2
println!("num1 = {}",num1);
}
运行结果:
u1 = 10
注意
- 所有权只会发生在堆上分配的数据,对比 C++,可以说所有权只会发生在指针上。
- 基本类型的存储都在栈上,因此没有所有权的概念。