Rust语言

内存所有权

和生命周期

作者:Nicholas Matsakis
linux.conf.au 2014

译者:Liigo (根据现场视频整理)

RUST是开源项目


我们很荣幸拥有一个活跃而富有激情的社区,并从中受益。


译者Liigo注:
https://github.com/mozilla/rust/
https://github.com/mozilla/rust/wiki
https://mail.mozilla.org/listinfo/rust-dev
http://chat.mibbit.com/?server=irc.mozilla.org&channel=%23rust
http://reddit.com/r/rust
https://plus.google.com/communities/100629002107624231185/   (Rust中文圈@Google+)

Mozilla


使命:发展并守护开放的互联网

SERVo:浏览器引擎


用什么语言开发?C++?

  • 数百万行C++代码
  • 可以达到所需性能
  • 却没有任何安全保证

为什么用Rust?


C/C++有很强的控制性、很弱的安全性
Haskell有很强的安全性、很弱的控制性
Java、Python/Ruby 介于两者之间,控制性和安全性都较弱

Rust同时拥有很强的控制性和很强的安全性


C++是一把双刃剑


{
map<int, Record> m;
m[i] = ...
Record &r = m[i];
return r;
}

m是在栈上创建的,函数返回后被销毁
而函数返回值 r 却引用了m内的成员
此后必然导致悬空指针的错误
(后面的配图形象地解释了上述结果,略)

十分形象的网络漫画

(配图:两个动物拿砖砌墙,相互争吵拆台,最后打在一起,墙也倒塌了。用以形容C++开发过程的混乱。)

C++有许多未被严格执行的安全规则:

  • 所有申请的内存都必须释放,且只能释放一次
  • 避免悬空指针和(多线程下的)数据竞争(data race)
  • 等等

常规解决方案


部署垃圾回收器(GC):

  • 有运行时开销、不可预测的停顿
  • 很难与C语言 和/或 其他虚拟机集成

Rust的解决方案


  • 编译器负责检查执行安全规则
  • 不要求有运行时(Runtime)


信任值得信任的:
Rust从C、C++、Cyclone、Haskell、Erlang借鉴了许多。
Rust支持高效模式
  • “指向栈和数据结构”的指针
  • “拥有共享内存”的多线程

阻止糟糕的Bug产生:
  • 没有悬空指针,没有内存泄漏
  • 没有数据竞争(data race)

可选的运行时(runtime)(运行时不是必须的):
  • 适用于编写操作系统内核、嵌入式代码
  • 易于跟其他语言和项目集成

本次演讲内容


  • “没有崩溃”的指针
  • “没有数据竞争”的多线程
  • 例外(Breaking the rules)

未涉及的内容


  • 模式匹配 (pattern matching)
  • 特征(Trait)(类型分类)
  • 可选的垃圾回收器(GC)
  • ……

“没有崩溃”的指针


(配图提到了多种编程语言的内存管理,或者有很强的控制性(malloc)、很弱的安全性,或者有很弱的控制性(GC)、很高的安全性,均有至少某一方面的欠缺。)

Rust语言却通过管理内存的
所有权(ownership)和租借(borrowing)
同时做到了很强的控制性和很高的安全性

所有权(Ownership)


(一块内存,或对象)同一时刻只有一个所有者(Owner)
只有所有者具有读写该内存的权限(除非所有权被租借)
一旦所有者被释放,它拥有的内存也被自动释放

译者Liigo注:
通俗的说,所有权是指:“指针持有内存”、“变量持有对象”
在Rust语言中,如果存在一个指针变量指向对象内存首地址
同一时刻就不可能有另一个指针变量指向该对象内存首地址
这一约束由Rust编译器在编译期间给予强力保证
这意味着对象不可能在别处被修改
用C/C++的话说,Rust不可能有悬空指针(raw指针例外)

所有权的转移(transfer)


所有权可以被转移
所有权被转移给新任所有者之后
前任所有者自动失去所有权
Move语义


译者Liigo注:
失去所有权 之后,就不可能访问那块内存了,
从而杜绝了悬空指针产生的可能性

所有权的租借(borrowing)

临时引用
租借不改变内存的所有者
在租借期内,租借者可以读写这块内存
在租借期内,所有者不再被允许读写这块内存
在租借期内,内存所有者保证不会释放/转移/租借这块内存
租借期满后,内存所有者收回读写权限
不允许租借者把内存引用带到租借期以外
(租借期通常就是租借者变量的生命周期?)

译者Liigo注:以上种种约束的实现机制
是Rust类型安全系统和内存安全系统的一部分
是由Rust编译器在编译期间给予静态保证的

小结


所有权(Ownership):
  • 同一时刻只能有一个所有者
  • 所有者可以释放(free)和移动(move)内存
  • 一旦被moved,原所有者就不能读写了

租借(Borrowing):
  • 引用(租借)不得超出所有者的生存周期
  • 租借期内,内存不能被转移(transferred)

译者Liigo:术语move 和 transfer 的区别?

没有数据竞争的多线程


数据竞争:
多个线程同时读写同一块内存(shared memory)

通常,多线程:
可控性越强(共享内存),安全性越低
可控性越弱(采用actors,没有共享内存),安全性越高

而Rust只采用安全的机制在多个线程间共享数据
(sharing only via safe mechanisms)
(可控性强,安全性高)

Rust里的多线程


默认情况下,没有共享内存
通过消息传递(Messaging)转移数据
(Messaging allows data to be transferred)

        启动一个线程:
    let m = HashMap::new();
    spawn(proc {
        use(m); // m被移动(move)到子线程
    });

译者Liigo注:这里隐含了内存所有权的转移

消息传递:Channel & Port


let (port, chan) = Chan::new();

chan.send(data);
let data = port.recv();
通过管道传递消息(内存所有权转移)

译者Liigo注:
通过转移内存所有权传递消息
避免了内存共享和内存拷贝
既安全,又高效

不可变的共享内存


共享的内存必须是不可变的(immutable)或者锁保护的

把要共享的数据放到ARC对象里:
ARC是该数据的所有者
ARC只提供“不可变引用”的接口(保证数据不被修改)

译者Liigo注:
std::sync::Arc 是标准库内定义的数据结构
An atomically reference counted wrapper for shared state.

小结


默认没有共享内存
创建线程和传递消息时,转移内存

以库的形式提供对共享内存的支持
但仅限于只读的共享内存
或者锁保护的共享内存

例外(breaking the rules)


unsafe {
// ...
}                

允许编写不安全代码块
要求编译器信任程序员
程序员获得最大的控制权,同时也丧失了一定的安全性

应尽量避免编写unsafe代码
除非追求性能,或实现特殊需求(tasks,arc)

小结


Rust坚持强制的安全约束,而不需要强制性的runtime
通过所有权和租借,达成内存安全
通过proc、消息传递、ARC,达成多线程安全
通过unsafe代码,打开应急舱口


谢谢您




(Rust Logo)



rust-lang.org
irc.mozilla.org #rust
reddit.com/r/rust