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 语句的使用一致性,但用于已经声明的变量,而不是创建新的变量。
  • 使用这种方式可以简化对多值赋值的操作,使代码更加简洁和易读。
相关推荐
传奇开心果编程9 分钟前
【Xilem基础语法学与练】第8课:条件渲染(one_of)
学习·rust·前端框架
qwsaedca2 小时前
在Mac上跑 Kokoro TTS经验总结
rust·mac·tts
qwsaedca2 小时前
Kokoro TTS v1.1 voices 文件格式逆向分析
rust
老猿讲编程4 小时前
【Eclipse OpenSOVD学习之五】拓扑引擎(Topology)
学习·rust·eclipse·sovd
object not found5 小时前
Nuxt4去掉body中默认的边距
开发语言·后端·rust
梦醒沉醉21 小时前
4、Rust参考手册——Crate和源文件
rust
chainbees21 小时前
Windows 系统 Rust 运行环境搭建
rust
k4m7v2pz1 天前
从 Python 搬到 Rust:pyglet MIDI DAW 变成 egui 鬼畜采样器的迁移复盘
开发语言·python·rust
小灰灰搞电子1 天前
Rust+Slint 实现ModbusRTU从机调试助手源码分享
开发语言·rust·modbusrtu