rust学习笔记

一、 编译与运行

在 Rust 中,编译和运行代码的常用命令是使用 cargo,这是 Rust 的包管理和构建工具。以下是使用 cargorustc(Rust 编译器)的具体命令:

1. 使用 cargo 工具

cargo 是 Rust 的推荐工具,因为它可以处理依赖管理、构建、测试等多个功能。

  • 编译和运行项目:

    bash 复制代码
    cargo run

    该命令将编译项目并运行可执行文件。cargo run 是一个方便的命令,适用于开发过程中快速测试代码。

  • 仅编译项目:

    bash 复制代码
    cargo build

    该命令只会编译项目,而不运行。编译后的可执行文件通常位于 target/debug/ 目录下。

  • 编译发布版本:

    bash 复制代码
    cargo build --release

    使用 --release 选项来编译一个优化的发布版本。编译后的可执行文件通常位于 target/release/ 目录下。

2. 使用 rustc 命令

如果你想直接使用 Rust 编译器 rustc,而不使用 cargo,可以使用以下命令:

  • 编译文件:

    bash 复制代码
    rustc main.rs

    这将使用 rustc 编译器直接编译 main.rs 文件,并生成一个名为 main(Windows 下为 main.exe)的可执行文件。

  • 运行可执行文件:

    编译完成后,运行生成的可执行文件:

    bash 复制代码
    ./main    # 在 Unix 或 Linux 系统上
    main.exe  # 在 Windows 系统上

二、如何显式的处理数字类型的溢出

相关教程章节:https://doc.rust-lang.org/book/ch03-02-data-types.html

Rust 提供了多种方法来处理整数溢出,这些方法可以帮助你根据需要选择适当的策略来应对可能的溢出。让我们逐个分析这些方法,并举例说明它们的用法。

1. wrapping_* 方法

  • 描述 :当发生溢出时,wrapping_* 方法会绕回到类型的最小值(或最大值),即在所有模式下(包括 debugrelease 模式)进行"环绕"(wrap around)处理。
  • 使用场景:适用于希望溢出后从类型的边界"环绕"回来的情况,比如在循环计数时。

示例:

rust 复制代码
fn main() {
    let max = u8::MAX; // u8 的最大值是 255
    let wrapped = max.wrapping_add(1); // 255 + 1 应该是 256,但会"环绕"回 0

    println!("Wrapped value: {}", wrapped); // 输出: Wrapped value: 0
}
  • 解释:u8 类型的最大值是 255。wrapping_add(1) 将会"环绕"回 0,因为 u8 只能存储 0 到 255 之间的值。

2. checked_* 方法

  • 描述checked_* 方法在发生溢出时返回 None,否则返回 Some(value)
  • 使用场景 :适用于需要检测溢出并希望在溢出时采取一些特定措施的情况(例如返回一个 None 值表示错误)。

示例

rust 复制代码
fn main() {
    let max = u8::MAX; // u8 的最大值是 255
    let checked = max.checked_add(1); // 255 + 1 会溢出

    match checked {
        Some(value) => println!("Checked value: {}", value),
        None => println!("Overflow occurred!"), // 输出: Overflow occurred!
    }
}
  • 解释:checked_add(1) 会检测到溢出,因为 255 + 1 超出了 u8 类型的范围,因此返回 None

3. overflowing_* 方法

  • 描述overflowing_* 方法返回一个元组 (value, bool),其中 value 是操作结果,bool 表示是否发生了溢出(true 表示发生溢出)。
  • 使用场景:适用于需要知道溢出发生与否,同时还需要获取操作结果的情况。

示例

rust 复制代码
fn main() {
    let max = u8::MAX; // u8 的最大值是 255
    let (overflowed_value, did_overflow) = max.overflowing_add(1); // 255 + 1 会溢出

    println!("Overflowing value: {}", overflowed_value); // 输出: Overflowing value: 0
    println!("Did overflow: {}", did_overflow); // 输出: Did overflow: true
}
  • 解释:overflowing_add(1) 返回一个元组,其中第一个值是溢出后的结果(即环绕后的值 0),第二个值是一个布尔值,表示是否发生了溢出(true 表示发生了溢出)。

