CS110L(Rust)

1.Rust 语法总结

数值类型
  • 有符号整数: i8, i16, i32, i64
  • 无符号整数: u8, u16, u32, u64
变量声明
  • 声明变量:
    let i = 0;    // 类型推断
    
    let n: i32 = 1; // 显式类型声明
  • 可变变量:
    let mut n =0;
    n = n + 1;
字符串 

注意,let s: str = "Hello world"; 是不正确的,因为 str 类型不能单独使用。它必须通过引用(&str)来使用。 

集合
  • 动态数组(向量):
    let mut v: Vec<i32> = Vec::new();
    v.push(1);
    v.push(0);
  • 固定大小数组:
  • 在 Rust 中,所有变量在使用之前必须初始化。这是为了防止未初始化变量引起的未定义行为。因此,您不能声明一个未初始化的数组或变量。

  • // 创建一个可变数组 `arr`,包含4个 `i32` 类型的元素,将所有元素初始化为0
    let mut arr: [i32; 4] = [0; 4];
    
    // 或者,可以逐个初始化每个元素
    let mut arr: [i32; 4] = [0, 0, 0, 0];
    
    // 修改数组的元素
    arr[0] = 0;
    arr[1] = 1;
    arr[2] = 2;
    arr[3] = 3;
    
循环
  • 迭代器循环:
        // 使用 for 循环迭代向量中的元素
        for i in v.iter() {
            println!("{}", i);  // 打印每个元素
        }

  • while 循环:
        while i < 9 {
            i += 1;
            println!("i = {}", i);  // 打印每次递增后的值
        }
  • 无限循环:
    fn main() {
        let mut i = 0; // 初始化一个可变变量 i,初始值为 0
    
        loop {
            i += 1; // 每次循环迭代将 i 的值增加 1
    
            if i > 10 { // 检查 i 是否大于 10
                break; // 如果 i 大于 10,则退出循环
            }
        }
    
        println!("Final value of i: {}", i); // 打印 i 的最终值
    }
    
函数
fn sum(a: i32, b: i32) -> i32 {
    a + b
}
  • 声明函数:
    fn sum(a: i32, b: i32) -> i32 {
        a + b
    }
表达式
  • 三元表达式:
    let x = if someBool { 2 } else { 4 }
输入输出

(1)确保所有缓冲区中的数据都被写入到标准输出(通常是终端或控制台)中

io::stdout().flush().unwrap();

(2)read_line 方法从标准输入读取用户输入并将其存储到 guess 中。如果读取失败,程序会崩溃并显示错误信息 "读取输入失败."。

let mut guess = String::new();
io::stdin().read_line(&mut guess).expect("读取输入失败.");
操作符
(1)? 操作符

当一个函数返回 ResultOption 类型时,可以使用 ? 操作符来自动处理这些结果。如果结果是 Ok,则返回其中的值;如果是 Err,则返回错误并退出当前函数。

fn read_file_lines(filename: &str) -> Result<Vec<String>, io::Error> {
    // 尝试打开文件
    let file = File::open(filename)?;
    // 如果成功打开文件,继续执行;如果失败,返回错误并退出函数
}
定义结构体
在 Rust 中,定义结构体类型时,我们声明了结构体的字段及其类型,而不是创建具体的实例。因此,不需要使用 letlet mut 这样的关键字。letlet mut关键字用于创建变量,而不是定义类型。

在这段代码中,我们定义了一个名为 Node 的泛型结构体类型,它包含三个字段:

  • elem:类型为 T,表示节点存储的值。
  • next:类型为 Link<T>,表示下一个节点的引用。
  • prev:类型为 Link<T>,表示前一个节点的引用。

这个定义仅仅是声明了 Node 结构体的形状,并没有创建任何实际的 Node 实例。

impl

Rust 中的 impl 块类似于其他编程语言中的 class 定义,但有一些关键的不同之处。

类似点
  1. 方法定义

    • impl 块中定义的方法类似于在类中定义的方法。
    • 你可以定义实例方法和静态方法(Rust 中称为关联函数)。
  2. 封装

    • Rust 的 impl 块可以用于封装数据和行为,类似于类。
不同点
  1. 数据和行为的分离

    • 在 Rust 中,数据(通过结构体或枚举)和行为(通过 impl 块)是分开的。
    • 在类中,数据和行为通常是在一个定义中。
  2. 没有继承

    • Rust 没有类的继承。相反,它使用特性(traits)来实现多态性。
    • 类系统通常有继承和多态性机制。
  3. 所有权和借用

    • Rust 强调所有权和借用,确保内存安全。
    • 类系统通常使用垃圾回收(如 Java)或手动内存管理(如 C++)。
struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    fn new(width: u32, height: u32) -> Rectangle {
        Rectangle { width, height }
    }

    fn area(&self) -> u32 {
        self.width * self.height
    }
}

fn main() {
    let rect = Rectangle::new(30, 50);
    println!("The area of the rectangle is {} square pixels.", rect.area());
}

