rust-所有权

2023-10-20 11:06:57 浏览数 (1)

所有权

所有权(系统)是 Rust 最为与众不同的特性。 它让 Rust 无需垃圾回收(garbage collector)即可保障内存安全。 所有权以及相关功能:借用(borrowing)、slice 以及 Rust 如何在内存中布局数据。

所有程序都必须管理其运行时使用计算机内存的方式。 一些语言中具有垃圾回收机制,如: java、python; 在另一些语言中,程序员必须亲自分配和释放内存,如:C/C ;

Rust 则选择了第三种方式:通过所有权系统管理内存,编译器在编译时会根据一系列的规则进行检查。

内存与分配

Rust 的所有权围绕着内存分配进行,Rust 对内存管理通过其所有权展开。

它是一种 后进先出 的机制,类似我们日常的落盘子,只能一个一个向上方,然后从最上面拿一个盘子。 一个变量要放到栈上,那么它的大小在编译时就要明确。i32 类型的变量,它就占用 4 个字节。Rust 中可以放到栈上的数据类型,他们的大小都是固定的。 如果是字符串,在运行时才会赋值的变量,在编译期的时候大小是未知或不确定的。所以字符串类型存储在堆上

用于编译时大小未知或不确定的,只有运行时才能确定的数据。在堆上存储一些动态类型的数据。堆是不受系统管理的,是用户自己管理的,也增加了内存溢出的风险。

1.所有权规则

记住这三句话,整个所有权就是围绕这三句话,这三句话也直接概括了所有权。

  1. Rust 中的每一个值都有一个所有者(owner)
  2. 值在任一时刻有且只有一个所有者。
  3. 当所有者(变量)离开作用域,这个值将被丢弃。

2.变量作用域

大部份编程语言都有 作用域(scope) 的概念,但是在rust中,这个概念被提到一个很重要的高度。 先看看rust一些变量的 作用域(scope)。 作用域是一个项(item)在程序中有效的范围。 下面这个例子,重点关注变量:let s = "hello"

代码语言:javascript复制
fn main() {
    {                      // s 在这里无效, 它尚未声明
        let s = "hello";   // 从此处起,s 是有效的

        // 使用 s
    }                      // 此作用域已结束,s 不再有效
}

s 进入作用域时,它就是有效的。 这一直持续到它 离开作用域 为止。

s 离开作用域的时候。当变量离开作用域,Rust 为我们调用一个特殊的函数。这个函数叫做 drop,在这里 String 的作者可以放置释放内存的代码。 Rust 在结尾的 }自动调用 drop

Rust 采取了一个不同的策略:内存在拥有它的变量离开作用域后就被自动释放。 核心就一句话,变量出了作用域,rust帮你释放了!!

3.移动

在 rust 当中一个变量指向另一个变量,并不是地址或引用的copy,而是称之为:移动。 当 s2=s1 时,引用s1被移动到s2上,这和其它编程语言完全不同!! 下面这段代码,在其它编程语言上指针s1指向了指针s1s1仍然有效,在rust当中,s1无效已经无效。 在rust中,这个操作被称为 移动(move),而不是叫做浅拷贝。

代码语言:javascript复制
fn main() {
    let s1 = String::from("hello");
    let s2 = s1;

    println!("{}, world!", s1);
}

运行结果:

代码语言:javascript复制
error[E0382]: borrow of moved value: `s1`
 --> src/main.rs:5:28
  |
2 |     let s1 = String::from("hello");
  |         -- move occurs because `s1` has type `String`, which does not implement the `Copy` trait
3 |     let s2 = s1;
  |              -- value moved here
4 |
5 |     println!("{}, world!", s1);   
  |                            ^^ value borrowed here after move
  |

为什么要这么设计? 为了防止二次释放。 当 s2s1 离开作用域,他们都会尝试释放相同的内存。 这是一个叫做 二次释放(double free)的错误,也是之前提到过的内存安全性 bug 之一。 两次释放(相同)内存会导致内存污染,它可能会导致潜在的安全漏洞。

如何保证,即要、也要

即要把s2=s1,也可保持s1可用,那就显示拷贝。

4.引用和借用 reference & borrowing

借用(borrowing)

借用就是字面意思,借来的数据,你并不拥有它。 看个例子:

代码语言:javascript复制
fn main() {
    let s1 = String::from("hello");
    // len 借用 s1
    let len = calculate_length(&s1);

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

fn calculate_length(s: &String) -> usize {
    s.len()
}

s1 移进了 calculate_length,但是所有权并没有转移,这里只是借用了s1。 也就是说:指向 值 s1 的引用,但是并不拥有它。 因为并不拥有这个值,所以当引用停止使用时,它所指向的值也不会被丢弃。

借用默认不允许修改值

这个是反例,当去改变一个借用的数据时,就会报错。

代码语言:javascript复制
fn main() {
    let s = String::from("hello");

    change(&s);
}

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

0 人点赞