Rust在编译时避免未定义行为而不是在运行时

  • 使用Box来在堆中定义变量:

    1
    2
    let a = Box::new([0; 1_000_000]);
    let b = a; // a的所有权被移动到b

    Box

  • fn dereferencing() {
        let mut x: Box<i32> = Box::new(1);
        println!("x = {}", x);
        let a: i32 = *x;
        *x += 1;
    
        let r1: &Box<i32> = &x;     // r1 points to x on the stack
        /**
         * println!("{}", variable):用于用户友好的显示。它会通过 Display trait 自动解引用指针和引用,直到找到一个具体的值来显示。
         * println!("{:p}", variable):用于调试和底层观察。它会打印出引用或指针变量本身存储的那个内存地址。
         */
        println!("r1 = {:p}", r1);
        /**
         * 下面是错误的, 因为rust为了内存安全规定: 一个值在同一时间只能有一个owner;
         */
        // let b: Box<i32> = *r1;  
        let b: i32 = **r1;
        println!("b = {}", b);
    
        let r2: &i32 = &*x;     // &*x 确实得到了 *x 所在位置的“地址”,但 Rust 把它包装成了一个更安全、更受限制的类型,叫做“引用 (&)” 
        println!("r2 = {}", r2);
        let c = *r2;
        println!("c = {}", c);
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30

    ```mermaid
    graph LR
    subgraph "内存区域"
    subgraph "栈 (Stack Memory)"
    direction TB

    addr_x["@ 0x7ffc_A0"] -- "变量 x" --> val_x["<b>值: 0x55ef_B0</b><br/>(类型: Box&lt;i32&gt;)"]
    addr_a["@ 0x7ffc_A8"] -- "变量 a" --> val_a["值: 1<br/>(类型: i32)"]
    addr_r1["@ 0x7ffc_AC"] -- "变量 r1" --> val_r1["<b>值: 0x7ffc_A0</b><br/>(类型: &amp;Box&lt;i32&gt;)"]
    addr_b["@ 0x7ffc_B4"] -- "变量 b" --> val_b["值: 2<br/>(类型: i32)"]
    addr_r2["@ 0x7ffc_B8"] -- "变量 r2" --> val_r2["<b>值: 0x55ef_B0</b><br/>(类型: &amp;i32)"]
    addr_c["@ 0x7ffc_C0"] -- "变量 c" --> val_c["值: 2<br/>(类型: i32)"]
    end

    subgraph "堆 (Heap Memory)"
    addr_heap["@ 0x55ef_B0"] -- "Box&lt;T&gt;指向的数据" --> val_heap["值: 2<br/>(类型: i32)"]
    end
    end

    %% 箭头表示“指向”关系
    val_x -- "指向堆数据" --> addr_heap
    val_r1 -- "引用栈上的 x" --> addr_x
    val_r2 -- "引用堆上的数据" --> addr_heap

    %% 样式
    style val_x fill:#f9f,stroke:#333
    style val_r1 fill:#ccf,stroke:#333
    style val_r2 fill:#9cf,stroke:#333
    style addr_heap fill:#f96,stroke:#333,stroke-width:2px
    一些误区:
    1
    2
    let x = Box::new(0);
    let y = Box:new(&x);
    这时候`***y` 才指向值`0`, `*y`是`x`的引用(`&x`)类型是`&Box<i32>`, `**y`指向`x`本身(`x: Box<i32>`), `***y`才指向`*x`(即x指向的堆的值`0`).
  • Rust Avoids Simultaneous Aliasing and Mutation

    Pointer Safety Principle: data should never be aliased and mutated at the same time.

    数据不允许同时别名和修改

    Vec为例:

    1
    2
    let mut v: Vec<i32> = vec![1, 2, 3];
    v.push(4);

    Vec的结构是这样的:vec-struct, Vec 是一个在栈上的结构体,它包含三个部分:一个指向堆内存的指针 (pointer)、长度 (length) 和容量 (capacity). 其中len代表Vec中实际有多少个元素, capacity代表v的容量是多少(可以放多少元素), 如果v.push之后超过了capacity, buf中的指针就会重新开辟一块内存转移数据并且释放掉原先的内存.

    那么如果进行下面的操作

    1
    2
    3
    4
    let mut v: Vec<i32> = vec![1, 2, 3];		// 这里使用vec!宏来初始化Vec容器, 那么默认len = capacity = 3
    let n: &i32 = &v[2]; // n 是v[2]的不可变引用immutable reference
    v.push(4); // 这里因为capacity不够, 所以v会重新开辟内存转移数据, 并且释放掉原先的内存
    println!("{n}"); // 这里就会报错, 因为n指向的地址已经被释放掉了, 所以这里会报错, 但是如果没有这一句, 编译器则不会报错: 虽然n现在还是指向已经释放的地址,但是编译器会自动判断n在释放地址之后没有再次使用,所以这里编译器不会报错
  • *References Change Permissions on Places

    Permission:

    • Read (R): data can be copied to another location.
    • Write (W): data can be mutated.
    • Own (O): data can be moved or dropped.

    Place: 任何可以放在赋值符号左侧的都可以是place, 比如: Variables, Dereferences of places, Array accesses of places, Field of places like tuples or a.field for structs, any combination of the above

    place默认有R(ead), O(wn)的权限, 如果赋值的时候使用mut符号, 那么还会增加W权限

    1
    2
    let v: Vec<i32> = vec![1, 2, 3];		// v 只有R和O权限
    let mut v1: Vec<i32> = vec![1, 2, 3]; // v1 有W,R,O权限

    **首先要明确, 如果place离开作用域, 权限就会被收回, eg: **

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    fn main() {
    let v: Vec<i32> = vec![1, 2, 3]; // 此时v有R和O权限
    {
    let v1: Vec<i32> = vec![1, 2, 3]; // 此时v1有R和O的权限
    } // 这里v1的作用域结束, v1失去所有权限
    println!("{:?}", v);

    change(v); // 因为Vec没有实现Copy Trait所以v是被move到了这个函数中, v的权限也随之移动到了函数中, 当函数结束时, 意味着v的作用域结束, v的权限此时被收回;
    println!("{:?}", v); // 这里会报错, v已经失去了所有权限

    } // main函数是v的作用域, 所以只有在main函数结束的时候, v才会失去所有权限

    fn change(v: Vec<i32>) {
    println!("{:?}", v);
    }

    一个解决方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    // 第一个解决方法
    let mut v: Vec<i32> = vec![1, 2, 3];
    let v_again = change(v);

    fn change(v: Vec<i32>) -> Vec<i32> {
    println!("{:?}", v);
    v
    }
    println!("{:?}", v_again);

    // 第二个解决方法
    let mut v: Vec<i32> = vec![1, 2, 3];
    change(v.clone()); // 使用clone方法, 但是clone方法会在堆中新开辟一块内存, 所以会有性能问题
    println!("{:?}", v);

    fn change(v: Vec<i32>) {
    println!("{:?}", v);
    }
    // 第三个方法 immutable references
    let mut v: Vec<i32> = vec![1, 2, 3];
    change(&v); // 使用引用的方式, 这里不会在heap中创建副本
    println!("{:?}", v);

    fn change(v: &Vec<i32>) {
    println!("{:?}", v);
    }

    **immutable references(shared references): ** 可以在不移动所有权的情况下提供临时的访问, 但是不能修改数据. 同一时间可以有很多Immutable references

    **mutable references(unique references): ** 可以对数据进行修改, 但是同一时间只能有一个mutable reference

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    let mut v: Vec<i32> = vec![1, 2, 3];
    let num: &mut i32 = &mut v[2];
    /**
    * 此时v失去所有权限
    * num只是一个存储v[2]地址的一个指针(引用), 所以num对于它自己本身有R和O的权限(没有使用mut), 注意: v[2]是v内部buf中的ptr地址, 但是v结构的地址不会变
    * *num 此时才是存储的v[2]的值, 此时*num有R和W权限(因为是mutable reference), 但是引用就是引用, 所以没有O权限
    */

    *num += 1;
    v.push(4);

    O (Own) 权限永远属于数据的所有者(在这里是 v)。引用 (&&mut) 只是临时“借用”了 RR+W 权限。*num 作为一个“位置 (place)”,它允许被读取和写入,但对这块数据的“生杀大权”(移动或销毁)仍然在 v 手里。

    some questions:

    Q&A:

    1
    2
    3
    4
    5
    6
    7
    fn main() {
    let mut s = String::from("Hello");
    let t = &mut s;
    /* here */ // s在这里没有任何权限, 因为t是mutable reference, 所以当t生命周期没有结束的时候, s失去了所有权限
    t.push_str(" world");
    println!("{}", s);
    }
  • *Data Must Outlive All Of Its References

    直接来看例子:

    1
    2
    3
    4
    5
    6
    fn main() {
    let s = String::from("Hello world");
    let s_ref = &s;
    drop(s); // 这里s还有引用s_ref, 所以如果这里drop掉, s_ref就会指向无效地址, 如果后续s_ref还有使用(生命周期没有结束), 编译器就会自动检测并报错
    println!("{}", s_ref); // s_ref生命周期没有结束, 但是现在s_ref指向无效地址了, 所以编译器会报错
    }

    因为编译器无法看到所有函数的周期, 他只能看到函数的signature(输入和输出), 所以如果引用从函数中返回的时候是安全的, 就会获得F权限

    F(low)权限: 视为 “出口许可证”。 只有当编译器能够证明该引用从其当前上下文中“流出”(例如从函数返回)是安全的时,引用才会获得 F 权限。

    1
    2
    3
    4
    fn first(strings: &Vec<String>) -> &String {
    let s_ref = &strings[0];
    s_ref
    }

    不安全的行为:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    /**
    * 这里的函数是会报错的,因为函数不知道返回的String生命周期和哪个输入一致, 具体在main函数
    */
    fn first_or<'a, 'b, 'c>(strings: &'a Vec<String>, default: &'b String) -> &'c String {
    if strings.len() > 0 {
    &strings[0]
    } else {
    default
    }
    }

    fn main() {
    let strings = vec![]; // 这里strings是一个空的Vec
    let default = String::from("default");
    let s = first_or(&strings, &default); // 根据first_or函数的定义: 这里会返回default, 所以s的生命周期也和defalut保持一致,但是这里有个问题: 编译器事先并不知道应该返回的生命周期该和strings保持一致还是和default保持一致, 如果和strings保持一致, 那么最后的println!宏会报一个清晰的错误:那么s的生命周期已经结束了, 因为string的生命周期在这一行结束就结束了, 如果和default保持一致, 那么经过下一句, s就会变成悬垂指针也会报错
    drop(default);
    println!("{}", s);
    }

    另一个例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    fn main() {
    let default = String::from("I live for a long time"); // `default` 的生命周期很长
    let final_ref;

    { // 进入一个新的、更短的生命周期范围
    let strings = vec![String::from("I live for a short time")]; // `strings` 的生命周期很短

    // 调用这份模糊的合同
    final_ref = first_or(&strings, &default);

    } // `strings` 在这里被销毁了!它的内存被回收了!

    // 灾难发生点:
    println!("{}", final_ref); // `final_ref` 现在指向的是谁?
    }

    修改后:

    1
    2
    3
    4
    5
    6
    7
    fn first_or2<'a>(strings: &'a Vec<String>, default: &'a String) -> &'a String {
    if(strings.len() > 0) {
    &strings[0]
    } else {
    default
    }
    }
    1. 我们定义了一个生命周期标签,名叫 'a
    2. 输入 strings 必须至少活得和 'a' 一样长。
    3. 输入 default 也必须至少活得和 'a' 一样长。
    4. 最重要的承诺:函数返回的引用,其有效期最多'a' 一样长。

    这里的 'a 实际上代表了 stringsdefault较短的那个生命周期。

    编译器看到调用 first_or(&strings, &default)

    它比较了 strings(生命周期短)和 default(生命周期长)的有效期。

    它确定了那个更短的有效期,并将其作为这次调用的 'a

    函数返回的 final_ref 因此也获得了这个较短的生命周期 'a

    所以,final_ref 的有效范围被严格限定在了那个内部 {} 之中。

    当代码试图在 {} 外部使用 println!("{}", final_ref) 时,审查员会立刻发现:final_ref 的生命周期已经结束了,它已经失效了!

    审查员报告一个新的、明确的错误:“你正在使用一个已经过期的引用。”

    重点: ** 当一个函数返回的引用可能来自多个不同的输入时,编译器会因为无法判断这个引用的“保质期”而拒绝编译。生命周期 'a 就是你给编译器提供的一个明确的“保质期”标签**,你向它承诺,返回的引用的有效期不会超过所有相关输入中最先失效的那个。这样,编译器就能确保安全了。

    Q1:

    1
    2
    3
    4
    5
    6
    7
    8
    fn incr(n: &mut i32) {
    *n += 1;
    }
    fn main() {
    let mut n = 1;
    incr(&n);
    println!("{n}");
    }

    上面这个程序不会通过编译, 因为incr(&n) 传递的是immutable reference, 应该修改为incr(&mut n)

    Q2:

    1
    2
    3
    4
    5
    6
    7
    fn main() {
    let mut s = String::from("hello");
    let s2 = &s;
    let s3 = &mut s;
    s3.push_str(" world");
    println!("{s2}");
    }

    这个程序也无法通过编译, 因为s2是immutable reference,所以在s2生命周期还没有结束的时候, 对s3操作是不合法的:
    借用规则:“你不能在存在一个活跃的不可变借用的同时,再创建一个可变借用。”

    Q3:

    Consider this Rust function that pushes a number onto the end of a vector, and then removes and returns the number from the front of the vector:

    1
    2
    3
    4
    fn give_and_take(v: &Vec<i32>, n: i32) -> i32 {
    v.push(n);
    v.remove(0)
    }

    Normally, if you try to compile this function, the compiler returns the following error:

    1
    error[E0596]: cannot borrow `*v` as mutable, as it is behind a `&` reference --> test.rs:2:5  |1 | fn give_and_take(v: &Vec<i32>, n: i32) -> i32 {  |                     --------- help: consider changing this to be a mutable reference: `&mut Vec<i32>`2 |     v.push(n);  |     ^^^^^^^^^ `v` is a `&` reference, so the data it refers to cannot be borrowed as mutable

    Assume that the compiler did NOT reject this function. Select each (if any) of the following programs that could possibly cause undefined behavior if executed. If none of these programs could cause undefined behavior, then check “None of these programs” .

    **A: **这里主要的错误是Vecpush的时候可能超过capacity, 导致v内部值的地址改变, 最终导致悬垂指针

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    let v = vec![1, 2, 3];
    let n = &v[0];
    give_and_take(&v, 4);
    println!("{}", n); // 这里就会导致错误, 此时n为悬垂指针

    let v = vec![1, 2, 3];
    let n = &v[0];
    let k = give_and_take(&v, 4); // 这里结束之后, n为悬垂指针了, 但是n在后续并没有使用(生命周期已经结束), 所以编译器不会报错
    println!("{}", k);

    let v = vec![1, 2, 3];
    let v2 = &v;
    give_and_take(&v, 4); // 这里改变的是v内部buf中ptr的值, 而不是v结构体的地址,所以当内部地址改变的时候v2内部也会发生改变, 不会报错
    println!("{}", v2[0]);

Summary

References provide the ability to read and write data without consuming ownership of it. References are created with borrows (& and &mut) and used with dereferences (*), often implicitly.

However, references can be easily misused. Rust’s borrow checker enforces a system of permissions that ensures references are used safely:

  • All variables can read, own, and (optionally) write their data.
  • Creating a reference will transfer permissions from the borrowed place to the reference.
  • Permissions are returned once the reference’s lifetime has ended.
  • Data must outlive all references that point to it.

  • Rust中的变量默认是不可变的, 但是可以添加mut关键字使之可变

    Rust有单次赋值(Single assignment)原则, 尽管Rust变量默认是不可变的, 但是它允许你在声明变量后延迟赋值一次, 只要编译器能确定该变量只会被赋值一次即可.

    1
    2
    3
    4
    5
    6
    7
    // 下面的语句是可以通过编译的
    let x;
    if cond {
    x = 1;
    } else {
    x = 2;
    }
  • 常量声明方式:

    阅读全文 »

  • 一般crate使用use导入, 但是Rust会自动导入prelude,里面包含了基本的功能, 详见standard library documentation.

  • Rust中的变量默认是不可变的, 可以添加mut关键字使之改变

  • read_line读取用户输入,并将其添加到字符串尾端(append), 而不是覆盖, 不同于cin

  • read_line会返回一个Result值

  • Result是一个枚举类型,其中有OkErr, 所以可以使用read_line(&mut guess).expect("Failed to read") expect函数来处理错误读入, 任何类型的值都有其方法

  • 正常情况下,expect()方法会返回用户输入的字节数

  • println!() 可以使用{}来输出变量, 但是如果变量名称不存在就会报错

  • crate是一组Rust源代码文件,用户构建的项目是一个二进制crate, 它是一个可执行文件. rand crate是一个库crate, 它包含的代码用于其他程序, 并不能单独运行

    阅读全文 »

此分类(The Rust Programming Language)采用brown university的实验版本, 对细节进行解释同时进行学习记录

image-20250616162739328

Getting Started

  • rustup 来管理机器上的Rust版本

  • rustc main.rs				// rustc的用法, rustfmt也可以这么使用
    rustfmt main.rs				// 使用rust的语法格式化文档
    ./main
    
  • cargo init: 自动获取cargo.toml文件\

  • cargo.lock文件追踪准确的版本依赖

  • cargo run 只有代码发生改变的时候才会重新编译运行, 否则直接运行

  • cargo build --release会在target/release下生成目标文件, 会让Rust代码运行更快,没有debug. 如果要衡量代码效率就是用此命令编译

    阅读全文 »

一个简单的时间获取客户程序

本系统: Debian 12

如何下载源码并编译运行

官方源码地址:https://github.com/unpbook/unpv13e

git clone https://github.com/unpbook/unpv13e.git

克隆到本地之后,根据README文件一步一步make配置

接下来可以直接 make 想要运行的源码 然后执行即可

阅读全文 »

引言

最近在开发项目的时候碰到了领域模型,但是并没有对其中的各层关系以及各层传递数据方式理解清晰。在此记录下分层领域模型数据的传递过程

所谓领域模型

所谓领域模型,其实就是C++类,再详细一点就是有作用范围的实体类。

其核心目标是业务抽象:将现实业务中的实体、流程和规则转化为可被代码理解和操作的结构。

其中的领域对象描述是这样的:

  • DO(Data Object):此对象与数据库表结构一一对应,通过 DAO 层向上传输数据源对象。
  • DTO(Data Transfer Object):数据传输对象,Service 或 Manager 向外传输的对象。
  • BO(Business Object):业务对象,可以由 Service 层输出的封装业务逻辑的对象。
  • Query:数据查询对象,各层接收上层的查询请求。
  • VO(View Object):显示层对象,通常是 Web 向模板渲染引擎层传输的对象。

img

阅读全文 »

前情提要

野指针(Wild Pointer): 野指针是指指向任意未知位置的指针,它没有被初始化或者指向无效的内存。

悬挂指针(dangling pointer):指指向已释放或无效内存的指针。当一个指向动态分配内存的指针所指向的内存被释放后,该指针仍然保留着原来的指向,但此时使用该指针访问所指向的内存将导致未定义的行为。

RAII (Resource Acquisition Is Initialization): 资源获取即初始化, R使用一个对象,在其构造时获取对应的资源,在对象生命期内控制对资源的访问,使之始终保持有效,最后在对象析构的时候,释放构造时获取的资源。像下面的例子:

阅读全文 »

一些基本概念

CORS(跨域资源共享):浏览器的安全特性,允许你控制哪些外部域名访问你的API

API路由:简单来说就是根据客户端发起的请求(包括请求的方法,如GET、POST、PUT等,以及请求的URL路径)将请求映射到不同的处理逻辑中:

1.匹配客户端发来的请求

2.根据请求的路径和方法,找到并执行相应的代码(处理逻辑)

3.返回一个响应给客户端

阅读全文 »

前两天突发奇想想把一直在用的实机(Debian 12)换成Anolis OS,在安装这个系统的时候碰到了不少问题,现记录于此.

机器配置:

笔记本品牌: HUAWEI MateBook D 14

CPU型号: R5 4500U

内存: DDR4 16GB 2666MHz

硬盘: SSD 512GB

网络通信: Realtek RTL8821CE/RTL8822CE Wi-Fi

安装:

准备: 至少16GB的U盘

  1. Anolis官网下载需要的镜像

  2. 通过u盘刻录软件将镜像刻录到U盘(我用的是rufus), 其中刻录的时候要注意的是:

    1.分区类型(MBR或GPT)一定要选择与系统相匹配的类型, MateBook D 14要选择GPT,不然会导致启动u盘的时候被验证失败(Error: Vertification failed).

    2.写入模式要选择DD模式, 不要选择ISO模式, 不然会导致进入安装界面的时候本地仓库识别不到.

  3. 最后根据引导完成安装即可.

    阅读全文 »
0%