Rust 系列分享—Fighting with Borrow Checker

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);

}
相关推荐
程序员陆通42 分钟前
Spring Boot RESTful API开发教程
spring boot·后端·restful
无理 Java1 小时前
【技术详解】SpringMVC框架全面解析:从入门到精通(SpringMVC)
java·后端·spring·面试·mvc·框架·springmvc
cyz1410012 小时前
vue3+vite@4+ts+elementplus创建项目详解
开发语言·后端·rust
liuxin334455662 小时前
大学生就业招聘:Spring Boot系统的高效实现
spring boot·后端·mfc
向上的车轮3 小时前
ASP.NET Zero 多租户介绍
后端·asp.net·saas·多租户
yz_518 Nemo3 小时前
django的路由分发
后端·python·django
超人不怕冷3 小时前
[rust]多线程通信之通道
rust
AIRust编程之星4 小时前
Rust中的远程过程调用实现与实践
后端
Stark、4 小时前
异常处理【C++提升】(基本思想,重要概念,异常处理的函数机制、异常机制,栈解旋......你想要的全都有)
c语言·开发语言·c++·后端·异常处理
逢生博客5 小时前
Rust 语言开发 ESP32C3 并在 Wokwi 电子模拟器上运行(esp-hal 非标准库、LCD1602、I2C)
开发语言·后端·嵌入式硬件·rust