• Rust所有权:你需要知道的10条规则
  • 发布于 2个月前
  • 127 热度
    0 评论
像C/C++这样的编程语言为开发人员提供了对内存管理的最大控制。但这也带来了很大的风险——很容易犯错误,导致崩溃、安全漏洞!

C/C++程序员面临的一些常见问题包括:
1.释放内存后再使用内存,这将导致崩溃或奇怪的漏洞。
2.用完后忘记释放内存,随着时间的推移,这会导致内存泄漏。
3.两部分代码同时访问同一内存,这可能导致竞态条件。

为了避免这些问题,Rust使用了所有权系统。这增加了一些编译器检查的规则,以确保内存安全。关键思想是Rust中的每个值都有一个所有者,所有者对这个值负责——管理它的生命周期,释放它,允许访问它,等等。通过跟踪所有权,Rust的编译器可以确保值在使用时是有效的,防止数据竞争,并在需要时释放内存。所有这些都不需要垃圾收集器!

这种所有权模型增强了Rust的安全性和速度,通过遵循一些所有权规则,使你的Rust程序将免受内存相关问题的影响。

让我们来看看Rust所有权提供的10种超能力:

1. 每个值都有一个变量,称为其所有者
在Rust中,每个值(如字符串或整数)都有一个所有者。所有者是绑定到该值的变量。例如:
let x = 5;
这里,x是整数值5的所有者。变量x跟踪并管理5的值。这种所有权系统避免了多个变量指向相同值的混乱情况。对于单一所有权,很明显x是数据5的唯一所有者。

2. 当所有者超出范围时,该值将被删除
当所有者变量超出作用域时,Rust将调用该值的drop函数并清理它:
{
  let y = 5; // y是5的所有者
} // y超出了作用域,5被删除

作用域指的是变量在哪个块中有效,在上面的例子中,y只存在于{}花括号中。一旦执行离开该块,y就消失了,值5也被删除了。这种自动释放数据的方式避免了内存泄漏。一旦所有者消失,Rust就会清理这个值。再也不用担心悬空指针或内存膨胀了!


3.一次只能有一个所有者
Rust强制每个值的单一所有权,这避免了昂贵的引用计数方案:
let z = String::from("5"); 
let x = z; // z的所有权移到了x
// z不再拥有5
在这个例子中,将所有权从z转移到x的成本很低。有些语言使用引用计数,其中多个变量可以指向一个值,但这有开销。对于单一所有权,Rust只更新一个内部所有者变量来将所有权从z移到x。没有代价高昂的计数器更新。

4. 复制所有者时,将移动数据
将所有者变量赋值给一个新变量会移动数据:
let s1 = "hello".to_string(); // s1拥有“hello”

let s2 = s1; // s1的所有权转移到了s2
             // s1不能再使用“hello”了
这里我们创建字符串“hello”并将其绑定到s1。然后我们把s1赋值给一个新的变量s2。这将所有权从s1转移到s2。S1不再拥有字符串!数据本身没有被复制,只是所有权移动了。这可以防止意外地制作昂贵的副本。要真正复制数据,必须明确的使用Rust的clone()方法。


5. 所有权可以通过引用来借用
我们可以创建引用变量来借用所有权:
let s = "hello".to_string(); // s拥有“hello”
let r = &s; // r不变地借用s
            // s仍然拥有“hello”

println!("{}", r);
&操作符创建了一个引用r,该引用在这个作用域中从s借用了所有权。可以把r看作是暂时借用s所拥有的数据,s仍然保留对数据的完全所有权,r只允许读取"hello"字符串。


6. 可变引用具有独占访问权
一次只能有一个对值的可变引用:
let mut s = "hello".to_string();  
let r1 = &mut s; // r1是s的可变引用
let r2 = &mut s; // error!
这可以防止编译时的数据竞争。可变引用r1对s有独占的写访问,所以在r1完成之前不允许有其他引用。这使同时访问数据变得不可能,从而避免了微妙的并发性错误。

7. 引用的有效期必须短于其所有者
引用的生命周期必须短于它们所有者的生命周期:
{
    let r = String::from("hello");  
    drop(r);          
    let s = &r;
    println!("{}", s);       
}
Rust通过强制执行引用不能比其所有者的生命周期更长这个规则来防止在内存释放后再次使用引用。

8. 结构体可以通过move或borrow传递
我们可以转移或借用struct的所有权:
struct User {
  name: String,
  age: u32  
}

let user1 = User {
  name: "John".to_string(),
  age: 27
}; 

let user2 = user1; // 所有权转移到user2
                   // user1不能再使用

let borrow = &user1; // 通过引用借用user1  
                     // user1仍然拥有数据
结构体将相关数据组织在一起,但是所有权规则仍然适用于它们的字段。我们可以将结构体的所有权传递给函数和线程,或者不变地借用它们。

9. 所有权规则也适用于在堆上分配的数据
let s1 = String::from("hello"); // 栈上的s1拥有堆数据
let s2 = s1.clone(); // 堆数据复制到新位置  
                     // s1和s2各自拥有独立的数据

let r = &s1; // r不变地借用s1的堆数据  
             // s1仍然拥有堆数据
这里s1是在栈上分配的,但包含一个指向堆分配的String。即使它在堆上,所有权规则也同样适用。Rust防止重复释放或在free之后使用,即使在使用指针时也是如此,所有权系统保证堆分配安全。

10. 所有权支持安全并发
所有权增强了Rust的无畏并发性:
use std::thread;

let v = vec![1, 2, 3]; 

let handle = thread::spawn(move || {
  println!("Here's a vector: {:?}", v);
});

handle.join().unwrap();
我们使用move闭包将v的所有权移到派生线程中。这可以防止多个线程并发访问v。在Rust中,所有权系统使得并发安全且简单。不需要锁,因为编译器强制单一所有权。
用户评论