终于把Rust所有权讲明白了
程序在内存中运行,所有权指的也是内存的所有权,这个概念被提出的目的,就是让Rust在编译阶段能够更有效地分析内存资源,并优化管理,相当于是静态的垃圾回收。
记 A 1 , A 2 , ⋯ , A n A_1,A_2,\cdots,A_n A1,A2,⋯,An是一群变量, V 1 , V 2 , ⋯ , V n V_1, V_2,\cdots, V_n V1,V2,⋯,Vn是内存中的值,那么Rust中的所有权可以写成三条规则:
- 某 V n V_n Vn对应某个变量 A n A_n An,则该变量 A n A_n An是 V n V_n Vn的所有者。
- 在某个时刻, V n V_n Vn只能对应一个 A n A_n An。
- 当 A n A_n An离开作用域后, V n V_n Vn会被删除。
这里面的值,可以理解为是物理上的一段存储空间,所有者即变量,是代码中用于标记这段内存的名字。
可行域
在Rust中,任何变量都有其可行域,在作用域中创建的变量,无法渗透到外面,这就是所有权的一个体现。比如在调用下面这个函数时就会报错
fn owner(){
{
let a = "micro";
}
println!("{}",a);
}
在上述代码中,a就是一个变量,或者说所有者;字符串micro则是a的值;外面的花括号就是a的可行域。根据所有权的第【3】条规则,a离开作用域后,"micro"会被删除。
在编程中难免遇到一些长度不固定的变量,所以程序必须有在执行期间分配内存的能力,不能一切都指望编译期。而且这个分配,既包括新内存的发放,也包括老内存的销毁。Rust通过所有权机制,在编译期会自动添加一些释放资源的函数,以维护内存安全。
移动和克隆
下面这种写法大家已经司空见惯了,将5绑定给x,然后再将x绑定给y,其编译运行结果没有任何疑问,符合直觉。
fn test_move1(){
let x = 5;
let y = x;
println!("x={},y={}", x,y)
}
但换一种数据类型,结果却报错了。
fn test_move2(){
let x = String::from("micro");
//let y = x; //报错
let y = x.clone(); // 正确
println!("x={},y={}", x,y)
}
根据等号赋值的错信息可知,需要调用clone方法对x进行克隆,即修改后采用的x.clone()。在直接使用等号赋值的过程中,相当于把x的值移动给了y,x自己就没有了,触犯了所有权的第【2】条规则。
在rust中,一些小而直接的数据类型,默认启用克隆模式,比如test_move1中演示的整数,这些类型还包括
- 所有整数类型,例如 i32、u32、i64 等。
- 布尔类型 bool,值为 true 或 false 。
- 所有浮点类型,f32 和 f64。
- 字符类型 char。
- 仅以上类型数据的元组
函数传参
在rust中,任何表达式都有返回值,故而可看成是函数,所以变量在传递过程中的特性,在函数传参时仍旧适用,示例如下
fn test_print(s:String){
println!("s={}", s);
}
fn test_print_int(i:i32){
println!("i={}", i);
}
fn test_move3(){
let x = String::from("micro");
//test_print(x); //所有权问题报错
test_print(x.clone());
println!("x={}", x);
let y = 5;
test_print_int(y); //无所有权问题
println!("y={}", y);
}
其中, y y y在成为函数的参数后,并未触发所有权问题;但字符串 x x x在直接传入test_print后,会因所有权问题报错。
引用与租借
如果想把一个复杂的变量x赋予y,要么选择移动,但这样x自己就没了;要么选择克隆,但开销比较大。一个自然的想法就是,能不能把x的地址传给b,这样两人就可以共享一块内存区域。
这种操作rust当然是支持的,名曰引用,只需用到取地址符&,这个特性在前面介绍循环时提到过,示例如下
fn test_ref(){
let x = String::from("cool");
let y = &x;
x.push_str("l")
println!("x={},y={}", x,y)
}
函数参数传递的道理一样。
但是,正所谓皮之不存毛将焉附,如果y在引用x的值之后,如果x的值被移动给了另外一个变量,那么y的引用也自然就作废了。
另一方面,y只是引用到了x的地址,但并没有得到这篇内存的所有权,所以y在理论上是不可写入的,这种感觉就像是借书一样,可以随便看,但不能乱写乱画。所以下面的代码报错就是理所当然的了。
实例
fn test_borrow(){
let x = String::from("cool");
let y = &x;
y.push_str("l");
println!("{}", y);
}
但是,非要更改也不是完全不可以,只需采用mut引用,写成下面这样就可以了,y成了一个可变引用类型的数据。
fn test_borrow2(){
let mut x = String::from("cool");
let y = &mut x;
y.push_str("l");
println!("{}", y);
}
但可变引用也存在问题,即只允许可变引用一次。最后,引用不可以作为函数的返回值,因为函数的返回值将不确定这个引用会给谁,可能导致灾难性后果。
这就是Rust,严谨而死板。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)