» Rust快速入门 » 3. 标准库篇 » 3.1 标准库常用类型

标准库常用类型

Box

在 Rust 中,默认情况下,所有的值都是栈分配的。通过创建 Box<T>,可以将值分配在堆上。Box 是指向堆上分配的 T 类型值的智能指针。

可以使用 * 运算符对已经装箱(Boxed)的值进行解引用。

// 定义一个结构体
struct Person {
    name: String,
    age: u32,
}

// 创建一个装箱的 Person 的函数
fn create_person(name: &str, age: u32) -> Box<Person> {
    let person = Person {
        name: String::from(name),
        age,
    };

    // 将 person 装箱并返回 Box
    Box::new(person)
}

fn main() {
    let boxed_person = create_person("Alice", 30);

    // 通过解引用访问字段
    println!("Name: {}, Age: {}", boxed_person.name, (*boxed_person).age);
    // Name: Alice, Age: 30
    // The memory for the person is automatically deallocated when the box goes out of scope
}

Vectors 向量

向量是可调整大小的数组。与切片一样,它们的大小在编译时是未知的,不同的是它们可以随时增长或缩小。

fn main() {
    // 创建一个带有初始值的向量
    let mut numbers: Vec<i32> = vec![1, 2, 3, 4, 5];

    // 访问元素
    println!("Original Vector: {:?}", numbers);

    // 向向量添加一个元素
    numbers.push(6);
    println!("After pushing 6: {:?}", numbers);

    // 移除最后一个元素
    let popped = numbers.pop();
    println!("Popped element: {:?}", popped);
    println!("After popping: {:?}", numbers);

    // 迭代向量
    println!("Doubling each element:");
    for num in &mut numbers {
        *num *= 2;
        println!("{}", num);
    }

    // 创建一个新的向量并将其与原向量拼接起来
    let more_numbers = vec![7, 8, 9];
    numbers.extend(more_numbers);
    println!("After extending with more_numbers: {:?}", numbers);

    // 使用 `contains` 方法
    let contains_8 = numbers.contains(&8);
    println!("Does the vector contain 8? {}", contains_8);

    // 使用 `map` 方法创建平方的向量
    let squares: Vec<i32> = numbers.iter().map(|&x| x * x).collect();
    println!("Vector of squares: {:?}", squares);
}

Strings 字符串

在Rust中有两种类型的字符串:String&str

String 存储为字节向量 (Vec<u8>),它在堆上分配,可增长且可变。

fn main() {
    // 创建一个新的空字符串
    let mut my_string = String::new();

    // 添加字面字符串
    my_string.push_str("Hello, ");

    // 添加另一个字符串
    let name = "Alice";
    my_string.push_str(name);

    // 显示结果
    println!("String: {}", my_string);

    // 将 String 转换为 &str 进行借用
    let borrowed_str: &str = &my_string;
    println!("Borrowed &str: {}", borrowed_str);
}

&str 是一个切片 (&[u8]),始终指向有效的 UTF-8 序列,它可以引用 String 的一部分,或者字符串字面值,甚至另一个 &str

fn main() {
    // Creating a string literal
    let hello_str: &str = "Hello, ";

    // Creating another string literal
    let name_str: &str = "Bob";

    // Concatenating string literals to form a new &str
    let greeting: &str = hello_str.to_owned() + name_str;

    // Displaying the result
    println!("Concatenated &str: {}", greeting);
}

字符串转义

有多种方法可以在字符串文字中编写带有特殊字符的字符串。通常情况下,特殊字符会用反斜杠字符 \ 转义。

fn main() {
    let orig_str = "Escapes work here: \x3F \u{211D}";
    println!("{}", orig_str);

    let raw_str = r"Escapes don't work here: \x3F \u{211D}";
    println!("{}", raw_str);

    // 如果需要在原始字符串中包含引号,添加一对 #
    let quotes = r#"He said: "It's impossible!""#;
    println!("{}", quotes);

    // 如果需要在字符串中包含 "#,只需在两边使用更多的 #
    let longer_delimiter = r###"It's a hashtag "#rust in it. And even "##!"###;
    println!("{}", longer_delimiter);
}

Option

Option<T> 枚举有两个变体:

  • Some(value): 具有类型 Tvalue 的元组结构体
  • None: 没有值
fn find_even_number(numbers: &[u32]) -> Option<u32> {
    for &num in numbers {
        if num % 2 == 0 {
            return Some(num);
        }
    }
    None
}

fn main() {
    let numbers = vec![1, 3, 5, 2, 8, 9];
    match find_even_number(&numbers) {
        Some(even_num) => println!("Found even number: {}", even_num),
        None => println!("No even number found."),
    }
}

Result

Result<T, E> 枚举也有两个变体:

  • Ok(value): 具有类型 Tvalue
  • Err(reason): 含有类型 E 的失败原因 reason
// 基于条件返回 Result<u32, String> 的函数
fn divide(a: u32, b: u32) -> Result<u32, String> {
    if b == 0 {
        Err(String::from("Cannot divide by zero"))
    } else {
        Ok(a / b)
    }
}

fn main() {
    let result2 = divide(8, 0);
    match result2 {
        Ok(quotient) => println!("Result: Quotient is {}", quotient),
        Err(error) => println!("Result: Error - {}", error),
    }
}

