Skip to the content.

从C++的RAII到Rust的所有权(一)

Contact me


如果你有C++的经验就知道,在C/C++里面变量之间默认都是复制的,从C语言的函数传参谈指针说的就是C中的变量复制,C++中如果你没有让构造函数做移动或其他特殊操作,那么变量也是复制的,比如:

Class C { /* sth. normal*/ };

C c1;
C c2 = c1;

c2是把c1复制了一份,如果是复制比较廉价还好,如果对象比较大的话,复制就比较昂贵,那么我们一般的做法是用指针,指针的复制很廉价,当然考虑到实际环境,你可能要考虑使用现代C++的智能指针,这就有好几种选择了,从C++的RAII理解智能指针的思路(一)从C++的RAII理解智能指针的思路(二)从RAII的角度做了智能指针的基本解释。当然如果要和老代码打交道,还需要考虑普通指针和智能指针的共存。而且使用指针必须注意指针臭名昭著的问题,例如野指针,内存释放,内存泄漏一系列头疼的问题。

对了,还有移动构造可以考虑,移动可以省去拷贝。

假设我们抛开C++的历史包袱,现在只存在两个对象传递的方法,一个是复制,一个是移动[引用],那么问题就清楚了,对于分配在栈上的变量,例如int,char啥的我们可以复制,复制很廉价,对于分配在堆上的对象,我们可以移动,只需要保留好移动对象的引用即可,因此这里把移动和引用当做一个方案。

// 我们的新方案

int a = 0;
int b = a;  // 对分配在栈上的类型拷贝,拷贝很廉价

Class C { /* sth. */ };

C *c1 = new C();       // 分配在堆上
C *c2 = c1;  // 对分配在堆上的类型,采用移动

/*注意,移动以后,c1的内容就没了,不能再对c1访问,c2掌握了所有权,现在只有一个C类型的对象*/

// 离开作用域自动释放资源

有点像使用了C++的unique_ptr,不过简化了写法。这样有什么好处呢?不会出现两个指针访问同一个资源的情况,离开作用域自动释放资源,我们就不用操心资源释放的问题了,没有野指针,不会出现指针悬挂,就像unique_ptr做的一样。似乎只是个语法糖,我们再加一点就和基本的Rust所有权差不多了,那就是每个资源只有一个拥有所有权的引用

那我们正式进入Rust(这里我们暂时不考虑Rust的智能指针):

let x = 0;
let y = x;      // 第一种方式,拷贝

let s1 = String::from("hello");
let s2 = s1;    // 第二种方式,移动

看起来这个逻辑很简单,但是马上我们会遇到一个问题,既然对于堆上的对象是移动,那么函数传递参数怎么办?

fn main() {
    let s = String::from("hello");  
    takes_ownership(s);
}

fn takes_ownership(some_string: String) {
    println!("{}", some_string);
}

我们不用管语法细节,代码很容易看懂,这里s传入函数,意味着s就废了,怎么办?如果你需要复制可以使用克隆:

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

但是这不是我们要做的,我们希望函数做了操作,我们依旧可以对s继续操作,因此这里就需要引入所有权借用:

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

fn takes_ownership(some_string: &String) {
    println!("{}", some_string);
}

但是我们还没有说一个特性,就是默认情况下变量是不可以变的,所以应该称为常量:

fn main() {
    let x = 5;
    x = 6;
}

这样做是报错的,你需要加入mut:

fn main() {
    let mut x = 5;
    x = 6;
}

同样,函数是这样写的:

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

    change(&mut s);
}

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

不可变有什么好处呢,不可变是安全的,不可变不存在不确定,尤其是并发等情况下,如纯函数式语言就是不可变的。需要时我们让变量可变,控制变化因素,有助于减少潜在的问题。

还要一些问题需要讨论,下一篇文章见。