2.进阶用法

(1)闭包:

闭包的语法如下:

|参数列表| 表达式

因此,在 |&c| c 中:

  • |&c| 是闭包的参数列表,表示闭包接收一个引用类型的参数 c
  • c 是闭包体,表示闭包返回参数 c 的值。
  • |&c| c 本质上是一个闭包,它接受一个引用并返回解引用后的值。这相当于一个专门用于解引用的函数。

(2)all 方法

fn all<F>(&mut self, f: F) -> bool
where
    F: FnMut(Self::Item) -> bool,

它接受一个闭包 f 作为参数,并对迭代器中的每个元素应用这个闭包。all 方法会返回一个布尔值:

  • 如果所有元素都满足闭包 f 的条件,则返回 true
  • 如果任何一个元素不满足闭包 f 的条件,则返回 false

(3)迭代器

let chars_left = vec![false, true, false, true];
原理:

创建迭代器:

let iter = chars_left.iter();

迭代示例:

let first = iter.next();  // Some(&false)
let second = iter.next(); // Some(&true)
let third = iter.next();  // Some(&false)
let fourth = iter.next(); // Some(&true)
let none = iter.next();   // None

用法:
  • 创建迭代器:通过调用集合的 iteriter_mutinto_iter 方法创建迭代器。
  • 遍历:使用 for 循环或 while let 语句。
  • 常用方法
    • 转换mapfilterenumeratezip
    • 收集collectfold
    • 检查allany
  • 链式调用:将多个迭代器方法链式调用以实现复杂的数据处理。
  • (1)转换
    • map():对每个元素应用一个函数,返回一个新的迭代器。
    • filter():过滤符合条件的元素,返回一个新的迭代器。
    • enumerate():为迭代器中的每个元素生成一个索引,返回 (索引, 元素) 对。
    • zip():将两个迭代器合并为一个新的迭代器,生成 (元素1, 元素2) 对。
  • (2)收集
    • collect():将迭代器的所有元素收集到一个集合类型中,通常是一个向量(Vec<T>)。
    • fold():将迭代器的所有元素通过一个累积函数聚合为一个值。
  • (3)检查
    • all():检查是否所有元素都满足一个条件。
    • any():检查是否有任意元素满足一个条件。
  • (4)链式调用

        将多个迭代器方法链式调用,以实现复杂的数据处理。例如:过滤、映射和收集的组合。

 例子:
fn main() {
    let vec = vec![1, 2, 3, 4, 5];

    // 使用迭代器遍历元素
    for val in vec.iter() {
        println!("{}", val);
    }

    // 使用链式调用过滤和映射元素,然后收集结果
    let processed: Vec<i32> = vec.iter()
        .filter(|&&x| x % 2 == 0)  // 过滤出偶数
        .map(|&x| x * 2)           // 将每个偶数乘以 2
        .collect();                // 收集结果到一个向量

    println!("{:?}", processed); // 输出: [4, 8]
}

 (4)枚举类型

(1)Option类型

