Rust 所有权

457 阅读4分钟

所有权

所有权是 rust 这门语言的独特特性,其他语言是此设计。因为它是新的东西,所以我们需要去适应。

所有权与堆栈

像 JS 这种动态编程语言,通常不用直接接触堆栈。但是 rust 作为系统编程语言,对内容的控制是要精确到堆栈的。所有权其实也是对堆栈内存控制的一个体现。

所有权规则

所有权只是规则,作用于值和变量上。下面是所有权的规则:

  • 变量(所有者)与值必须有对应关系,变量就是值的 owner(主人)
  • 所有者只能有一个
  • 范围(作用域)内有效,超出无效

rust 范围

范围一般使用 {} 包裹:

// 
let s = "string";

// {} 范围
{
   let a = "this is a";
}

// s 在当前访问范围内
// a 不在当前访问内,不能使用,超出它能使用的范围,此时内部已经将其删除。

赋值,移动与变量类型所有权

赋值

赋值只是正对基本数据类型

  • str
  • 所有整形
  • 布尔类型
  • 浮点
  • 元组

基础类型字符串与所有

let str = "this";
let o_str = str;

println!("{}-{}", str, o_str); // this-this

str 是一个简单的切片类型,我们 str 直接赋值给 o_str, 没有任何的问题。

引用类型字符串与所有权

let str_ref = String::from("this is ref string");
println!("{}-{}", str_ref, o_str);
let o_str_ref = str_ref; // 引用类型数据赋值,str_ref 对应的值的所有权发生了变化

println!("{}-{}", o_str_ref, str_ref); // str_ref 此时已经没有所有权对应的值

对于 str_ref 变量, rust 编译器会认为其值发生了移动 (move),而没有所有权。

image.png

clone 克隆,解决 move 的没有所有权的部分问题

上面我们其实是使用了转移 move,但是 rust 语言的所有权的设计, str_ref 已经发生了转移,为了也能访问,其实 rust 的 String 给我们提供了 clone 方法来解决这个问题

let str_ref = String::from("this is ref string");
println!("{}-{}", str_ref, o_str);
let o_str_ref = str_ref.clone(); // clone 相当于单独的 clone 一个新的内容,到新的地址

println!("{}-{}", o_str_ref, str_ref); // str_ref 此时已经没有所有权对应的值

函数参数-函数返回值转移所有权

转移有一个规则:

将值分配给另一个变量会移动它。当包含堆上数据的变量超出范围时,该值将被清除,drop除非该数据已被移至由另一个变量拥有。

传入参数

一个变量通过参数,传递所有权后,变量的所有权归函数内部的变量所有权,在没有返回值时,在函数外部已经访问该参数的所有权。

fn main() {
    let val = String::from("this is val"); // val 获取 this is val 所有权
    
    fn get_param_val_ownership(val){
        println!("{}", val);
    }
    
   get_param_val_ownership(val);
   
   // val is no in scope, because is no fn get_param_val_ownership
   println!("{}", val)  // error 
}

错误还和上面的图片一致:

image.png

这个问题会在下面的引用和借用有了解

函数返回值

函数的返内部的变量具有的返回值,函数的返回值赋值给函数外部变量新的

fn return_val_ownership() -> String {
   let c = String::from("this is return val ownership"); 
   return c;
}

let outer_c = return_val_ownership();
println!("{}", outer_c); // outer_c get the c val ownership

不获取的所有权操作:引用和借用

其实前面报错中,我们也发现了 borrowed 的相关错误。下面从引用开始,在到借用的相关所有权。

fn borrow_fn() {
    let val = String::from("this is test borrow");

    let len = borrow_fn_t(&val); // 这里我们借用 val,并没有将所有权通过参数传递到函数内部

    println!("{}-{}", val, len); // 借用,使得 val 还能够访问
}

fn borrow_fn_t(val: &String) -> usize {
    val.len()
}

borrow_fn_t(&val) 中 & 即引用操作符,用于表示引用了 val 变量。

引用和可变引用

  • &String
  • &mut String

不可变引用

fn change_val_ref_error(val: &String) {
    return val.push_str("sfdf"); // 这里要特别注意借用过来的 val 是不能修该的
}

可变引用

为了能够需改数据,我们的数据必须是 mut 不可变的,而且 &mut

fn main() { 
    let mut s = String::from("hello");
    change(&mut s); 
} 

fn change(some_string: &mut String) { 
    some_string.push_str(", world"); 
}

可变的引用 &mut:一次只能有一个对特定数据的可变引用

let mut s = String::from("hello");

let r1 = &s; // no problem
let r2 = &s; // no problem
let r3 = &mut s; // BIG PROBLEM

println!("{}, {}, and {}", r1, r2, r3);

悬空引用

fn m_t() { 
    let ref_to_nothing = return_ref_s(); 
} 

fn return_ref_s() -> &String { 
    let s = String::from("this is string"); 
    &s // 这里的 &s, 是返回值,但是这个引用没有被使用到,处于悬垂的作用
}

无所有权类型总结

  1. 切片类型 slice 无所有权