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

}
相关推荐
机器之心2 小时前
图学习新突破:一个统一框架连接空域和频域
人工智能·后端
.生产的驴2 小时前
SpringBoot 对接第三方登录 手机号登录 手机号验证 微信小程序登录 结合Redis SaToken
java·spring boot·redis·后端·缓存·微信小程序·maven
顽疲3 小时前
springboot vue 会员收银系统 含源码 开发流程
vue.js·spring boot·后端
机器之心3 小时前
AAAI 2025|时间序列演进也是种扩散过程?基于移动自回归的时序扩散预测模型
人工智能·后端
hanglove_lucky4 小时前
本地摄像头视频流在html中打开
前端·后端·html
皓木.5 小时前
(自用)配置文件优先级、SpringBoot原理、Maven私服
java·spring boot·后端
i7i8i9com5 小时前
java 1.8+springboot文件上传+vue3+ts+antdv
java·spring boot·后端
秋意钟5 小时前
Spring框架处理时间类型格式
java·后端·spring
我叫啥都行6 小时前
计算机基础复习12.22
java·jvm·redis·后端·mysql
Stark、6 小时前
【Linux】文件IO--fcntl/lseek/阻塞与非阻塞/文件偏移
linux·运维·服务器·c语言·后端