今天学习的内容是 Rust 中的所有权的概念。
所有权是 Rust 的核心特性之一。
Rust 和其他语言相比最出色的就是安全性。这个安全性就是建议在所有权之上。
常见的内存管理机制
程序运行在内存之中,所有的编程语言都有自己的一套内存管理的机制。
对于 JSer来说,由于 V8 引擎使用的是 GC 垃圾回收机制,并不需要开发者介入,所以JSer对于内存的管理的概念通常会比较陌生。
下面是两种常见的内存管理机制:
-
Malloc 和 free:采用
malloc
和free
机制的代表是 C 语言,需要开发者手动管理内存,使用时申请,不用时手动释放。 -
GC:采用 GC 的语言有
Golang
,Java
,JavaScript
等。开发者只需要申请内存拿来用,不需要手动管理。垃圾回收器会自动检测可回收的内存空间,对于那些不再被使用的内存,就会被回收和释放掉。
Rust 采用了一种介于手动管理和自动管理的中间方案,既想要手动管理的高性能,又想要自动管理的易用性。
为了管理内存,Rust 引入了生命周期和所有权的概念。
生命周期
在 C 语言中需要开发者自己手动调用 free 去释放内存。
Rust 在编译期间会计算变量的使用范围,当变量不再被使用时,编译器会自动在源码中插入 free 代码,从而释放掉内存。这就是内存的生命周期。
所有权
Rust 中任何一个值都必须绑定一个变量,称之为该值的所有者。
fn main() {
let s1 = String::from("string");
println!("{}", s1);
}
对于这段代码,有两种描述方式:
1.创建了一个变量 s1,它的值是字符串 string
2.创建了一个字符串值,将其赋值给了变量,也就是该值的所有者是变量 s1
Rust 中每一个值都必须有一个所有者。同时每个值都还有自己的作用域。当值离开作用域时,它所占用的内存就会被回收。
比如:
fn main() {
let s1 = String::from("string1");
println!("{}", s1);
{
let s2 = String::from("string2");
println!("{}", s2);
}
}
运行代码,控制它可以打印出变量 s1 和 s2 的值。
但是一旦变量 s2 离开了它所处的作用域:
fn main() {
let s1 = String::from("string1");
println!("{}", s1);
{
let s2 = String::from("string2");
}
println!("{}", s2);
}
就会报错:
error[E0425]: cannot find value
s2 in this scope
的意思是在当期作用域内找不到 s2 的值。
拿 JS 做一个对比,ES6 以前,JS 中只有全局作用域和函数作用域。
ES6 提供了新的声明变量的关键字 let 和 const,于是 JS 中也有了块级作用域的概念。
Rust 中变量的作用域,就好比 JS 中的变量作用域一样。在声明变量之前是不可访问的,在作用域内声明变量之后是可以访问的,离开作用域后访问会报错,比如:
if(true) {
console.log(a); // 报错:ReferenceError: Cannot access 'a' before initialization
let a = 'a';
console.log(a); // 'a'
}
console.log(a); // 报错:ReferenceError: a is not defined
如果对 Rust 变量作用域有所疑惑,看了 JS 的示例之后,应该就能理解了。
小结
所有权,就是每一个值都有一个对应的变量作为它的所有者。当所有者离开自己的作用域时,所占用的内存空间就会被释放。对于初学者,尤其是没有接触过内存管理的概念的人来说,还是有一定难度的。