The Borrow Checker
- 对于 Rust developer 可以做的工作就是 fighting with The borrow Checker
- 所以对于 Rust 工程师更好理解 The borrow checker 工作原理,帮助你将 borrow checker 从对头变成朋友
我们知道 Rust 提供内存管理不同于之前的垃圾回收机制(例如 Java)和手动分配和释放内存方式(例如 c++) 提出全新的方式也就是 ownership 和 borrow 机制
概要
- 简单介绍 Rust Compiler
- 深入了解 borrow checker
我们知道 Rust 提供内存管理不同于之前的垃圾回收机制(例如 Java)和手动分配和释放内存方式(例如 c++) 提出全新的方式也就是 ownership 和 borrow 机制
概要
-
简单介绍 Rust Compiler
-
深入了解 borrow checker
接下通过简单例子,borrow 机制
rust
fn print_out_item(item:Vec<u32>){ for i in item{ println!("{}",i); }}fn main(){ let item = vec![1,2,3];
print_out_item(item); print_out_item(item);
}
这里因为在 item 作为参数传入到函数 print_out_item 时候已经将所有权交给了函数参数 item。当 print_out_item 函数结束, item drop 时候就释放掉这块内存了。** **
rust
fn print_out_item(item:&Vec<u32>){ for i in item{ println!("{}",i); }}fn main(){ let item = vec![1,2,3];
print_out_item(&item); print_out_item(&item);
}
这里就是可以用借用 borrow 只是将 item 的引用传入到函数,而不是不会将所有权交给函数 print_out_item 来解决这个问题。
rust
#[derive(Debug)]struct Tut{}
fn main(){ let mut vector = vec![Tut{},Tut{},Tut{}]; let last_tut = vector.last();
vector.pop(); println!("last tut {:?}",last_tut);}
immutable borrow later used here
ini
let last_tut = vector.last();
immutable 借用 vector,其实这个问题也比较好理解,因为我们在mutable borrow(也就是可以修改值的借用前)使用 immutable borrow 然后再输出 immutable borrow 的引用,很明显我们需要在 mutable borrow 前使用 immutable borrow 引用,这样确保输出是想要的值而不是修改后的值,大家可以细细品味,是合情合理的。
rust
#[derive(Debug)]struct Tut{ level:usize,}
fn reverse_and_print(tuts:&mut Vec<Tut>){ tuts.reverse(); for f in tuts.iter(){ println!("{:?}",f); }}
fn main(){ let mut vector = vec![Tut{level:1},Tut{level:2},Tut{level:3}];
reverse_and_print(&mut vector); reverse_and_print(&mut vector);}
rust
fn main(){
let numbers = vec![1,2,3,5,7];
for n in numbers{
println!("{}",n);
}
}
Rust Compiler
Rust Compiler 工作流程
- lexical Analysis(词法分析)
- parsing (语法分析)
- Semantic Analysis(语法分析)
- Optimization(代码优化)
- Code Generation(代码生成)
词法分析(lexical Analysis)
- 词法分析是将 source code 转变为 token 序列
语法分析(Parsing)
- 将词法分析阶段输出 Tokens 解析为 AST(抽象语法树)
在这个阶段 Rust Compiler 会做那些工作呢
- 在源码中将 macros 展开,例如
println!
、format!
这些 macros 进行展开
rust
::std::io::_print(::core::fmt::Arguments::new_v1(
&["","\n"],
&match(&n,){
(arg0,)=>[::core::fmt::ArgumentV1::new(
arg0,
::core::fmat::Display::fmt,
)],
}
));
- 对一些语法糖进行解构生成对应的代码 例如将代码
for n in numbers{}
语法糖解构为
rust
let _result = match IntoIterator::into_iter(numbers){
mut iter=> loop {
let next;
match iter.next(){
Some(val) => next = val,
None => break,
};
println!("{}",next);
}
};
- 将引入代码进行解析
生成 HIR
这个阶段是将 AST 转换为 HIR(Higher-level Intermediate Representation)
- Node 对应一段特定代码,在 HIR 用 Hirld 标识符来表示
- node 属于 Definition,Detifition 又是 Crate 中的内容
- crate 是 Rust 的编译单元,Defintion 就是 crate 的中内容
json
Arm {
hir_id: HirId{
owner:DefId(0:3 ~ sample_code_1[24c3]::main[0]),
local_id:107
},
span:src/main.rs:4:5: 6:6
}
- Arm: 表示 match 表达式的的目的
- Node
- span: 对语法进行解构后如果代码出现问题,可以返回定位到源码以提示用户 Lowering to MIR(Mid-level Intermediate Representation)
- control Flow Graph: 例如合并执行语句等工作
rust
bb2: {
_5 = const<std::vec::Vec<i32> as std::iter::IntoIterator::into_iter(move _6)
span: src/main.rs4:14: 4:21
}
- Local 栈的局部内存
语义分析(Semantic Analysis)
在语义分析,会根据文法规则来检测,例如数组的下标是否为整数,函数调用方式是否正确,我们的主角 borrow checker 也是位于这里 borrow checker 会根据了解的一些 borrow 规则来检查例如 要修改 reference 数据类型是否为 mut
优化和生成代码
LVMM 是模块化可以复用的编译器和工具链,MIR LLVM IR
css
bb2:
; preds
rust
fn main(){
let numbers = vec![1,2,3,5,7];
let _result = match IntoIterator::into_iter(numbers){
mut iter=> loop {
let next;
match iter.next(){
Some(val) => next = val,
None => break,
};
println!("{}",next);
}
};
}
Borrow Checker
rust
fn main(){
let x:String;
x = String::from("hello world");
let y = x;
println!("{}",x);
println!("{}",y);
}
对 ownership 和 borrow 规则有所了解,不难看出上面代码在运行时候会出现问题,因为 let y = x 时候 x 已经不再持有数据,也就是具有数据的所有权了
接下我们就来看一看 borrow checker 是如何发现问题的
那么 borrow checker 主要做的几件事
- 跟踪初始化和所有权转移(move)
rust
let x: String;//这里只是变量的声明,并没有对变量进行初始化
这里只是声明了类型 String 类型的变量,并没有赋值,看一看对应 MIR 代码这里只是定义 local _1
c
debug x => _1;
let _1:std::string::String
下面语句对 x 进行初始化,也就是对变量进行赋值这才是真正的初始化
rust
x = String::from("hello world");
rust
_2 = const <String as From<&str>>::from(const "hello world")
_1 = move _2;
这里用 local _2 保存了一个 String ,然后将 _2 值移动给 local _1 注意这里 local _1 对应 x 变量。
ini
let y = x;
这里为变量 y 创建了 local _3 然后指定类型,接下来将 _1 的值移动到 _3
ini
debug y => _3;
let _3: &std::string::String;
_3 = move _1;
arduino
println!("{}",x);
css
main.rs:6:19
19 表示 MIR 位置,6 表示 source code ,borrower 还会提示出详细信息。rustc --explain E0382 给出详细信息,还有给出修改方案
css
rustc --explain E0382
变量的lifetime
- 在变量的释放前的在时间上跨度
- lifetime 定义作用域
rust
x = String::from("hello world");
ini
let y = x;
在将对于资源所有权交给 y 后 x 的 lifetime 就结束了
ini
let y = &x;
ini
debug x => _1;
debug y => _3;
_3 = &_1;
rust
fn main(){
let x:String;
x = String::from("hello world");
let y = &x;
drop(x);
println!("{}",x);
println!("{}",y);
}