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+)
SERVo:浏览器引擎
用什么语言开发?C++?
-
数百万行C++代码
-
可以达到所需性能
- 却没有任何安全保证
为什么用Rust?
C/C++有很强的控制性、很弱的安全性
Haskell有很强的安全性、很弱的控制性
Java、Python/Ruby 介于两者之间,控制性和安全性都较弱
Rust同时拥有很强的控制性和很强的安全性
C++是一把双刃剑
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