4. saturating_* 方法

  • 描述saturating_* 方法在溢出时返回类型的最小值或最大值,而不是产生环绕效果。
  • 使用场景:适用于希望结果在类型的范围内"饱和"(即限制在最大值或最小值)而不发生环绕的情况,比如计算结果不能超过一个特定的上限或下限。

示例

rust 复制代码
fn main() {
    let max = u8::MAX; // u8 的最大值是 255
    let saturated = max.saturating_add(1); // 255 + 1 会溢出

    println!("Saturating value: {}", saturated); // 输出: Saturating value: 255
}
  • 解释:saturating_add(1) 在发生溢出时不会绕回到 0,而是返回 u8 类型的最大值(255),这是"饱和"效果的表现。

总结

  • wrapping_* 方法:当发生溢出时,结果"环绕"回类型的边界值。
  • checked_* 方法 :当发生溢出时,返回 None,否则返回 Some(value)
  • overflowing_* 方法 :返回一个元组 (value, bool),表示结果和是否发生溢出。
  • saturating_* 方法:当发生溢出时,返回类型的最小值或最大值(不发生环绕)。

三、控制台打印中{}{:?}的区别

在 Rust 中,{:?}{} 都是格式化占位符,但它们的用途和作用有所不同。

{:?}{} 的区别

  1. {:?} :表示调试格式化 (debug formatting)。它用于打印那些实现了 Debug trait 的类型的值。它的输出通常更详细,适用于开发和调试用途。使用 {:?} 时,Rust 不仅会打印变量的值,还会尽可能地显示它们的内部结构。

  2. {} :表示显示格式化 (display formatting)。它用于打印那些实现了 Display trait 的类型的值。Display trait 更像是用户友好的输出格式,而不是用于调试的信息。并不是所有类型都实现了 Display trait,例如,常见的 Rust 标准类型(如 OptionResult)默认没有实现 Display

为什么用 {:?} 而不是 {}

在 Rust 中,大多数基本数据类型(如 booli32f64 等)都同时实现了 DebugDisplay trait,因此你可以使用 {:?}{} 来打印它们的值。但是,使用 {:?} 更通用,它适用于更广泛的类型。

例子:

rust 复制代码
println!("a = {:?}, b = {:?}", a, b);

这是用来打印布尔值 ab。虽然 bool 类型同时实现了 DebugDisplay,但一般情况下:

  • 使用 {:?} 是一种好习惯 ,因为它可以打印更复杂的数据结构,例如元组、数组、向量、枚举等。在这些情况下,{:?} 可以打印更多细节而无需更改代码。

  • {:?} 更通用 ,它能确保你在需要打印复杂数据结构时不出错,而 {} 只能用于实现了 Display 的类型。

示例:{:?} vs {}

让我们看一个例子来更好地理解这两个占位符的区别。

使用 {:?} 调试格式化
rust 复制代码
fn main() {
    let tuple = (42, "hello", vec![1, 2, 3]);

    // 使用调试格式化打印
    println!("{:?}", tuple);
}

输出:

(42, "hello", [1, 2, 3])
  • 这里使用 {:?} 成功打印了元组的所有内容,包含整数、字符串和向量的详细信息。
使用 {} 显示格式化
rust 复制代码
fn main() {
    let tuple = (42, "hello", vec![1, 2, 3]);

    // 使用显示格式化打印
    println!("{}", tuple); // 编译错误
}

输出:

error[E0277]: `({integer}, &str, std::vec::Vec<{integer}>)` doesn't implement `std::fmt::Display`
  • 上述代码会产生编译错误,因为元组 (42, "hello", vec![1, 2, 3]) 没有实现 Display trait,因此不能使用 {} 来打印它。