在 Rust 中,Option 类型是一种枚举,用于表示一个值可能存在(Some)或者不存在(None

fn main() {
    let numbers = vec![1, 2, 3];
    let empty: Vec<i32> = Vec::new();

    match get_first_element(&numbers) {
        Some(value) => println!("第一个元素是: {}", value),
        None => println!("数组为空"),
    }

    match get_first_element(&empty) {
        Some(value) => println!("第一个元素是: {}", value),
        None => println!("数组为空"),
    }
}
(2)Result类型

Result<T, E>:

  • 用于表示一个操作的成功或失败。
  • 在 Rust 中,Result 枚举类型需要两个类型参数:

  • Result<T, E>:表示操作的结果。
    • Ok(T):表示操作成功,包含类型 T 的值。
    • Err(E):表示操作失败,包含类型 E 的错误信息。
  • fn read_file_lines(filename: &str) -> Result<Vec<String>, io::Error> {
        let file = File::open(filename)?;
        let reader = BufReader::new(file);
        let mut lines = Vec::new();
    
        for line in reader.lines() {
            let line = line?;
            lines.push(line);
        }
    
        Ok(lines)
    }
    

 使用 Ok 包装一个值时,你实际上是在创建一个 Result 类型的实例,表示操作成功,并返回该值作为 Result 的成功变体。

(5)match 表达式

match 表达式是 Rust 中用于模式匹配的强大工具。它可以根据不同的模式执行不同的代码分支。

match value {
    pattern1 => expr1,
    pattern2 => expr2,
    _ => expr3, // 通配模式,匹配所有其他情况
}

(6)读取文件 

在 Rust 中读取文件的流程通常包括以下步骤:

  1. 导入必要的模块:包括文件系统和 I/O 操作的模块。
  2. 打开文件:使用 std::fs::File::open 方法打开文件,并处理可能的错误。
  3. 创建缓冲读取器(可选):如果逐行读取文件内容,可以使用 std::io::BufReader 创建一个缓冲读取器。
  4. 读取文件内容:根据需要选择读取文件内容的方法,例如逐行读取、一次性读取到字符串、一次性读取到字节数组等。
  5. 处理文件内容:对读取到的文件内容进行处理。
  6. 错误处理:在读取和处理文件内容的过程中,处理可能的错误。
方法一:逐行读取文件内容

关键点:使用 BufReaderlines 方法逐行读取文件

use std::fs::File;
use std::io::{self, BufRead, BufReader};

fn read_file_lines(filename: &str) -> Result<Vec<String>, io::Error> {
    let file = File::open(filename)?; // 打开文件
    let reader = BufReader::new(file); // 创建缓冲读取器
    reader.lines().collect() // 逐行读取并收集结果
}

fn main() {
    match read_file_lines("example.txt") {
        Ok(lines) => lines.iter().for_each(|line| println!("{}", line)),
        Err(e) => eprintln!("Error reading file: {}", e),
    }
}
方法二:一次性读取整个文件内容到字符串

关键点:使用 read_to_string 方法一次性读取整个文件内容

use std::fs::File;
use std::io::{self, Read};

fn read_file_to_string(filename: &str) -> Result<String, io::Error> {
    let mut file = File::open(filename)?; // 打开文件
    let mut contents = String::new();
    file.read_to_string(&mut contents)?; // 读取文件内容到字符串
    Ok(contents)
}

fn main() {
    match read_file_to_string("example.txt") {
        Ok(contents) => println!("{}", contents),
        Err(e) => eprintln!("Error reading file: {}", e),
    }
}
方法三:一次性读取整个文件内容到字节数组

关键点:使用 read_to_end 方法一次性读取整个文件内容到字节数组

use std::fs::File;
use std::io::{self, Read};

fn read_file_to_bytes(filename: &str) -> Result<Vec<u8>, io::Error> {
    let mut file = File::open(filename)?; // 打开文件
    let mut contents = Vec::new();
    file.read_to_end(&mut contents)?; // 读取文件内容到字节数组
    Ok(contents)
}

fn main() {
    match read_file_to_bytes("example.txt") {
        Ok(contents) => println!("{:?}", contents),
        Err(e) => eprintln!("Error reading file: {}", e),
    }
}
方法四:使用 std::fs::read_to_string 直接读取整个文件到字符串

关键点:使用 fs::read_to_string 直接读取文件内容到字符串

use std::fs;

fn read_file_to_string(filename: &str) -> Result<String, std::io::Error> {
    fs::read_to_string(filename) // 直接读取文件内容到字符串
}

fn main() {
    match read_file_to_string("example.txt") {
        Ok(contents) => println!("{}", contents),
        Err(e) => eprintln!("Error reading file: {}", e),
    }
}
方法五:使用 std::fs::read 直接读取整个文件到字节数组

关键点:使用 fs::read 直接读取文件内容到字节数组

use std::fs;

fn read_file_to_bytes(filename: &str) -> Result<Vec<u8>, std::io::Error> {
    fs::read(filename) // 直接读取文件内容到字节数组
}

fn main() {
    match read_file_to_bytes("example.txt") {
        Ok(contents) => println!("{:?}", contents),
        Err(e) => eprintln!("Error reading file: {}", e),
    }
}
总结
  1. 逐行读取文件内容:使用 BufReaderlines 方法。
  2. 一次性读取整个文件内容到字符串:使用 read_to_string 方法。
  3. 一次性读取整个文件内容到字节数组:使用 read_to_end 方法。
  4. 直接读取整个文件到字符串:使用 fs::read_to_string 方法。
  5. 直接读取整个文件到字节数组:使用 fs::read 方法。

RefCell 概括

RefCell 是 Rust 提供的一种类型,用于在不可变的上下文中实现内部可变性。它允许你在运行时执行借用检查,以确保安全地修改数据。这在某些数据结构(如链表)和特定场景(如闭包或异步编程)中非常有用。

核心特点
  1. 内部可变性

    • 允许在不可变的上下文中修改数据。
    • 使用 borrow() 获取不可变引用。
    • 使用 borrow_mut() 获取可变引用。
  2. 运行时借用检查

    • 在借用时进行运行时检查,确保借用规则不被违反。
    • 如果在借用过程中违反规则,会导致运行时错误。
  3. 典型用法

    • 适用于实现复杂数据结构,如链表、图等需要相互引用的结构。
    • 适用于跨越函数的借用,尤其在闭包和异步编程中。
示例代码
use std::cell::RefCell;

let x = RefCell::new(5);
{
    let y = x.borrow();
    println!("y: {}", *y);  // 输出: y: 5
}
{
    let mut z = x.borrow_mut();
    *z = 10;
    println!("x: {}", x.borrow());  // 输出: x: 10
}

Rc 的核心作用概括

Rc(Reference Counted)是 Rust 提供的一种智能指针,允许多个所有者共享同一个数据

核心特点
  1. 共享所有权

    • 允许多个变量同时拥有同一个数据。
    • 适用于需要在多个地方访问和使用同一个数据的场景。
  2. 自动管理内存

    • 通过引用计数管理数据的生命周期。
    • 当最后一个引用被删除时,数据会自动释放。
  3. 单线程环境

    • 只能在单线程环境中使用。
    • 如果需要在多线程环境中共享数据,使用 Arc(Atomic Reference Counted)。
使用场景
  1. 数据共享

    • 例如,在树或图数据结构中,多个节点可以共享同一个子节点。
  2. 不可变数据

    • 通常用于共享不可变数据,因为 Rc 默认不允许多个可变引用。
    • 如果需要修改数据,可以结合 RefCell 使用。
示例代码
use std::rc::Rc;

fn main() {
    let data = Rc::new(5);  // 创建一个 Rc 指针,包含数据 5

    let data1 = Rc::clone(&data);  // 创建 data 的克隆引用
    let data2 = Rc::clone(&data);  // 创建 data 的另一个克隆引用

    println!("Reference count: {}", Rc::strong_count(&data));  // 输出: 3
    println!("data: {}", data);
    println!("data1: {}", data1);
    println!("data2: {}", data2);
}
Rc 的销毁时机

对于 Rc(Reference Counted)智能指针,当一个 Rc 实例超出其作用域时,引用计数会自动减少。如果引用计数减少到零,Rc 管理的数据将被释放。

use std::cell::RefCell;
use std::rc::Rc;

fn main() {
    {
        let data = Rc::new(RefCell::new(5));  // 创建一个包含 RefCell 的 Rc 指针
        println!("Initial reference count: {}", Rc::strong_count(&data));  // 输出: 1

        {
            let data1 = Rc::clone(&data);  // 克隆 Rc 指针,引用计数增加到 2
            println!("Reference count after creating data1: {}", Rc::strong_count(&data));  // 输出: 2

            {
                let data2 = Rc::clone(&data);  // 再次克隆 Rc 指针,引用计数增加到 3
                println!("Reference count after creating data2: {}", Rc::strong_count(&data));  // 输出: 3

                *data2.borrow_mut() = 10;  // 修改数据
                println!("Modified data through data2: {}", data.borrow());  // 输出: 10
            }  // data2 超出作用域,引用计数减少到 2
            println!("Reference count after data2 goes out of scope: {}", Rc::strong_count(&data));  // 输出: 2

        }  // data1 超出作用域,引用计数减少到 1
        println!("Reference count after data1 goes out of scope: {}", Rc::strong_count(&data));  // 输出: 1

    }  // data 超出作用域,引用计数减少到 0,数据被释放
    // 由于 data 已经被释放,不能再访问它
}

3.Rust特性

(1)所有权

所有权机制是Rust用来管理内存的一种系统,它确保了内存安全性并防止了许多常见的编程错误。以下是所有权机制的核心概念和规则:

1. 所有权规则
  • 每个值在Rust中都有一个所有者

    • 所有者是一个变量,只有一个变量可以是某个值的所有者。
  • 值在任一时刻只能有一个所有者

    • 当所有者变量超出作用域时,该值将被自动清理。
  • 当所有者离开作用域时,该值将被丢弃

    • Rust在所有者超出作用域时自动调用drop函数来释放内存。
2. 所有权转移(Move)
  • (1)非 Copy trait 的类型赋值
    let s = String::from("hello");
    let s1 = s;
    • 在这个例子中,s的栈上的数据所有权被转移给 s1(堆上数据仍然不变,移动语义),因此在之后使用s1会导致编译错误。
  • (2)self 作为参数

方法或函数以 self 作为参数时,会获取调用者的所有权,调用后原变量失效。

  • (3) 非引用参数

类似地,函数以非引用类型参数接收变量时,也会获取其所有权。

  • (4)克隆
let s = String::from("hello");
let s1 = s.clone();

在Rust中,使用clone方法可以进行深拷贝。深拷贝会复制堆上的数据,并在栈上创建一个新的所有权指向这块堆内存。结果是栈上和堆上都有独立的拷贝,因此两个变量互不影响。 

  • (5) Copy trait 的类型
  • 整数类型 (i32, u32, 等)
  • 浮点数类型 (f32, f64)
  • 布尔类型 (bool)
  • 字符类型 (char)
  • 元组(如果元组内的所有元素都实现了 Copy trait)
  • (6)无法copy的类型
  • 非Copy类型

    • 复杂类型如String不实现Copy特性,因为它们涉及更复杂的内存管理。
    • 不能对不实现Copy的类型进行直接赋值拷贝。
  • 解决方案

    • 使用引用:对于无法实现Copy的类型,可以通过引用来解决所有权冲突。
    • fn main() {
          let x = (1, 2, (), "hello".to_string());
          let y = (&x.0, &x.1, &x.2, &x.3);
          println!("{:?}, {:?}", x, y);
      }
      

3. 借用(Borrowing)

引用的可变性决定了你是否可以通过引用来修改所引用的值。

  • (1)不可变借用
    • 可以有多个不可变引用,但不能同时有可变引用。
    let s = String::from("hello");
    let r1 = &s; // 不可变引用 r1
    let r2 = &s; // 不可变引用 r2
    
    println!("r1: {}, r2: {}", r1, r2); // 可以同时使用多个不可变引用
    
  • (2)可变借用
    • 同一时间只能有一个可变引用,且不能同时存在不可变引用。
    let mut s = String::from("hello");
    let r1 = &mut s; // 可变引用 r1
    
    r1.push_str(", world");
    
    println!("{}", r1); // r1 修改了 s 的内容
    
  1. 唯一的可变引用:在任何给定的时间点,一个变量只能有一个可变引用(&mut)。
  2. 不可变引用与可变引用互斥:在有可变引用存在时,不允许同时存在不可变引用(&)。反之,在存在不可变引用时,不允许存在可变引用。

这些规则确保了在访问和修改数据时不会出现竞争条件。

(3)注意:

1.不允许在存在不可变引用时修改原始变量 
fn main() {
    let mut s = String::from("hello"); // 可变变量 `s` 被创建
    let ref1 = &s; // 创建对 `s` 的不可变引用 `ref1`
    s = String::from("goodbye"); // 尝试修改 `s` 的值
    println!("{}", ref3.to_uppercase()); // 使用 `ref3` 打印 `s` 的值
}

一种修复方法是将 println! 语句移动到修改 s 之前,确保在修改 s 之前,所有的不可变引用都已经被使用完毕。例如:

fn main() {
    let mut s = String::from("hello");
    let ref1 = &s;
    let ref2 = &ref1;
    let ref3 = &ref2;
    println!("{}", ref3.to_uppercase()); // 在修改 `s` 之前使用 `ref3`
    s = String::from("goodbye"); // 现在可以安全地修改 `s`
}
2. 悬垂引用

返回的是一个局部变量的引用,函数作用域结束后,变量销毁

fn drip_drop() -> &String {
    let s = String::from("hello world!");
    return &s;
}

修改方法:直接返回所有权

fn drip_drop() -> String {
    let s = String::from("hello world!");
    return s;
}
 3.借用检查器错误

v[0]返回一个引用,试图将向量中元素的引用赋值给一个所有权变量 

fn main() {
    let s1 = String::from("hello");
    let mut v = Vec::new();
    v.push(s1);
    let s2: String = v[0];  // 试图移动元素的所有权
    println!("{}", s2);
}

 解决方法:(仅读取)

fn main() {
    let s1 = String::from("hello");
    let mut v = Vec::new();
    v.push(s1);
    let s2: &String = &v[0];
    println!("{}", s2);
}
4.切片

切片(slice)是Rust中对数组、字符串等集合部分数据的引用。它具有以下核心特性:

  1. 引用类型:不拥有数据所有权,只是借用数据的一部分。
  2. 不可变和可变:支持不可变切片(&[T])和可变切片(&mut [T])。
  3. 高效:避免数据拷贝,直接引用原数据。
  4. 安全:编译时和运行时边界检查,防止越界访问和数据竞争。
(1)不可变切片
fn main() {
    let arr = [1, 2, 3, 4, 5];
    let slice = &arr[1..4]; // 引用数组的部分数据
    println!("{:?}", slice); // 输出 [2, 3, 4]
}
(2)可变切片
fn main() {
    let mut arr = [1, 2, 3, 4, 5];
    let slice = &mut arr[1..3]; // 可变切片,引用数组的部分数据
    slice[0] = 10;
    println!("{:?}", arr); // 输出 [1, 10, 3, 4, 5]
}
(3)字符串切片
fn main() {
    let s = String::from("hello, world");
    let hello = &s[0..5]; // 引用字符串的部分数据
    println!("{}", hello); // 输出 "hello"
}
(4)切片操作
fn main() {
    let arr = [1, 2, 3, 4, 5];
    let slice = &arr[1..4];
    println!("Length: {}", slice.len()); // 输出 "Length: 3"
    println!("First element: {:?}", slice.first()); // 输出 "First element: Some(2)"
}

5. 数据竞争的避免
  • Rust的借用检查器在编译时强制执行借用规则,以确保在任何给定时间只有一个可变引用或多个不可变引用,从而避免数据竞争。
5. 生命周期
  • Rust通过生命周期标注来确保引用的有效性,防止悬空引用。
     

(2)显式使用引用操作符

常见情况

  1. 创建引用

    • 当你需要创建一个变量的引用时,需要显式地使用 &
    let x = 5;
    let y = &x; // 创建对 x 的不可变引用
    let z = &mut x; // 创建对 x 的可变引用(需要 x 是可变的)
    
  2. 函数参数传递引用

    • 当你定义一个函数,并希望它接收一个引用作为参数时,需要显式地使用 &
    fn print_value(value: &i32) {
        println!("{}", value);
    }
    
    let x = 10;
    print_value(&x); // 传递 x 的引用
    
  3. 解引用

    • 当你需要从一个引用中获取实际值时,需要显式地使用 *
    let x = 5;
    let y = &x;
    println!("{}", *y); // 解引用 y 获取 x 的值
    

注意和习惯

基本类型

为什么要手动设置变量可变性
  • Rust支持可变和不可变变量,提供了灵活性和安全性,性能优化
  • 将无需改变的变量声明为不可变,可以提升运行性能,避免多余的运行时检查。
变量命名
  • 使用下划线开头的变量名,可以忽略未使用变量的警告。
  • let表达式可以用于变量解构,从复杂变量中匹配出部分内容。
整形溢出处理
  • Rust提供了多种方法显式处理整型溢出,如wrapping_*checked_*overflowing_*saturating_*
  • wrapping_* 方法

    • 描述:当发生溢出时,值会按照二进制补码环绕(wrap around)。这意味着溢出后的结果将从最低有效位开始重新计算。
    • 用法wrapping_addwrapping_subwrapping_mul等。
    • 示例
      let x: u8 = 255;
      let y = x.wrapping_add(1); // y == 0
      
    • 核心:溢出后环绕,继续计算,不会引发程序错误。
  • checked_* 方法

    • 描述:当发生溢出时,返回一个None,否则返回Some(结果)。适合需要检测并处理溢出的情况。
    • 用法checked_addchecked_subchecked_mul等。
    • 示例
      let x: u8 = 255;
      if let Some(y) = x.checked_add(1) {
          // 不会执行
      } else {
          println!("溢出检测到");
      }
      
    • 核心:通过返回Option类型来检测和处理溢出。
  • overflowing_* 方法

    • 描述:返回一个包含计算结果和布尔值的元组,布尔值指示是否发生溢出。
    • 用法overflowing_addoverflowing_suboverflowing_mul等。
    • 示例
      let x: u8 = 255;
      let (y, overflowed) = x.overflowing_add(1); // y == 0, overflowed == true
      
    • 核心:提供溢出后的结果,并显式指示溢出是否发生。
  • saturating_* 方法

    • 描述:当发生溢出时,值会被夹紧到类型的最大或最小值。适合需要确保结果在一定范围内的情况。
    • 用法saturating_addsaturating_subsaturating_mul等。
    • 示例
      let x: u8 = 255;
      let y = x.saturating_add(1); // y == 255
      
    • 核心:溢出后结果被限制在合法范围内(最大或最小值)。
数字字面量下划线

在Rust中,数字字面量中的下划线(_)可以用于增加可读性,它们不会影响数值的实际值。

示例:1_000.000_1 表示 1000.0001

Rust字符

Rust 的字符不仅仅是 ASCII,所有的 Unicode 值都可以作为 Rust 字符,包括单个的中文、日文、韩文、emoji 表情符号等等,都是合法的字符类型。Unicode 值的范围从 U+0000 ~ U+D7FF 和 U+E000 ~ U+10FFFF

由于 Unicode 都是 4 个字节编码,因此字符类型也是占用 4 个字节:

单元类型()

在Rust中,单元类型 () 表示空值或空元组,通常用于函数不返回任何值的情况。尽管逻辑上是空的,但它在内存中占用的大小为0字节。使用 std::mem::size_of_val 可以确认这一点,例如 assert!(size_of_val(&unit) == 0);,这保证了 unit 的内存占用为0,体现了Rust中零大小类型(ZST)的概念和用途。

永远不会返回的函数(发散函数)
  • 发散函数:返回类型为!,表示函数永远不会正常返回控制权。
  • 实现方法
    1. 无限循环:使用loop {}创建一个永不退出的循环。
    2. panic!:触发一个恐慌,使程序中止。
    3. std::process::exit:立即终止程序并返回指定的状态码。

// 方法一:使用无限循环
fn never_return_fn() -> ! {
    loop {
        // 无限循环,永远不会返回
    }
}

// 方法二:调用panic!
fn never_return_fn() -> ! {
    panic!("This function never returns!");
}

// 方法三:使用std::process::exit
use std::process;

fn never_return_fn() -> ! {
    process::exit(1); // 退出程序并返回状态码1
}
当所有权转移时,可变性也可以随之改变。
let x = 5; // 不可变变量
let mut y = x; // 所有权转移,y 变为可变
y += 1; // 修改 y 的值
部分移动
  • 部分移动

    • 解构时可以同时移动变量的一部分,并借用另一部分。
    • 被移动部分的所有权转移,原变量不能再使用该部分。
    • 被借用部分仍然可以通过引用使用。
  • 原变量状态

    • 整体不能再使用,因为部分所有权已转移。
    • 未转移所有权的部分仍可通过引用使用。

fn main() {
   let t = (String::from("hello"), String::from("world"));

   let _s = t.0;

   // 仅修改下面这行代码,且不要使用 `_s`
   println!("{:?}", t.1);
}
Ref和&的区别
  • 使用场景

    • & 是直接引用,用于创建一个指向某个值的引用,适用于任何需要引用的地方。
    • ref 主要在模式匹配中使用,用于方便地在模式匹配过程中获取某个值的引用。
  • 代码简洁性和可读性

    • 使用 & 创建引用时,代码逻辑清晰,直接指向某个值,易于理解。
    • 使用 ref 在模式匹配中创建引用,可以使模式匹配的代码更加简洁和直观,避免了在模式匹配外部手动创建引用的繁琐。

当你在模式匹配中需要创建多个嵌套值的引用时,ref 可以大大简化代码的编写和阅读。例如:

struct Point {
    x: i32,
    y: i32,
}

let p = Point { x: 10, y: 20 };

match p {
    Point { ref x, ref y } => {
        // 在这里 x 和 y 都是引用
        println!("x: {}, y: {}", x, y);
    }
}

在这个例子中,使用 ref 可以直接在模式匹配中创建 xy 的引用。如果不使用 ref,你需要手动创建引用,这样会使代码变得更复杂:

struct Point {
    x: i32,
    y: i32,
}

let p = Point { x: 10, y: 20 };

let Point { x, y } = p;
let x_ref = &x;
let y_ref = &y;

println!("x: {}, y: {}", x_ref, y_ref);
进行切片时需要注意字符边界

UTF-8 编码的字符可能占用多个字节,切片操作必须在字符边界上进行,否则程序会崩溃。

核心要点:

  1. UTF-8 编码:字符可能占用1至4个字节,汉字通常占3个字节。
  2. 字符边界:切片索引必须对齐到字符边界。
  3. 避免崩溃:切片时需确保索引在字符边界,否则会导致程序崩溃。
  4. 使用工具:可以使用 .char_indices() 方法获取字符边界,确保切片安全。

示例:

let s = "中国人";
let a = &s[0..3]; // 正确的切片,取第一个汉字 "中"
println!("{}", a); // 输出 "中"

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/799981.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

WebSocket、服务器推送技术

WebSocket 是一种在单个 TCP 连接上进行 全双工 通信的协议&#xff0c;它可以让客户端和服务器之间进行实时的双向通信&#xff0c;且不存在同源策略限制 WebSocket 使用一个长连接&#xff0c;在客户端和服务器之间保持持久的连接&#xff0c;从而可以实时地发送和接收数据…

区块链与云计算的融合:新时代数据安全的挑战与机遇

随着信息技术的迅猛发展&#xff0c;云计算和区块链技术作为两大前沿技术在各自领域内展示出了巨大的潜力。而它们的结合&#xff0c;即区块链与云计算的融合&#xff0c;正在成为数据安全领域的新趋势。本文将探讨这一融合对数据安全带来的挑战和机遇&#xff0c;以及其在企业…

django实现用户的注册、登录、注销功能

创建django项目的步骤&#xff1a;Django项目的创建步骤-CSDN博客 一、前置工作 配置数据库&#xff0c;设置数据库引擎为mysql 1、在settings文件中找到DATABASES, 配置以下内容 DATABASES {"default": {ENGINE: django.db.backends.mysql, # 数据库引擎NAME: dja…

【数据结构与算法】希尔排序:基于插入排序的高效排序算法

&#x1f493; 博客主页&#xff1a;倔强的石头的CSDN主页 &#x1f4dd;Gitee主页&#xff1a;倔强的石头的gitee主页 ⏩ 文章专栏&#xff1a;《数据结构与算法》 期待您的关注 ​ 目录 一、引言 二、基本原理 三、实现步骤 四、C语言实现 五、性能分析 1. 时间复杂度…

Hadoop3:HDFS-查看logs文件,排查NameNode故障原因。

问题一、NameNode进程退出 我们发现&#xff0c;NameNode进程不存在。 情况1、单独启动NameNode hdfs --daemon start namenode能够正常拉起&#xff0c;那就没问题 情况2、无法独立启动NameNode 先尝试启动NameNode hdfs --daemon start namenode发现&#xff0c;没能成…

el-date-picker手动输入日期,通过设置开始时间和阶段自动填写结束时间

需求&#xff1a;根据开始时间&#xff0c;通过填写阶段时长&#xff0c;自动填写结束时间&#xff0c;同时开始时间和节数时间可以手动输入 代码如下&#xff1a; <el-form ref"ruleForm2" :rules"rules2" :model"formData" inline label-po…

Redis深度解析:从基础到高级特性,剖析关键技术

一、关于Redis Redis介绍 REmote DIctionary Server(Redis) 是一个由 Salvatore Sanfilippo 写的 key-value 存储系统&#xff0c;是跨平台的非关系型数据库。 Redis 是一个开源的使用 ANSIC 语言编写、遵守 BSD&#xff08;开源协议&#xff09; 协议、支持网络、可基于内存…

指令判断数据更改,文本变色

默认数据是这样&#xff0c;如果更改了其中一个&#xff0c;文本框变成红色 <el-form-item label"Activity name"><el-inputv-model"form.name"v-highlight"datas[name]"input"changeValue(name)"/></el-form-item>…

excel系列(二) - 利用 easypoi 快速实现 excel 文件导入导出

一、介绍 在上篇文章中&#xff0c;我们介绍了 apache poi 工具实现 excel 文件的导入导出。 本篇我们继续深入介绍另一款优秀的 excel 工具库&#xff1a;easypoi。 二、easypoi 以前的以前&#xff0c;有个大佬程序员&#xff0c;跳到一家公司之后就和业务人员聊上了&…

智慧水利:迈向水资源管理的新时代,结合物联网、云计算等先进技术,阐述智慧水利解决方案在提升水灾害防控能力、优化水资源配置中的关键作用

本文关键词&#xff1a;智慧水利、智慧水利工程、智慧水利发展前景、智慧水利技术、智慧水利信息化系统、智慧水利解决方案、数字水利和智慧水利、数字水利工程、数字水利建设、数字水利概念、人水和协、智慧水库、智慧水库管理平台、智慧水库建设方案、智慧水库解决方案、智慧…

LLM-阿里 DashVector + langchain self-querying retriever 优化 RAG 实践【Query 优化】

文章目录 前言self querying 简介代码实现总结 前言 现在比较流行的 RAG 检索就是通过大模型 embedding 算法将数据嵌入向量数据库中&#xff0c;然后在将用户的查询向量化&#xff0c;从向量数据库中召回相似性数据&#xff0c;构造成 context template, 放到 LLM 中进行查询…

HCIE是什么等级的证书?

HCIE&#xff08;华为认证互联网专家&#xff0c;Huawei Certified Internetwork Expert&#xff09;是华为认证体系中的最高等级证书。它要求考生具备在复杂网络环境中规划、设计、部署、运维和优化网络的能力。HCIE认证是华为认证体系中最具挑战性和含金量的认证之一&#xf…

MWA(Modern Web App)初学那些事-2-Basic HTML CSS

初学MWA(Modern Web App&#xff09;那些事-2-Basic HTML & CSS 目录 初学MWA(Modern Web App&#xff09;那些事-2-Basic HTML & CSS前言一、本节学习目标二、HTML基础内容2.1关键元素2.4 Scripts 三、CSS 基础内容3.1 级联样式表-用于设置网页样式和布局3.2 CSS规则语…

cuda缓存示意图

一、定义 cuda 缓存示意图gpu 架构示意图gpu 内存访问示意图 二、实现 cuda 缓存示意图 DRAM: 通常指的是GPU的显存&#xff0c;位于GPU芯片外部&#xff0c;通过某种接口&#xff08;如PCIE&#xff09;与GPU芯片相连。它是GPU访问的主要数据存储区域&#xff0c;用于存储大…

Day53:图论 岛屿数量 岛屿的最大面积

99. 岛屿数量 时间限制&#xff1a;1.000S 空间限制&#xff1a;256MB 题目描述 给定一个由 1&#xff08;陆地&#xff09;和 0&#xff08;水&#xff09;组成的矩阵&#xff0c;你需要计算岛屿的数量。岛屿由水平方向或垂直方向上相邻的陆地连接而成&#xff0c;并且四周…

IP地址与物理地址:网络通信的基础详解

在学习网络通信时&#xff0c;理解IP地址与物理地址&#xff08;也称为硬件地址&#xff09;的区别至关重要。这篇文章将为你解答这些基本概念&#xff0c;并帮助你更好地掌握网络通信的基础。 什么是IP地址和物理地址&#xff1f; IP地址是网络层的逻辑地址&#xff0c;用于标…

《书生大模型实战营第3期》入门岛 学习笔记与作业:Linux 基础知识

文章大纲 Linux 系统简介系统简介为啥正儿八经的深度学习都用 Linux&#xff1f; Linux 基础命令3.1 文件管理3.2 进程管理3.3 工具使用 LinuxInternStudio1. InternStudio开发机介绍2. SSH及端口映射2.1 什么是SSH&#xff1f;2.2 如何使用SSH远程连接开发机&#xff1f;2.2.1…

VUE前端HTML静默打印(不弹出打印对话框)PDF简单方案

前言 在做打印功能的时候&#xff0c;以前大部分客户端都是用C#做的&#xff0c;静默打印&#xff08;也就是不弹出打印对话框&#xff09;比较简单。 但是使用浏览器作为客户端&#xff0c;静默打印&#xff08;也就是不弹出打印对话框&#xff09;做起来就比较困难。困难的…

Mac Dock栏多屏幕漂移固定的方式

记录一下 我目前的版本是 14.5 多个屏幕&#xff0c;Dock栏切换的方式&#xff1a; 把鼠标移动到屏幕的中间的下方区域&#xff0c;触到边边之后&#xff0c;继续往下移&#xff0c;就能把Dock栏固定到当前屏幕了。

教学原则及方法

直观性原则 启发性原则 循序渐进性原则 巩固性原则 量力性原则 思想性与科学性相统一原则 理论联系实际原则 因材施教原则