4.Rust中的所有权(Rust成名绝技)

Rust成名绝技

Rust 之所以能成为万众瞩目的语言,就是因为其内存安全性。在以往,内存安全几乎都是通过 GC 的方式实现,但是 GC 会引来性能、内存占用以及全停顿等问题,在高性能场景、实时性要求高和系统编程上是不可接受的,因此 Rust 采用了所有权系统。这也是Rust核心、如果不理解所有权Rust就无法深入学习下去。

内存管理方案

所有的程序都必须和计算机内存打交道,如何从内存中申请空间来存放程序的运行内容,如何在不需要的时候释放这些空间,成了重中之重,也是所有编程语言设计的难点之一。

手动管理

所有程序都需要和计算机内存打交道,学过C语言的都知道,C语言中我们需要手动管理内存空间。

int main() {
    int size;
    int *array;
    printf("Enter the size of the array: ");
    scanf("%d", &size);
    // 动态分配内存
    array = (int *)malloc(size * sizeof(int));
    if (array == NULL) {
        printf("Memory allocation failed.\n");
        return 1;
    }
    // 释放内存
    free(array);
    return 0;
}

手动管理内存会有什么问题呢?

  • 内存泄漏:如果你分配了内存但没有显式释放,就会导致内存泄漏。内存泄漏会逐渐消耗可用内存,最终可能导致程序崩溃或运行缓慢。
  • 悬空指针:如果你释放了一块内存,但后续仍然使用指向该内存的指针,就会导致悬挂指针。使用悬挂指针可能会导致未定义的行为,包括访问无效内存或者覆盖其他数据。
  • 内存访问错误:手动分配内存时,需要确保不会越界访问分配的内存块。如果越界访问数组或者在访问已释放的内存,可能会导致程序崩溃或产生不可预测的结果。
  • 多次释放:如果你多次释放同一块内存,会导致内存错误,可能会导致程序崩溃或者破坏其他数据。
  • 内存碎片:频繁地分配和释放内存可能会导致内存碎片化,即内存空间被分割成多个小块,无法有效利用。这可能会降低程序的性能。

GC(garbage collection)垃圾回收

采用GC的代表性语言:Java、Go、Js。
这里以JS为例可以看我的这篇文章

通过所有权来管理内存

代表语言Rust,这种方式,利用编译器在编译时进行规则检查,不会造成运行时的性能损耗,解下来我们详细介绍所有权的概念。

栈和堆是编程语言最核心的数据结构,但是在很多语言中,你并不需要深入了解栈与堆。 但对于 Rust 这样的系统编程语言,值是位于栈上还是堆上非常重要, 因为这会影响程序的行为和性能。

栈和堆的核心目标就是为程序在运行时提供可供使用的内存空间。

栈按照顺序存储值并以相反顺序取出值,这也被称作后进先出。想象一下一叠盘子:当增加更多盘子时,把它们放在盘子堆的顶部,当需要盘子时,再从顶部拿走。不能从中间也不能从底部增加或拿走盘子!

增加数据叫做进栈,移出数据则叫做出栈。

因为上述的实现方式,栈中的所有数据都必须占用已知且固定大小的内存空间,假设数据大小是未知的,那么在取出数据时,你将无法取到你想要的数据。

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

当向堆上放入数据时,需要请求一定大小的内存空间。操作系统在堆的某处找到一块足够大的空位,把它标记为已使用,并返回一个表示该位置地址的指针, 该过程被称为在堆上分配内存,有时简称为 “分配”(allocating)。

接着,该指针会被推入栈中,因为指针的大小是已知且固定的,在后续使用过程中,你将通过栈中的指针,来获取数据在堆上的实际内存位置,进而访问该数据。

由上可知,堆是一种缺乏组织的数据结构。想象一下去餐馆就座吃饭: 进入餐馆,告知服务员有几个人,然后服务员找到一个够大的空桌子(堆上分配的内存空间)并领你们过去。如果有人来迟了,他们也可以通过桌号(栈上的指针)来找到你们坐在哪。

性能区别

