所有权"/>
讲透Rust核心概念:所有权
文章目录
- 可行域
- 移动和克隆
- 函数传参
- 引用与租借
Rust初步上手
程序在内存中运行,所有权指的也是内存的所有权,这个概念被提出的目的,就是让Rust在编译阶段能够更有效地分析内存资源,并优化管理,相当于是静态的垃圾回收。
Rust中的所有权有以下三条规则:
- 每个值对应一个变量,变量是值的所有者。
- 这个值在某一时刻,只能有一个所有者。
- 若所有者不在程序运行范围,该值将被删除。
可行域
在Rust中,任何变量都有其可行域,在作用域中创建的变量,无法渗透到外面,这就是所有权的一个体现。比如在调用下面这个函数时就会报错
fn owner(){{let a = "micro";}println!("{}",a);
}
错误发生在编译期间
>rustc main.rs
error[E0425]: cannot find value `a` in this scope
--> main.rs:10:19|
10 | println!("{}",a);| ^|
help: the binding `a` is available in a different scope in the same function --> main.rs:8:13|
8 | let a = "micro";| ^
error: aborting due to previous erro
在编程中难免遇到一些长度不固定的变量,所以程序必须有在执行期间分配内存的能力,不能一切都指望编译期。而且这个分配,既包括新内存的发放,也包括老内存的销毁。
Rust在编译时,会自动在合适的地方添加一些释放资源的函数,从而维护了内存安全。
移动和克隆
下面这种写法大家已经司空见惯了,将5绑定给x,然后再将x绑定给y,其编译运行结果没有任何疑问,就是x=5,y=5
fn test_move(){let x = 5;let y = x;println!("x={},y={}", x,y)
}
但换一种数据类型,结果却报错了,说是把x的值移动给了y,所以x自己就没有了。
fn test_move2(){let x = String::from("micro");let y = x;println!("x={},y={}", x,y)
}
并且报错中还给出了温馨提示,说
help: consider cloning the value if the performance cost is acceptable|
13 | let y = x.clone();| ++++++++
即需要调用clone方法来将x值克隆给y,否则就相当于是把x的值移动给了y,所以x自己就没有了。二者的区别相当于是复制和剪切。
在rust中,一些小而直接的数据类型,在数据传递过程中,是默认克隆模式的,比如test_move1中演示的整数,这些类型还包括
- 所有整数类型,例如 i32 、 u32 、 i64 等。
- 布尔类型 bool,值为 true 或 false 。
- 所有浮点类型,f32 和 f64。
- 字符类型 char。
- 仅以上类型数据的元组
而其他数据类型,不好意思,默认的就是移动,如果想把其他类型的x复制给y,就要用到下面的形式
fn test_clone(){let x = String::from("micro");let y = x.clone();println!("x={},y={}", x,y);
}
函数传参
在rust中,要知道任何表达式都可以当成是一个函数,是有返回值的。那么自然地,变量在参数传递过程中的特性,也自然要发生在函数传参时,下面的写法有报错了,要求把test_print(x)
改写为test_print(x.clone())
fn test_print(s:String){println!("s={}", s);
}fn test_move3(){let x = String::from("micro");test_print(x);println!("x={}", x);
}
但是同样地,如果传递的参数是一个基础类型的变量,那就完全没问题。
fn test_print_int(i:i32){println!("i={}", i);
}fn test_move4(){let x = 5;test_print_int(x);println!("x={}", x);
}
引用与租借
对于复杂变量x,如果想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(0[let x = String::from("cool");let y = &mut x;y.push_str("l");println!("{}", y);
])
但可变引用也有一个问题,即只允许可变引用一次。最后,引用不可以作为函数的返回值,因为函数的返回值将不确定这个引用会给谁,可能导致灾难性后果。
这就是Rust语言的风格,严谨而死板,由此带来的好处则是更加安全。
更多推荐
讲透Rust核心概念:所有权
发布评论