所有权

所有权

所有权是Rust独有的特性,它让Rust无需GC就可以保证内存安全。

所有权解决的问题

Stack-栈

压入(把值压入stack不叫分配)
stack存放的是固定大小的数据。
应为指针是已知固定大小的,可以把指针存放到stack上。
把数据压入stack比在heap上分配快的多。

Heap-堆

分配
跟踪代码那些部分正在使用heap的那些数据。
最小化heap上的重复数据。
清理heap上未使用的数据,以免空间不足。
管理heap数据才是所有权存在的特性。

所有权规则

  • 每一个值都有一个变量,这个变量是该值的所有者。
  • 每个值同时只能有一个所有者(变量)。
  • 当所有者(变量)超出作用域(scope)时,该值将被删除(drop)。

作用域

fn main() {
    {    // s 在这里无效,它尚未声明
        let s = "hello";  // 从此处起,s 是有效的

        // 使用 s
        println!("s is {}", s);
    }   // 此作用域已结束,s不再有效
} 

变量和数据交换方式

移动

基本数据类型

x、y已知固定大小的简单值,都会被压到stack中

stateDiagram Stack栈 state Stack栈 { [*] --> y=5 : <font color=#32CD32>new</font> y=5 --> x=5 x=5 --> [*] }
let x = 5;
let y = x;

复杂数据类型

Alt text
s2只复制了s1在stack上的指针信息,并没有复制heap上数据。
并且上s1废弃,当s1离开作用域时不需要释放任何东西。
如果s1没有被废弃,那么当s1,s2离开作用域时,会释放两次heap内容,内存不安全。

浅拷贝和深拷贝,Rus复制了指针、长度、容量视为浅拷贝,但是会把原先的变量失效了,所以叫做移动

stateDiagram direction LR Stack栈 Heap堆 s1变量 --> hello字符串 : <font color=FF0000>1.失去所有权</font> s2变量 --> hello字符串 : <font color=#32CD32>2.获取所有权</font> state Stack栈{ s1变量 s2变量 } state Heap堆{ hello字符串 }
fn main() {
    let s1 = String::from("hello");
    let s2 = s1;  // 所有权发生了转移,s1不再有效
    // println!("s1 = {}", s1) // 如果接着打印会报错
    println!("s2 = {}", s2);
} 

克隆

2s把s1的heap上的数据clone了一份

stateDiagram-v2 direction LR Stack栈 Heap堆 s1变量 --> hello字符串 s2变量 --> hello字符串' : <font color=#32CD32>复制s1在heap的内容</font> state Stack栈{ s1变量 s2变量 } state Heap堆{ hello字符串 hello字符串' }
fn main() {
    let s1 = String::from("hello");
    let s2 = s1.clone();
    println!("s1 = {}", s1);
    println!("s2 = {}", s2);
}

拷贝

1.Copy trait通常用于像整型等存储在栈上的类型。
2.如果一个类型实现了Copy trait,在赋值给其他变量后,旧的变量仍然可用,即发生按位复制而不是所有权转移。
3.一个类型实现了Drop trait, 那么不允许实现Copy trait,因为这类类型在值离开作用域时需要特殊处理。

以下类型实现了 Copy trait

  • 所有整数类型,比如 u32。
  • 布尔类型,bool,它的值是 true 和 false。
  • 所有浮点数类型,比如 f64。
  • 字符类型,char。
  • 元组,当且仅当其包含的类型也都实现 Copy 的时候。比如,(i32, i32) 实现了 Copy,但 (i32, String) 就没有。
fn main() {
    let x = 5;
    let y = x;
    println!("x = {}, y = {}", x, y);
} 

所有权与函数

fn main() {
    let s = String::from("hello"); // s 进入作用域
  
    takes_ownership(s); // s 的值移动到函数里 ...
    // println!("s: {}", s); 所以到这里s就失效了,如果打印的话就报错了

    let x = 5; // x 进入作用域

    makes_copy(x); // x 应该移动函数里,
    // 但 i32 是 Copy 的,所以在后面可继续使用 x

    println!("x: {}", x);
  
} // 这里, x 先移出了作用域,然后是 s。但因为 s 的值已被移走,所以不会有特殊操作

fn takes_ownership(some_string: String) { // some_string 进入作用域
    println!("{}", some_string);
} // 这里,some_string 移出作用域并调用 `drop` 方法。占用的内存被释放

fn makes_copy(some_integer: i32) { // some_integer 进入作用域
    println!("{}", some_integer);
} // 这里,some_integer 移出作用域。不会有特殊操作

返回值与作用域

fn main() {
  let s1 = gives_ownership();         // gives_ownership 将返回值
                                      // 移给 s1

  let s2 = String::from("hello");     // s2 进入作用域

  let s3 = takes_and_gives_back(s2);  // s2 被移动到
                                      // takes_and_gives_back 中,
                                      // 它也将返回值移给 s3
} // 这里, s3 移出作用域并被丢弃。s2 也移出作用域,但已被移走,
  // 所以什么也不会发生。s1 移出作用域并被丢弃

fn gives_ownership() -> String {           // gives_ownership 将返回值移动给
                                           // 调用它的函数

  let some_string = String::from("yours"); // some_string 进入作用域

  some_string                              // 返回 some_string 并移出给调用的函数
}

// takes_and_gives_back 将传入字符串并返回该值
fn takes_and_gives_back(a_string: String) -> String { // a_string 进入作用域

  a_string  // 返回 a_string 并移出给调用的函数
}

转移返回值的所有权

在每一个函数中都获取所有权并接着返回所有权有些啰嗦。
所以Rust对此提供了一个功能,叫做引用

fn main() {
    let s1 = String::from("hello");

    let (s2, len) = calculate_length(s1);

    println!("The length of '{}' is {}.", s2, len);
}

fn calculate_length(s: String) -> (String, usize) {
    let length = s.len(); // len() 返回字符串的长度
    // 每次都需要返回所有权,麻烦
    (s, length)
}
posted @ 2024-09-25 09:56  lxd670  阅读(46)  评论(0)    收藏  举报