Rust 所有权和借用

Rust 所有权和使用

所有程序都需要和内存打交道,有三种流派

  • 垃圾回收机制(gc),程序运行时不断寻找不在使用的内存,如 Java,go
  • 手动管理内存的分配释放,如 C++
  • 通过所有权来关系内存,编译器在编译的时候进行检查

所有权

不同于 go 语言的 gc,Rust 使用第三种 所有权系统,只在编译时检查,不在运行期检查,不会有性能损失。

栈和堆

栈和堆都是为了给程序提供可以使用的内存空间。


栈中所有的数据都是固定大小的内存空间。


与栈相对,当存储大小未知或可能变化的数据,我们需要将它存储在堆上。

当向堆上放入数据时,需要请求一定大小的内存空间。操作系统在堆的某处找到一块足够大的空位,把它标记为已使用,并返回一个表示该位置地址的指针, 该过程被称为在堆上分配内存。
接着,该指针会被推入栈中,因为指针的大小是已知且固定的,在后续使用过程中,你将通过栈中的指针,来获取数据在堆上的实际内存位置,进而访问该数据。

基于栈只是放新数据到栈顶,而堆需要找足够的内存空间;而且栈数据通常可以直接存储到 cpu 缓存,而堆只能在内存中;处理器处理和分配在栈上数据会比在堆上的数据更加高效。

所有权和堆栈
当调用函数时,传给函数的参数依次被压入栈中,当函数调用结束时,这些值将被从栈中按照相反的顺序依次移除。
因为堆上的数据缺乏组织,因此跟踪这些数据何时分配和释放是非常重要的,否则堆上的数据将产生内存泄漏。这些数据将永远无法被回收。这就是 Rust 所有权系统为我们提供的强大保障。

所有权原则

  1. Rust 中每一个值都被一个变量所拥有,该变量被称之为值的所有者
  2. 一个值同时只能被一个变量所拥有,或者说一个值只能拥有一个所有者
  3. 当所有者(变量)离开作用域范围时,这个值将被丢弃 (drop)

变量绑定背后的数据交互

let x = 5;
let y = x;

将 5 绑定到变量 x;接着拷贝 x 的值赋给 y,最终 x 和 y 都等于 5,因为整数是 Rust 基本数据类型,是固定大小的简单值,因此这两个值都是通过自动拷贝的方式来赋值的,都被存在栈中,完全无需在堆上分配内存

let s1 = String::from("hello");
let s2 = s1;

String 不是基本类型,存储在堆上,不能自动拷贝
String 是复杂类型,由存储在栈中的堆指针、字符串长度、字符串容量共同组成。
对于 let s2 =s1 有两种情况

  • 对数据进行深拷贝,无论是 String 本身还是底层的堆上数据,都会被全部拷贝,性能问题大
  • 只拷贝 String 本身,这样很快,但是两个 String 都指向堆上该数据,数据由两个所有者,当离开作用域时,会出现重复二次问题。

因此rust 中,当 s1 赋予 s2 后,Rust 认为 s1 不再有效,因此也无需在 s1 离开作用域后 drop 任何东西,这就是把所有权从 s1 转移给了 s2,s1 在被赋予 s2 后就马上失效了;在转移所有权后,s1 也就不能再被使用了。

即如果操作或访问 s1 则会 value used here after move

克隆
首先,Rust 永远也不会自动创建数据的 “深拷贝”。因此,任何自动的复制都不是深拷贝,可以被认为对运行时性能影响较小。

如果我们确实需要深度复制 String 中堆上的数据,可以使用clone

let s1 = String::from("hello");
let s2 = s1.clone();

println!("s1 = {}, s2 = {}", s1, s2);

拷贝
浅拷贝只发生在栈上,因此性能很高

let x = 5;
let y = x;

println!("x = {}, y = {}", x, y);

函数传值与返回

将值传递给函数,一样会发生 移动 或者 复制,就跟 let 语句一样

引用和借用

仅仅支持通过转移所有权的方式获取一个值,那会让程序变得复杂,Rust 也可以像其它编程语言一样,使用某个变量的指针或者引用。
Rust 通过 借用(Borrowing) 这个概念来达成上述的目的,获取变量的引用,称之为借用。

引用与解引用

常规引用是一个指针类型,指向了对象存储的内存地址。
和其他语言一样,引用 &,解引用 *

不可变引用

引用允许你使用值,但是不获取所有权。

fn main() {
    let s1 = String::from("hello");
    let len = calculate_length(&s1);   // 传引用
    println!("The length of '{}' is {}.", s1, len); // s1 还可以使用
}

fn calculate_length(s: &String) -> usize {        // 类型为 &String
    s.len()
}

通过 &s1 语法,我们创建了一个指向 s1 的引用,但是并不拥有它。因为并不拥有这个值,当引用离开作用域后,其指向的值也不会被丢弃。
就如变量不可变,引用指向的值默认也是不可变的。

fn main() {
    let s = String::from("hello");                // String 不可变
    change(&s);
}

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

可变引用

首先,声明 s 是可变类型,其次创建一个可变的引用 &mut s 和接受可变引用参数 some_string: &mut String 的函数。

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

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

可变引用同时只能存在一个,同一作用域,特定数据只能有一个可变引用;为了使 Rust 在编译期就避免数据竞争,再检查时,检查到最后一次使用

可变引用与不可变引用不能同时存在,为了防止不可变的引用被需要了内容

悬垂引用

悬垂引用也叫做悬垂指针,意思为指针指向某个值后,这个值被释放掉了,而指针仍然存在,其指向的内存可能不存在任何值或已被其它变量重新使用。在 Rust 中编译器可以确保引用永远也不会变成悬垂状态:当你拥有一些数据的引用,编译器可以确保数据不会在其引用之前被释放,要想释放数据,必须先停止其引用的使用。

fn main() {
    let reference_to_nothing = dangle();
}

fn dangle() -> &String {
    let s = String::from("hello");

    &s                      // 此处 s 离开作用域,内存被释放。
}

解决方法是直接返回 String:

fn no_dangle() -> String {
    let s = String::from("hello");

    s               //String 的 所有权被转移给外面的调用者
}

借用规则如下:

  • 同一时刻,你只能拥有要么一个可变引用, 要么任意多个不可变引用
  • 引用必须总是有效的
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值