自学内容网 自学内容网

深入理解Rust的所有权和借用


Rust编程语言的所有权机制和借用是它的核心特性之一,旨在确保内存安全、并发安全以及避免数据竞争。由于所有权机制,不需要通过垃圾回收进行内存处理,在保证高性能的同时,还保证了内存安全。通过该机制Rust在编译时就能检查程序的内存安全问题,而不需要在运行时进行额外的开销。理解Rust的所有权和借用是掌握Rust编程的关键。下面详细介绍一下这两个概念

所有权

所有权是Rust的一项独特的内存管理机制,它在编译时就能强制确保内存的安全使用。Rust通过所有权来控制每个值的生命周期和访问权限。每个值在Rust中都有一个"所有者",并且每个值只能有一个所有者。这样做的目的就是避免内存泄漏、数据竞争和悬挂指针等问题。

所有权基本规则

1.每个值都有一个所有者: 在Rust中 每个数据都由一个变量(所有者)拥有。变量是数据的所有者。
2.值在同一时间只能有一个所有者: 当一个值的所有者被转移时,原所有者将不再能够使用该值,Rust会确保在转移后不会有两个所有者持有该值。
3.所有权一旦转移,原所有者将不再有效: 一旦变量的所有权转移,原变量将无法再访问这个值。这种机制叫做所有权转移。
4.当离开变量对应的作用域时,变量这个值将被丢弃。

fn main() {
    let s1 = String::from("Hello");
    //s1的所有权被转移到 s2
    let s2 = s1;  
    //println!("{}", s1);  //error: s1的所有权已被转移,编译报错  
    println!("{}", s2);    //right: s2拥有s1原来的值
    //通过这种方式避免了指针悬挂  
    {
        let s3 = String::from("Inner Rust");
        println!("{}", s3);    //right 这里能访问到
    }
    //离开了变量的作用域 变量值被丢弃 访问异常 
    println!("{}", s3);    //error 访问不到 
}

值的拷贝

基本类型的变量比如i32、i64、u32、u64等固定大小的简单值,通过自动拷贝来进行赋值都被存在栈中,完全无需在堆上分配内存。所以不需要发生所有权的转移。在堆上分配内存的值,在赋值时才需要发生所有权的转移。String类型是一个复杂类型,由存储在栈中的堆指针、字符串长度、字符串容量共同组成,其中堆指针指向了真实存储字符串内容的堆内存,所以在赋值的时候会发生所有权的转移。

进一步,任何基本类型的组合可以自动拷贝的,不需要分配内存或某种形式资源的类型是可以自动拷贝的。

  • 所有整数类型布尔类型、浮点类型、字符类型(char)
  • 元组,当且仅当其包含的类型也都是Copy的时候。比如(i32, i32)是Copy的,但(i32, String)不是
  • 不可变引用&T ,但是注意可变引用&mut T是不可以Copy的
let x = 5;
let y = x; //x的值被拷贝到了y中  
//这里x和y都有效  

let s1 = String::from("hello");
let s2 = s1;
//所有权转移,s2有效s1无效  

Rust不会自动创建数据的"深拷贝",因此,任何自动的复制都不是深拷贝,可以被认为对运行时性能影响较小。如果我们确实需要深度复制String中堆上的数据,而不仅仅是栈上的数据,可以使用一个叫做clone的方法。clone方法会生成一个全新的变量,堆上的内存也会被重新分配一份。

let s1 = String::from("hello");
let s2 = s1.clone();
//两个变量都有效  
println!("s1 = {}, s2 = {}", s1, s2);

传递函数

将值传递给函数,一样会发生所有权移动或者复制,就跟let语句一样,下面的代码展示了所有权、作用域的规则。

fn takes_ownership(some_string: String) { 
    println!("{}", some_string);
    //执行完毕之后 外部传入的string会被drop释放掉
} 

fn makes_copy(some_integer: i32) { 
    println!("{}", some_integer);
    //基本类型不会 drop 在栈上被处理  
} 

fn main() {
    let s1 = String::from("hello rust"); 
    //s1的所有权被转移到函数中
    takes_ownership(s1);
    //这里访问s1会出问题  

    let x = 5;    
    //x是基本类型 没有在堆上分配内存 是Copy的                  
    makes_copy(x);
    //这里x可以继续使用

} 

通过函数的返回值转移所有权

//在函数内部分配内存空间  
fn gives_ownership() -> String { 
    let some_string = String::from("hello"); 
    some_string 
}
fn takes_and_gives_back(a_string: String) -> String { 
    //将外部传入的变量作为返回值 返回出去  
    a_string  
}
fn main() {
    //获取函数内部分配变量的所有权  
    let s1 = gives_ownership();
    let s2 = String::from("hello");
    //s2的所有权转移到函数内部 然后通过返回值给到s3  
    let s3 = takes_and_gives_back(s2); 
                                       
} 

借用

借用允许你访问一个值而不取得其所有权。Rust通过借用来确保不会违反所有权的规则,同时使得多次读取和使用同一数据变得安全。

借用分类

Rust中的借用分为两种类型:
1.可变借用(Mutable Borrow)
可变借用允许你通过借用访问并修改数据。在同一时间,只有一个可变借用者能够访问数据。

2.不可变借用(Immutable Borrow)
不可变借用允许多个借用者同时读取数据,但在同一时刻不能有可变借用。也就是说Rust不允许同时拥有不可变借用和可变借用。

//同一作用域,特定数据只能有一个可变引用   
fn main() {
    let mut s = String::from("Hello");
    //可变借用
    let s2 = &mut s; 
    //let s3 = &mut s; //error  
    s2.push_str(", World!"); 
    println!("{}", s2); 
}

//允许多个不可变借用存在  
fn main() {
    let s = String::from("Hello");
    let s1 = &s; // 不可变借用
    let s2 = &s; // 另一个不可变借用
    
    println!("{}", s1); // 正确: 输出 "Hello"
    println!("{}", s2); // 正确: 输出 "Hello"
}

//可变引用与不可变引用不能同时存在   
fn main() {
    let s = String::from("Hello");
    let s1 = &s;     // 不可变借用
    let s2 = &mut s; // 可变借用 报错 
}

借用的原则

1.同一作用域,特定数据只能有一个可变引用;这种限制的好处就是使Rust在编译期就避免数据竞争
2.可变引用与不可变引用不能同时存在,允许多个不可变借用存在;
多个不可变借用被允许是因为没有人会去试图修改数据,每个人都只读这一份数据而不做修改,因此不用担心数据被污染。

总结

Rust 的所有权和借用机制通过以下几点确保内存安全:
1.每个值只能有一个所有者,且所有者会自动负责清理内存。
2.可以通过不可变借用或可变借用来访问数据,但必须遵循并发借用规则。
3.借用的生命周期与引用的有效性密切相关,编译器通过生命周期分析来确保引用不会悬挂。

通过这些特性,Rust 提供了强大的内存安全性和并发安全性,使得开发者能够避免许多常见的内存管理错误,同时不依赖垃圾回收。


原文地址:https://blog.csdn.net/yang1fei2/article/details/143891380

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!