总结

  • {:?} :用于调试格式化,适用于所有实现了 Debug trait 的类型。更通用,更适合打印复杂的数据结构和调试输出。
  • {} :用于显示格式化,适用于实现了 Display trait 的类型。输出更简洁,但不适用于所有类型。

四、BACKTRACE

在 Rust 中,当程序发生 panic(恐慌)时,会打印一个错误消息,描述导致 panic 的原因。

note: run with RUST_BACKTRACE=1 environment variable to display a backtrace 的含义

这条提示信息的意思是:如果你希望看到导致 panic 的完整调用栈(backtrace),可以运行程序时设置环境变量 RUST_BACKTRACE=1

什么是 Backtrace?

  • Backtrace 是一种调试信息,它显示了程序在发生 panic 时的调用堆栈(函数调用的路径)。
  • 通过查看 backtrace,可以了解程序是如何到达 panic 点的,这对调试程序非常有用。
  • 在 Rust 中,默认情况下,当程序发生 panic 时,Rust 只会显示一个简短的错误信息(如你看到的那样),不会显示完整的 backtrace。

如何启用 Backtrace?

要启用 backtrace,可以在运行程序时设置 RUST_BACKTRACE 环境变量为 1。这样 Rust 会在 panic 发生时打印出更详细的调用堆栈。

示例:在不同平台上启用 Backtrace
  • Linux/macOS

    在终端中运行以下命令:

    bash 复制代码
    RUST_BACKTRACE=1 cargo run

    或者如果你直接使用编译后的可执行文件,可以这样运行:

    bash 复制代码
    RUST_BACKTRACE=1 ./your_program_name
  • Windows

    在命令提示符中运行以下命令:

    bash 复制代码
    set RUST_BACKTRACE=1
    cargo run

    或者,如果你直接运行可执行文件,可以这样运行:

    bash 复制代码
    set RUST_BACKTRACE=1
    your_program_name.exe

启用 Backtrace 后的输出

启用 backtrace 后,运行程序时,你会看到类似以下的输出:

plaintext 复制代码
thread 'main' panicked at 'index out of bounds: the len is 5 but the index is 10', src/main.rs:19:19
stack backtrace:
   0: std::panicking::begin_panic
   1: core::panicking::panic_bounds_check
   2: <your_function_name>
   ...
   8: main
   9: __libc_start_main
  10: _start

解释

  • 这些信息显示了程序中发生 panic 的函数调用栈。每一行对应一个函数调用。
  • 通过查看这些信息,你可以追踪导致 panic 的路径,找到出错的代码段并进行修复。

五、解构式赋值

rust教程:https://course.rs/basic/variable.html

这段教程介绍了 Rust 1.59 版本后支持的一种新的赋值方式------解构式赋值 。这种方式允许在赋值语句中使用元组、切片和结构体的模式,从而直接给多个变量赋值或更新变量的值。它类似于 let 语句的解构绑定,但不同的是,let 语句用于变量的初次绑定,而解构式赋值用于已经绑定的变量的再赋值。

什么是解构式赋值?

解构式赋值 是一种赋值方法,它允许你使用模式匹配的方式将值"解构"并赋给多个变量。在 Rust 1.59 之前,这种操作只能在 let 语句中使用,而在 Rust 1.59 之后,它被扩展到普通的赋值语句中,使得代码更加简洁和易于理解。

解释代码示例

我们来看一下你的代码示例,并逐步分析每一行:

rust 复制代码
struct Struct {
    e: i32
}

fn main() {
    let (a, b, c, d, e);

    (a, b) = (1, 2);
    // _ 代表匹配一个值,但是我们不关心具体的值是什么,因此没有使用一个变量名而是使用了 _
    [c, .., d, _] = [1, 2, 3, 4, 5];
    Struct { e, .. } = Struct { e: 5 };

    assert_eq!([1, 2, 1, 4, 5], [a, b, c, d, e]);
}
1. 结构体定义
rust 复制代码
struct Struct {
    e: i32
}
  • 定义了一个简单的结构体 Struct,它只有一个字段 e,类型是 i32