? 运算符是在 Rust 中报错传播的一种简写。它可以在返回 Result 的函数中使用,使得错误处理更为简洁。

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

fn read_file_contents(file_path: &str) -> Result<String, io::Error> {
    // 尝试打开文件
    let mut file = File::open(file_path)?;

    // 将文件内容读入字符串
    let mut contents = String::new();
    file.read_to_string(&mut contents)?;

    Ok(contents)
}

panic!

在 Rust 中,panic! 宏用于在遇到不可恢复的错误时展开堆栈并终止程序,以防止发生危险的未定义行为。

fn main() {
    let divisor = 0;
    if divisor == 0 {
        // 尝试除以零时引发 panic
        panic!("Attempted to divide by zero!");
    }

    // 如果发生 panic,将不会执行到达此处
    let result = 42 / divisor;
    println!("Result: {}", result);
}

HashMap

在 Rust 中,可以使用 std::collections 模块中的 HashMap 数据结构来存储键值对。

use std::collections::HashMap;

fn main() {
    // 创建一个空的 HashMap
    let mut my_map = HashMap::new();

    // 将键值对插入 HashMap
    my_map.insert("one", 1);
    my_map.insert("two", 2);
    my_map.insert("three", 3);
    my_map.insert("literank", 58);

    // 通过键访问值
    match my_map.get("two") {
        Some(&value) => println!("The value of 'two' is: {}", value),
        None => println!("Key 'two' not found."),
    }

    // 更新值
    my_map.insert("three", 30);

    // 遍历 HashMap
    println!("Iterating over the HashMap:");
    for (key, value) in &my_map {
        println!("Key: {}, Value: {}", key, value);
    }

    // 移除键值对
    my_map.remove("literank");

    // 检查键是否存在
    if my_map.contains_key("literank") {
        println!("Key 'literank' is present.");
    } else {
        println!("Key 'literank' is not present.");
    }
}

HashSet

HashSet 数据结构也由 std::collections 模块提供,允许存储一组唯一值。

use std::collections::HashSet;

fn main() {
    // 创建一个空的 HashSet
    let mut my_set = HashSet::new();

    // 将元素插入 HashSet
    my_set.insert("apple");
    my_set.insert("banana");
    my_set.insert("orange");

    // 检查元素是否在 HashSet 中
    if my_set.contains("banana") {
        println!("The set contains 'banana'.");
    } else {
        println!("The set does not contain 'banana'.");
    }

    // 从 HashSet 中移除元素
    my_set.remove("apple");

    // 遍历 HashSet
    println!("Iterating over the HashSet:");
    for fruit in &my_set {
        println!("Fruit: {}", fruit);
    }

    // 清空 HashSet
    my_set.clear();

    // 检查 HashSet 是否为空
    if my_set.is_empty() {
        println!("The HashSet is empty.");
    } else {
        println!("The HashSet is not empty.");
    }
}

Rc(引用计数)

Rc(引用计数)是 std::rc 模块中的类型,通过引用计数提供对值的共享所有权。

use std::rc::Rc;

#[derive(Debug)]
struct Person {
    name: String,
    age: u32,
}

fn main() {
    // 创建包含 Person 的 Rc
    let person = Rc::new(Person {
        name: String::from("Alice"),
        age: 30,
    });

    // 创建对 Rc 的多个引用
    let reference1 = Rc::clone(&person);
    let reference2 = Rc::clone(&person);

    // 打印引用计数
    println!("Reference Count after creation: {}", Rc::strong_count(&person));

    // 通过引用访问打印数据
    println!("Person: {:?}", *reference1);
    println!("Person: {:?}", *reference2);

    // 析构引用
    drop(reference1);
    println!("Reference Count after dropping one reference: {}", Rc::strong_count(&person));

    // 析构最后的引用
    drop(reference2);
    // 引用都没有了之后,Rc 被自动销毁

    // 引用都销毁后尝试访问数据会触发报错
}

Arc(原子引用计数)

一个线程安全的引用计数指针。类型 Arc<T> 提供了对在堆中分配的类型为 T 的值的共享所有权。 当你需要在多个线程之间共享所有权并确保线程安全时,Arc 很有用。它使用原子操作进行引用计数,使其适用于并发编程。

例如:

use std::sync::Arc;
use std::thread;

#[derive(Debug)]
struct Person {
    name: String,
    age: u32,
}

fn main() {
    // 创建包含 Person 的 Arc
    let person = Arc::new(Person {
        name: String::from("Bob"),
        age: 25,
    });

    // 在新线程中创建对 Arc 的引用
    let thread_person = Arc::clone(&person);

    // 生成一个新线程
    let handle = thread::spawn(move || {
        // 通过引用在新线程中访问并打印数据
        println!("Person in the new thread: {:?}", *thread_person);
    });

    // 在主线程中访问并打印数据
    println!("Person in the main thread: {:?}", *person);

    // 等待新线程完成
    handle.join().unwrap();

    // 在线程完成后,当最后一个引用被析构时,Arc 会自动释放
}

代码挑战

修改编辑器中的代码,尝试获取 Alice 的成绩。

Loading...
> 此处输出代码运行结果
上页
下页