本来是准备发一篇非常长的 Rust 入门文章的,但是坚持了很多次,实在是受不了了。主要是边学边写完全是一直在打脸,非要等到全部学完了才行,那篇反反复复返工意义不大,不如当作笔记,学完了然后自己再重新写一篇。本篇文章主要就是来讲一讲 Rust 的内存所有权机制,这部分其实不难,但与我们平常代码的编码有很大不同。本文主要也是将学习 Rust 过程中的一些见解进行发布,内容比较单独,可以独立成篇。

所有权与 GC

Rust 所有权乍看之下似乎与其他语言格格不入,但你细看之下就会发现它实际上是另一种形式的 GC 。它依靠编译将可能出现的、实际上会影响 GC 的麻烦排除掉,并直接在语法层面去杜绝循环依赖。相比之下, Rust 不需要像 Java 、 Go 、 Python 等一样 “Stop the World” ,但代价就是,你必须放弃原来在 Java 上的那套代码习惯,不过,带来的好处是,如果你的代码能够通过编译,那么无论是 GC 、 内存还是依赖,你都可以放很多心。

什么是所有权?

Rust 的所有权,本质上是用来规定指针与指针所指向对象关系的。在 Rust 中,非基础类型实际上都是一个指针,它们的对象都存放到堆中,指针与指针指向的对象是一一对应的, Rust 的所有权规定,不允许有第二个指针指向同一个对象。在 Rust 中,我们可以使用深拷贝使得两个指针所指向的对象其值是一样的。但如果确实的需要将对象移交给另一个指针呢? Rust 称之为转移,但由于一个指针只能有一个对象,所以另一个指针此刻就失去对值的所有权,指针此刻是一个空指针。因此,从这个原理上看, Rust 是绝对不会出现依赖循环的。

那么 Rust 什么时候释放对象呢?当一个对象不再被任何任何指针所指向的时候,对象就会被释放。而指针不再指向对象的时候有两个情况,一个是移交了对象之后,另一个则是在作用域结束之后。因此,在 Rust 中,所有对象需要被释放的时间都可以被清晰地计算出来而没有其他有 GC 语言所遭遇的问题。

这么看, Rust 的所有权确实不错,不过,这么编程不难受吗?

借用

我们来看一段代码,

fn main() {
    let s = String::from("Hello, ");
    let length =  len(&s);
    println!("{}: {}", s, length);
}

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

此处,我们使用了引用。如果我们不使用引用的话,我们得这么写,

fn main() {
    let s = String::from("Hello, ");
    let (s, length) =  len(s);
    println!("{}: {}", s, length);
}

fn len(s: String) -> (String, usize) {
    let length = s.len();
    (s, length)
}

这是因为在 Rust 中传递参数也算是一种转移,但这就会给我们的编程带来不便。因此, Rust 便提出了引用。

引用,在 Rust 所有权机制中也被称为借用。在借用当中,所有权没有发生变化,值只是被借出去用了。跟赋值一样, Rust 对可变与不可变做了严格地限制, Rust 默认是不可变引用,也就是不能修改被引用值,如果想要改变被引用值必须显示声明。 Rust 规定,同一时间只能有一个可变引用,或者是多个不可变引用,程序员需要判断好引用是处在什么情况下被使用的。不过,这并不是一个很难的判断。

结语

Rust 是一门很工程的语言,它将代码上探索的最佳实践当作语法,以期在编译期杜绝潜在的烂代码。只要有足够的代码实践其实就能明白 Rust 的好处,但 Rust 的方法固然不错,对于很多初学者来说还是过于难了,因此还是推荐有一定编程经验的人学习。即使不写代码,学习思想也是很有益处的。