如何理解rust的所有权
所有权
如何理解rust
的所有权机制,会让我们更深入地理解rust
优秀的内存管理机制以及比C++
更为安全的代码结构。
首先以C语言举个例子,如果我们要定义一个指针对象:Fun *p = do_something()
,接下来使用这个对象do_otherthing(p)
;在函数do_othering
对p
使用过后,我们不知道函数内部对p做了那些改变,碰巧如果该函数实现极为复杂,对p
使用了多次,这会对程序的维护运行引入不小的麻烦。如果p
在函数内部被释放,而外部词法作用域并不知道,这样会形成一个空指针,在这样的情况如果再次使用p
,会带来错误。
在此基础上C++
引入了智能指针来减少了这类bug
的出现,此时也出现了所有权
的概念。
rust
中的所有权代表了以下概念:
- 每个值在Rust中都有一个变量来管理,这个变量便是这块内存的管理者。
- 每个值或者说每块内存在一个时间点上只能有一个拥有
所有权
的拥有者。 - 当变量所在的作用域结束的时候,变量会被释放。
fn main(){
let mut s = String::from("hello");
s.push_str("juejin");
println!("{:?}",s);
}
在上面这一小段代码中,定义了一个String
类型的字符串,接着对他进行修改,然后输出。main
函数结束,s的生命周期也结束,被内存释放。
生命周期是rust对于管理所有权的一个扩充
如果在上面第一行代码后面加一行:
let s1 = s;
接下来对与s的使用便会报错,提示value used here after move
,这是因为s的所有权转移给了s1
,这里原因还有String
类型没有实现Copy
trait。实现了Copy
trait的类型,会在赋值时拷贝一块内存给赋值的变量,这样两个指向不同的内存地址,也就可以共存。rust中一些简单的变量都实现了Copy
trait,便于开发使用。如数字,布尔等。
智能指针
Box
rust中有三中简单的智能指针Box<T>
,Rc<T>
,Cell<T>
。这三种与所有权语义都有联系。
首先是Box
,rust
中所有权值默认在栈上分配。而我们可以通过Box
指针将值包装,使他在堆上分配内存。
fn main(){
let num = Box::new(1);
let i = #
// let k = num; Error
let j = #
println!("{:?}",num);
println!("{}",i)
}
由于Box
自身实现了Deref
trait,所以我们可以通过&
解引用获得内部的值1.而同时Box也实现了Drop
trait,这样会在作用域结束的时候自动销毁。销毁过程是先释放堆上的数据,后释放值。使用Box的情况主要在于要知道一个类型在定义时占用多少内存空间,这时使用Box便可以轻松确定。Box指针本身在栈上,里面的数据在堆上分配。
Rc
Rc
指针,顾名思义,是一个引用计数指针。Rc
所包裹的为不可变类型,他允许同时存在多个引用,但不可对内部进行修改。Rc
通过clone()
方法来增加引用。
use std::rc::Rc;
let arr = Rc::new(vec![1,2,3]);
let arr_1 = arr.clone();
let arr_2 = Rc::clone(&arr);
如果我们需要一个不可变变量的多次引用,并且修改。可使用RefCell
。
Cell
首先说一下内部可变性
。即多个引用共享,同时存在。共享引用可以修改内部数据。虽然rust所有权的核心是共享不可变,但这两个并不冲突。因为之前的共享不可变是编译阶段编译器的检查,使用Cell
可以通过返回包裹的值的一个拷贝,在对其进行修改,再用新值替换旧值。Cell
的使用有一个要求是Cell<T>
中T必须实现Copy
trait。这也对应了前面拷贝的语义。
let num = Cell::new(String::from("12"));
num.set(String::from("22"));
println!("{}",num.get());
// Error
// note: the following trait bounds were not satisfied:
// `String: Copy`
let num_1 = Cell::new(1);
num_1.set(123);
println!("{}",num_1.get());