在栈上分配内存比在堆上分配内存要快,因为入栈时操作系统无需进行函数调用(或更慢的系统调用)来分配新的空间,只需要将新数据放入栈顶即可。相比之下,在堆上分配内存则需要更多的工作,这是因为操作系统必须首先找到一块足够存放数据的内存空间,接着做一些记录为下一次分配做准备,如果当前进程分配的内存页不足时,还需要进行系统调用来申请更多内存。 因此,处理器在栈上分配数据会比在堆上分配数据更加高效。

所有权与堆栈

当你的代码调用一个函数时,传递给函数的参数(包括可能指向堆上数据的指针和函数的局部变量)依次被压入栈中,当函数调用结束时,这些值将被从栈中按照相反的顺序依次移除。

因为堆上的数据缺乏组织,因此跟踪这些数据何时分配和释放是非常重要的,否则堆上的数据将产生内存泄漏 —— 这些数据将永远无法被回收。这就是 Rust 所有权系统为我们提供的强大保障。

对于其他很多编程语言,你确实无需理解堆栈的原理,但是在 Rust 中,明白堆栈的原理,对于我们理解所有权的工作原理会有很大的帮助。

所有权

所有权规则:

  1. 每一个值同时只能被一个变量拥有。(值就像钞票,只能被一个人持有,毕竟钞票带编号没有相同的)
  2. 当所有者离开作用域范围,该值将被丢弃。
fn main(){
	let x = 1; // 1的所有权被x持有,x就是所有者。 
}

拷贝Copy trait

关于trait后面会介绍~。
在 Rust 中,实现了 Copy trait 的类型是可复制的。Copy trait 表示类型的值可以通过简单的位拷贝来复制,而不会发生所有权转移。当一个变量的值被复制到另一个变量时,原始变量仍然保留对其值的所有权。
以下是 Rust 标准库中一些常见的实现了 Copy trait 的类型:

  • 所有的整数类型(如 i32、u64 等)
  • 所有的浮点数类型(如 f32、f64 等)
  • bool 类型
  • 字符类型 char
  • 元组,只有当元组的所有元素都是可复制的时候才能拷贝
	let x = 1;
	let y = x;// 拷贝 x 的值到 y,x 仍然保留其所有权
	println!(x) // 1 

可能有同学会有疑问:这种拷贝不消耗性能吗?实际上,这种栈上的数据足够简单,而且拷贝非常非常快,只需要复制一个整数大小(i32,4 个字节)的内存即可,因此在这种情况下,拷贝的速度远比在堆上创建内存来得快的多。

所有权转移

当变量离开作用域后,Rust 会自动调用 drop 函数并清理变量的堆内存。不过由于两个 String 变量指向了同一位置。这就有了一个问题:当 s1 和 s2 离开作用域,它们都会尝试释放相同的内存。这是一个叫做 二次释放(double free) 的错误,也是之前提到过的内存安全性 BUG 之一。两次释放(相同)内存会导致内存污染,它可能会导致潜在的安全漏洞。
因此,Rust 这样解决问题:当 s1 被赋予 s2 后,Rust 认为 s1 不再有效,因此也无需在 s1 离开作用域后 drop 任何东西,这就是把所有权从 s1 转移给了 s2,s1 在被赋予 s2 后就马上失效了。
Rust对于实现了 Drop trait 的类型是不可复制的,因为它们可能会具有特殊的资源释放行为。当一个不可复制的类型的值被赋值给另一个变量时,所有权会被移动,而不是发生拷贝。例如:

	let s1 = String::from("hello Rust");
	let s2 = s1;
	println!("s1:{},s2:{}",s1,s2);

在这里插入图片描述
可以看到报错所有权已经转移,他没有实现Copy trait.

函数参数和返回值岁有权转移示例:

fn main() {
    let s = String::from("hello");  // s 进入作用域

    takes_ownership(s);             // s 的值移动到函数里 ...
    println!(s)                     // ... 所以到这里不再有效会报错

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

    makes_copy(x);                  // x 应该移动函数里,
                                    // 但 i32 是 Copy 的,所以在后面可继续使用 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 移出作用域。不会有特殊操作
  • 18
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大鲤余

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值