Rust Come on
# Rust 是什么?
套用官网的一句话:一门赋予每个人构建可靠且高效软件能力的语言。
它的语法类似 c++,但是 Rust 能更高效地提供许多功能来保证 性能 和 安全,而且 Rust 还能在无需使用传统的垃圾收集系统的情况下保证内存的安全性。
# 可以做什么?
来一张官网的截图:
可以看出来能做的事情之多。。。
# 安装
可以根据官网方法安装:Rsut (opens new window),mac 可以通过 brew 安装,w10 可以通过 scoop 安装。
# Rust是怎么管理内存安全的
上文说到 Rust 能在无需使用传统的垃圾收集系统的情况下保证内存的安全性,那它是通过什么来保证的呢?
# 所有权
什么是所有权?它是 rust 独有的管理系统(可以使 rust 无需垃圾回收就可以保证内存安全)。它是在编译时完成的,所以并不会带来任何运行时的成本。
rust 默认是在 栈上(已知且固定的大小) 分配内存的,如果需要存储大小未知或者大小可能变化的数据,就要改为存储到 堆 上(会返回位置指针)。
规则:
- Rust 中的每一个值都有一个被称为其 所有者(owner)的变量
- 值有且只有一个所有者
- 当所有者(变量)离开作用域,这个值将被丢弃
查看以下代码:
fn main() {
let str1 = "heelo";
{
let str2 = "测试";
}
// str2 不能使用,因为出了作用域,由编译器完成
println!("s is {}, b is {}", str1, str2);
}
- 再看一个例子:
fn main() {
let x = Box::new(5); // 智能指针(储存在 堆上)详见 https://doc.rust-lang.org/book/ch15-01-box.html
add_one(x);
println!("{}", x);
}
fn add_one(mut num: Box<i32>) {
*num += 1; // * 解引用
}
输出:
在调用 add_one
方法时,变量 x
的所有权转移到了 num
上 (value moved here),当所有权转移时,可变性可以从不可变变成可变的(mut (opens new window)),函数完成后,num 的内存将自动释放(出了作用域,会被 drop 掉)。当 println!
再次使用已经没有所有权的变量 x
时,编译器就会报错(如上图所示)。可以尝试 add_one 返回一个 Box 数据,把所有权转移回来,更好的做法时 引入 借用。
let x = Box::new(5); // 智能指针(储存在 堆上)详见: https://doc.rust-lang.org/book/ch15-01-box.html
let x = add_one(x); // 隐藏(Shadowing)详见:https://kaisery.gitbooks.io/trpl-zh-cn/content/ch03-01-variables-and-mutability.html
println!("{}", x);
fn add_one(mut num: Box<i32>) -> Box<i32> {
*num += 1;
num
}
# 引入 与 借用
- 引用:通过
&
使用; - 借用:通过
*
使用;
fn main() {
let mut vec0 = Vec::new();
vec0.push("2");
let vec1 = fill_vec(vec0);
println!("{} has leangth {}", vec0, vec1)
}
fn get_len(vec: Vec<&str>) -> usize {
vec.len()
}
调用 get_len
,传入变量 vec0
,这个时候如果直接传入,运行后会报如下错误:
会告诉我们详细发生了什么(被移动了),就是说在 调用函数结束后 会释放内存,因为在 rust 中 vec0
被移动到了 vec
中,vec0
无效了 (所有权 (opens new window)),还记得之前 我们提到 离开 作用域 被丢弃了,函数完成 vec
就被清理了,所以 什么都没有了...
改动一下 代码如下:
...
let vec1 = fill_vec(vec0);
fn get_len(vec: &Vec<&str>) -> usize {
vec.len()
}
...
// 输出:["2"] has leangth 1
借用:就是把 所有权 借用给了 vec
,函数完成后,vec 又把 所有权还给了 vec0
(这里的引用时不变引用,就是可以 读取 不能 修改,对应的 又 可变引用 &mut
),所以运行成功了。
特别注意:
- 在任意给定时间,要么 只能有一个可变引用,要么 只能有多个不可变引用。
- 引用必须总是有效的。
# 生命周期:生命周期标记
rust 中生命周期基本跟其他语言类似:声明一个变量,生命周期开始,变量离开作用域,生命周期结束。
查看如下代码:
{
let r;
{
let x = 5;
r = &x;
}
println!("r: {}", r);
}
这段代码会编译失败,因为引用了一个 无效引用,变量 x
离开作用于了。
通过 rust 借用检查器(它比较作用域来确保所有的借用都是有效的),示例如下:
{
let r; // ---------+-- 'a
// |
{ // |
let x = 5; // -+-- 'b |
r = &x; // | |
} // -+ |
// |
println!("r: {}", r); // |
}
可以看到 'b
没有 'a
看起来长,就是代表 'b
的生命周期比 'a
的要小:被引用的对象比它的引用者存在的时间更短(悬垂引用)。
更改后代码如下:
{
let x = 5; // ----------+-- 'b
// |
let r = &x; // --+-- 'a |
// | |
println!("r: {}", r); // | |
// --+ |
}
rust中生命周期重要的部分是 生命周期标记 : 通过 '
撇号开头,名称基本都是小写:
&i32 // 引用
&'a i32 // 带有显式生命周期的引用
&'a mut i32 // 带有显式生命周期的可变引用
我们可以声明一个带有 生命周期标记的 结构体:
#[derive(Debug)]
struct Test<'c, 'd> {
x: &'c String,
y: &'d String,
}
fn main() {
let a = &String::from("hello");
let b = &String::from("world");
let test = Test { x: a, y: b };
println!("Test is {:?}", test);
}
当前结构体 Test
包含两个对 String 的引用,所以需要声明当前域生命周期注解,因为当前结构体有自己的 生存期,需要保证 引用 比当前 结构体 有更长的生命周期,否则就会编译失败,这样就会避免 悬垂引用 的问题。
再看一个例子(函数签名中的生命周期注解):
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
fn main() {
let a = "hello";
let result;
{
let b = String::from("world");
result = longest(a, b.as_str());
}
println!("The longest string is {}", result);
}
可以看到报错信息:b does not live long enough
,这是因为 result
生命周期 跟 main
一样长,但是 b
只有在花括号内才是有效的,但是出了花括号作用域就会被释放,所有就无效了。然后根据 longest
函数签名生命周期注解中表示 参数 x
、y
的生命周期必须和返回值的 生命周期一样,所以 rust 在编译过程通过 借用检查器 判断出 b
的 生命周期不够长,所以就会报:b does not live long enough
。
修改代码如下:
let b;
{
...
}
...
这样就保证:b
的生命周期和 a
的一样,就可以同归编译输出:The longest string is world
。
所以 ”显示的指定” (Rust的设计哲学之一) 方便了编译器检查,但是对于 程序猿 有点 繁琐,但是为了安全考虑,我们就欣然接受吧~