2. 解构式赋值的例子
rust 复制代码
fn main() {
    let (a, b, c, d, e);
  • 这里用 let 声明了五个变量 a, b, c, d, 和 e,但是没有为它们赋值。
rust 复制代码
    (a, b) = (1, 2);
  • 解构式赋值 :将元组 (1, 2) 中的值解构并分别赋给 ab。结果是 a = 1b = 2
rust 复制代码
    [c, .., d, _] = [1, 2, 3, 4, 5];
  • 这里我们对数组 [1, 2, 3, 4, 5] 使用了解构赋值。
    • c 取得第一个元素 1
    • .. 表示忽略中间的元素(这里是 [2, 3])。
    • d 取得倒数第二个元素 4
    • _ 匹配最后一个元素 5,但我们不关心它的值,因此使用 _ 忽略掉。
    • 结果是 c = 1d = 4
rust 复制代码
    Struct { e, .. } = Struct { e: 5 };
  • 对结构体进行解构赋值。
    • Struct { e, .. } 是一个模式,匹配 Struct 结构体类型。
    • .. 表示忽略结构体中的其他字段(如果有的话)。
    • e 取得结构体中 e 字段的值(这里是 5)。
    • 结果是 e = 5
3. 断言
rust 复制代码
    assert_eq!([1, 2, 1, 4, 5], [a, b, c, d, e]);
  • assert_eq! 宏用于断言两个值相等。
  • 它比较数组 [1, 2, 1, 4, 5] 和变量的值 [a, b, c, d, e] 是否相等。
  • 由于前面的解构式赋值,我们知道 a = 1b = 2c = 1d = 4e = 5,因此断言成功,程序继续执行。

解构式赋值 vs. let 绑定

  • let 绑定 :在初次声明变量时使用。let 语句会新建变量,并绑定一个值或解构多个值。

    rust 复制代码
    let (x, y) = (10, 20); // 新建变量 x 和 y,并赋值
  • 解构式赋值:在已经声明的变量上使用。解构式赋值仅仅是对已存在变量的再赋值。

    rust 复制代码
    let x;
    x = 5; // 给已声明的变量 x 赋值
注意事项
  • 使用 += 等运算符进行赋值时,还不支持解构式赋值。这意味着你不能对解构后的变量直接使用 += 等操作符,例如:

    rust 复制代码
    (x, y) += (1, 2); // 这是不允许的,编译时会报错

总结

  • 解构式赋值 是一种灵活的赋值方式,它允许你在赋值语句中使用元组、切片和结构体模式进行解构。
  • 它保持了与 let 语句的使用一致性,但用于已经声明的变量,而不是创建新的变量。
  • 使用这种方式可以简化对多值赋值的操作,使代码更加简洁和易读。
相关推荐
brrdg_sefg5 小时前
Rust 在前端基建中的使用
前端·rust·状态模式
m0_748230945 小时前
Rust赋能前端: 纯血前端将 Table 导出 Excel
前端·rust·excel
SomeB1oody12 小时前
【Rust自学】6.1. 定义枚举
开发语言·后端·rust
SomeB1oody13 小时前
【Rust自学】5.3. struct的方法(Method)
开发语言·后端·rust
itas1091 天前
Rust调用C动态库
c语言·rust·bindgen·bindings·rust c绑定
SomeB1oody1 天前
【Rust自学】5.1. 定义并实例化struct
开发语言·后端·rust
m0_748236111 天前
Calcite Web 项目常见问题解决方案
开发语言·前端·rust
SomeB1oody3 天前
【Rust自学】4.1. 所有权:栈内存 vs. 堆内存
开发语言·后端·rust
SomeB1oody3 天前
【Rust自学】4.2. 所有权规则、内存与分配
开发语言·后端·rust
SomeB1oody3 天前
【Rust自学】4.5. 切片(Slice)
开发语言·后端·rust