Rust入门学习笔记

学习指南: https://github.com/pretzelhammer/rust-blog/blob/master/posts/translations/zh-hans/learning-rust-in-2020.md

基本使用

  1. 创建工程
cargo new greeting 
  1. 更改配置文件的版本:
rustc --version
# rustc 1.79.0 (129f3b996 2024-06-10)
  1. 运行代码
fn main() {
    println!("Hello, world!");
}
cd ./greeting 
cargo build 
cargo run 

语法

参考:
https://fasterthanli.me/articles/a-half-hour-to-learn-rust
https://rustwiki.org/zh-CN/book/title-page.html
https://rustwiki.org/zh-CN/rust-by-example/

在 Rust 中,变量在初始化之前是不能被使用的。这是因为 Rust 强调内存安全,并且确保在使用变量之前必须对其进行初始化。

const + 变量名(rust编译器强制要求全局变量名大写,是的,rust编译器会强制规定这种语言风格)+类型+初始值。
同时,const定义的变量不能是mut,也就是不可修改。这也是出于并发操作安全的考虑。

rustlings通关记录

部分节选自:
https://www.cnblogs.com/Roboduster/p/17781712.html

00_intro

01_variables

  1. variables2:变量出现必须与一个数据做绑定

  2. variables6: 考察类似C语言中的全局变量的使用,const + 变量名(rust编译器强制要求全局变量名大写,是的,rust编译器会强制规定这种语言风格)+类型+初始值。
    同时,const定义的变量不能是mut,也就是不可修改。这也是出于并发操作安全的考虑。

02_functions

  1. functions2:函数参数,在调用函数的括号内必须指定参数类型。
  2. functions5:一个语法糖,如果函数中的某个语句不以分号;结尾,那么就是将这个语句(其实是一个value节点)作为返回值来返回。

03_if

04_primitive_types

  1. primitive_types3:let a = [0; 100];,a.len()
  2. primitive_types4:
let a = [1, 2, 3, 4, 5];
let nice_slice = &a[1..4]; // 左闭右开
  1. primitive_types6:
let numbers = (1, 2, 3);
let second = numbers.1;

05_vecs

tuple: 大小固定,通常存储在栈上。
array: 大小固定,通常存储在栈上。
vec: 动态大小,元素存储在堆上,元数据(例如长度和容量)存储在栈上。
栈访问比堆快很多。

  1. vecs1:
let a = [10, 20, 30, 40]; // Array
let v1 = a.to_vec();
let v2 = vec![10, 20, 30, 40];
  1. vecs2:
input.iter().map(|element| element + 1).collect();
// or
let mut output = Vec::new();
for element in input {
    output.push(element * 2);
}
output
// or
for element in v.iter_mut() {
    *element = *element*2;
}

06_move_semantics

Copy:不可变引用 &T ,例如转移所有权中的最后一个例子,但是注意: 可变引用 &mut T 是不可以 Copy的
https://course.rs/basic/ownership/ownership.html

  1. move_sementics3:
//注意mut的位置
fn fill_vec(mut vec: Vec<i32>) -> Vec<i32> {
    vec.push(88);
    vec
}

07_structs

  1. structs1
//声明
struct ColorRegularStruct {
    red: u8,
    green: u8,
    blue: u8,
}
struct ColorTupleStruct(u8, u8, u8);
struct UnitStruct;
//调用
let green = ColorRegularStruct {red : 0, green: 255, blue: 0}; 
let green = ColorTupleStruct(0, 255, 0);
let unit_struct = UnitStruct;
  1. structs2
let your_order = Order {
    name: String::from("Hacker in Rust"),
    count: 1,
    ..order_template
};

08_enums

  1. enums3
enum Message {
    // TODO: Implement the message variant types based on their usage below.
    Resize { width: u64, height: u64 },
    Move(Point),
    Echo(String),
    ChangeColor(u8, u8, u8),
    Quit
}
fn process(&mut self, message: Message) {
    // TODO: Create a match expression to process the different message
    // variants using the methods defined above.
    match message {
        Message::Resize { width, height } => self.resize(width, height),
        Message::Move(point) => self.move_position(point),
        Message::Echo(s) => self.echo(s),
        Message::ChangeColor(red, green, blue) => self.change_color(red, green, blue),
        Message::Quit => self.quit(),
    }
}

09_strings

  1. strings1
"blue".to_string()
String::from("blue")
  1. strings3
input.trim()
input.to_string() + " world!"
input.replace("cars","balloons")
  1. string4
string_slice("blue");
string("red".to_string());
string(String::from("hi"));
string("rust is fun!".to_owned());// 和to_string()相近
string("nice weather".into());//自动转换类型
string(format!("Interpolation {}", "Station"));
// WARNING: This is byte indexing, not character indexing.
// Character indexing can be done using `s.chars().nth(INDEX)`.
string_slice(&String::from("abc")[0..1]);
string_slice("  hello there ".trim());
string("Happy Monday!".replace("Mon", "Tues"));
string("mY sHiFt KeY iS sTiCkY".to_lowercase());

10_modules

  1. modules2
pub use self::fruits::PEAR as fruit;
  1. modules3
use std::time::{SystemTime,UNIX_EPOCH};

11_hashmaps

  1. hashmaps1
let mut basket = HashMap::new();
// Two bananas are already given for you :)
basket.insert(String::from("banana"), 2);
  1. hashmaps2
for fruit in fruit_kinds {
    basket.entry(fruit).or_insert(10);
}
// or
for fruit in fruit_kinds {
    if !basket.contains_key(&fruit) {
        basket.insert(fruit, 1);
    }
}

12_options

  1. options1
fn maybe_icecream(hour_of_day: u16) -> Option<u16> {
    if hour_of_day >=24{
        None
    }
    else if hour_of_day < 22{
        Some(5)
    } else {
        Some(0)
    }
}
fn raw_value() {
    let icecreams = maybe_icecream(12).unwrap();
}

13_error_handling

  1. errors1 .as_ref() 和 .as_deref()
    .as_ref() 将 Option 转换为 Option<&T>,例如将 Option 转换为 Option<&String>。
    .as_deref() 将 Option 转换为 Option<&U>,例如将 Option 转换为 Option<&str>,因为 String 可以被解引用为 str。
fn generate_nametag_text(name: String) -> Option<String> {
    if name.is_empty() {
        // Empty names aren't allowed.
        None
    } else {
        Some(format!("Hi! My name is {name}"))
    }
}

fn generate_nametag_text(name: String) -> Result<String,String> {
    if name.is_empty() {
        // Empty names aren't allowed.
        Err("Empty names aren't allowed".into())
    } else {
        Ok(format!("Hi! My name is {}", name))
    }
}
  1. errors2
let qty = item_quantity.parse::<i32>()?;
Ok(qty * cost_per_item + processing_fee)

item_quantity 是一个字符串(String 或者 &str)。
parse::() 尝试将 item_quantity 解析为一个 i32 类型的整数。
? 操作符用于错误处理。如果 parse 成功,qty 将是一个 i32 类型的整数;如果 parse 失败,? 会将错误传播给调用者,而不是继续执行代码。
? 相当于 match 语句中的 Result::Ok 或 Result::Err,在失败时直接返回错误。

  1. errors3
let cost = total_cost(pretend_user_input).unwrap();
//or
fn main() -> Result<(), ParseIntError>//修改main函数
Ok(())//main函数返回值

errors3:跟errors2很像,但是报错了,百思不得其解,但是看到编译器的报错信息就大致明白了,因为main函数默认没有返回值,或者说返回值类型是(),在这种函数中不能使用?,故而有两种解决方案:

给main函数增加返回值类型,fn main() -> Result<(), ParseIntError>
不使用?,用unwrap代替:letcost=total_cost(pretend_user_input).unwrap();

  1. error4
impl PositiveNonzeroInteger {
    fn new(value: i64) -> Result<Self, CreationError> {
        // TODO: This function shouldn't always return an `Ok`.
        if value < 0 {
            return Err(CreationError::Negative);
        }
        if value == 0 {
            return Err(CreationError::Zero);
        }
        Ok(Self(value as u64))
    }
}
  1. error5
fn main() -> Result<(), Box<dyn Error>>  {
    let pretend_user_input = "42";
    let x: i64 = pretend_user_input.parse()?;
    println!("output={:?}", PositiveNonzeroInteger::new(x)?);
    Ok(())
}
  1. error6
impl ParsePosNonzeroError {
    fn from_creation(err: CreationError) -> Self {
        Self::Creation(err)
    }
    // TODO: Add another error conversion function here.
    // fn from_parseint(???) -> Self { ??? }
    fn from_parseint(err: ParseIntError) -> Self {
        Self::ParseInt(err)
    }
}
fn parse(s: &str) -> Result<Self, ParsePosNonzeroError> {
    // TODO: change this to return an appropriate error instead of panicking
    // when `parse()` returns an error.
    let x: i64 = s.parse().map_err(ParsePosNonzeroError::from_parseint)?;
    Self::new(x).map_err(ParsePosNonzeroError::from_creation)
}

14_generics

  1. generics1
let mut numbers: Vec<i16> = Vec::new();
let n1: u8 = 42;
numbers.push(n1.into());
let n2: i8 = -1;
numbers.push(n2.into());
  1. generics1
struct Wrapper<T> {
    value: T,
}
impl<T> Wrapper<T> {
    fn new(value: T) -> Self {
        Wrapper { value }
    }
}

15_traits

// 定义一个名为 `Summary` 的 trait,包含一个方法签名 `summarize`
trait Summary {
    fn summarize(&self) -> String;
}

// 为 `NewsArticle` 结构体实现 `Summary` trait
struct NewsArticle {
    headline: String,
    content: String,
}

impl Summary for NewsArticle {
    fn summarize(&self) -> String {
        format!("{}: {}", self.headline, self.content)
    }
}

// 为 `Tweet` 结构体实现 `Summary` trait
struct Tweet {
    username: String,
    content: String,
}

impl Summary for Tweet {
    fn summarize(&self) -> String {
        format!("{}: {}", self.username, self.content)
    }
}

fn main() {
    let article = NewsArticle {
        headline: String::from("Breaking News"),
        content: String::from("Rust is awesome!"),
    };

    let tweet = Tweet {
        username: String::from("rustacean"),
        content: String::from("Learning Rust!"),
    };

    println!("Article Summary: {}", article.summarize());
    println!("Tweet Summary: {}", tweet.summarize());
}
  1. traits4
trait Licensed {
    fn licensing_info(&self) -> String {
        "Default license".to_string()
    }
}

struct SomeSoftware;
struct OtherSoftware;

impl Licensed for SomeSoftware {}
impl Licensed for OtherSoftware {}

// TODO: Fix the compiler error by only changing the signature of this function.
fn compare_license_types(software1: impl Licensed, software2: impl Licensed) -> bool {
    software1.licensing_info() == software2.licensing_info()
}
  1. traits5
fn some_func(item: impl SomeTrait + OtherTrait) -> bool {
    item.some_function() && item.other_function()
}

16_lifetimes

  1. lifetimes1
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}
  1. lifetimes3
// Lifetimes are also needed when structs hold references.

// TODO: Fix the compiler errors about the struct.
struct Book <'a>{
    author: &'a str,
    title: &'a str,
}

fn main() {
    let book = Book {
        author: "George Orwell",
        title: "1984",
    };

    println!("{} by {}", book.title, book.author);
}

17_tests

  1. tests1
// Tests are important to ensure that your code does what you think it should
// do.

fn is_even(n: i64) -> bool {
    n % 2 == 0
}

fn main() {
    // You can optionally experiment here.
}

#[cfg(test)]
mod tests {
    // TODO: Import `is_even`. You can use a wildcard to import everything in
    // the outer module.
    use super::is_even;
    #[test]
    
    fn you_can_assert() {
        // TODO: Test the function `is_even` with some values.
        assert!(!is_even(1));
        assert!(is_even(2));
    }
}
  1. tests2
assert_eq!(power_of_2(0), 1);
  1. test3
struct Rectangle {
    width: i32,
    height: i32,
}

impl Rectangle {
    // Don't change this function.
    fn new(width: i32, height: i32) -> Self {
        if width <= 0 || height <= 0 {
            // Returning a `Result` would be better here. But we want to learn
            // how to test functions that can panic.
            panic!("Rectangle width and height can't be negative");
        }

        Rectangle { width, height }
    }
}

fn main() {
    // You can optionally experiment here.
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn correct_width_and_height() {
        // TODO: This test should check if the rectangle has the size that we
        // pass to its constructor.
        let rect = Rectangle::new(10, 20);
        assert_eq!(rect.width, 10); // Check width
        assert_eq!(rect.height, 20); // Check height
    }
    // TODO: This test should check if the program panics when we try to create
    // a rectangle with negative width.
    #[test]
    #[should_panic(expected = "Rectangle width and height can't be negative")]
    fn negative_width() {
        let _rect = Rectangle::new(-10, 10);
    }
    // TODO: This test should check if the program panics when we try to create
    // a rectangle with negative height.
    #[test]
    #[should_panic(expected = "Rectangle width and height can't be negative")]
    fn negative_height() {
        let _rect = Rectangle::new(10, -10);
    }
    
}

18_iterators

  1. iterators1
let my_fav_fruits = ["banana", "custard apple", "avocado", "peach", "raspberry"];
let mut fav_fruits_iterator = my_fav_fruits.iter();
assert_eq!(fav_fruits_iterator.next(), Some(&"banana"));
  1. iterators2
// In this exercise, you'll learn some of the unique advantages that iterators
// can offer.

// TODO: Complete the `capitalize_first` function.
// "hello" -> "Hello"
fn capitalize_first(input: &str) -> String {
    let mut chars = input.chars();
    match chars.next() {
        None => String::new(),
        Some(first) => first.to_uppercase().collect::<String>() + chars.as_str()
    }
}

// TODO: Apply the `capitalize_first` function to a slice of string slices.
// Return a vector of strings.
// ["hello", "world"] -> ["Hello", "World"]
fn capitalize_words_vector(words: &[&str]) -> Vec<String> {
    let mut vec_string: Vec<String> = Vec::new();
    let mut words_iter = words.iter();
    while let Some(first_word) = words_iter.next() {
        vec_string.push(capitalize_first(first_word));
    }
    vec_string
    // words.iter().map(|&word| capitalize_first(word)).collect()
}

// TODO: Apply the `capitalize_first` function again to a slice of string
// slices. Return a single string.
// ["hello", " ", "world"] -> "Hello World"
fn capitalize_words_string(words: &[&str]) -> String {
    let mut words_string = String::new();
    let mut words_iter = words.iter();
    while let Some(first_word) = words_iter.next() {
        words_string.push_str(&capitalize_first(first_word));
    }
    words_string
    // words.iter().map(|&word| capitalize_first(word)).collect::<Vec<_>>().concat()
}
fn main() {
    // You can optionally experiment here.
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_success() {
        assert_eq!(capitalize_first("hello"), "Hello");
    }

    #[test]
    fn test_empty() {
        assert_eq!(capitalize_first(""), "");
    }

    #[test]
    fn test_iterate_string_vec() {
        let words = vec!["hello", "world"];
        assert_eq!(capitalize_words_vector(&words), ["Hello", "World"]);
    }

    #[test]
    fn test_iterate_into_string() {
        let words = vec!["hello", " ", "world"];
        assert_eq!(capitalize_words_string(&words), "Hello World");
    }
}
  1. iterators3
// TODO: Add the correct return type and complete the function body.
// Desired output: `Ok([1, 11, 1426, 3])`
fn result_with_list() -> Result<Vec<i64>, DivisionError> {
    let numbers = [27, 297, 38502, 81];
    let division_results = numbers.into_iter().map(|n| divide(n, 27)).collect();
    division_results
}

// TODO: Add the correct return type and complete the function body.
// Desired output: `[Ok(1), Ok(11), Ok(1426), Ok(3)]`
fn list_of_results() -> Vec<Result<i64, DivisionError>>{
    let numbers = [27, 297, 38502, 81];
    let division_results = numbers.into_iter().map(|n| divide(n, 27)).collect();
    division_results
    
}
  1. iterators4
fn factorial(num: u64) -> u64 {
    (1..=num).fold(1, |acc, x| acc * x)
}
  1. iterators5
// TODO: Implement the functionality of `count_for` but with an iterator instead
// of a `for` loop.
fn count_iterator(map: &HashMap<String, Progress>, value: Progress) -> usize {
    // `map` is a hash map with `String` keys and `Progress` values.
    // map = { "variables1": Complete, "from_str": None, … }
    map.values().filter(|&progress| *progress == value).count()
}

// TODO: Implement the functionality of `count_collection_for` but with an
// iterator instead of a `for` loop.
fn count_collection_iterator(collection: &[HashMap<String, Progress>], value: Progress) -> usize {
    // `collection` is a slice of hash maps.
    // collection = [{ "variables1": Complete, "from_str": None, … },
    //               { "variables2": Complete, … }, … ]
    collection
    .iter()
    .map(|map| count_for(map, value))
    .sum()
}

19_smart_pointers

  1. box1

Box:
功能: Box 是最简单的智能指针,它将数据放在堆上而不是栈上。使用 Box 的主要场景是当你在编译时不知道数据的大小,或者你需要转移大量数据但又不想复制它们。
所有权: Box 拥有它所指向的数据,数据的所有权在 Box 被销毁时自动释放。

// TODO: Use a `Box` in the enum definition to make the code compile.
enum List {
    Cons(i32, Box<List>),
    Nil,
}
// TODO: Create an empty cons list.
fn create_empty_list() -> List {
    List::Nil
}
// TODO: Create a non-empty cons list.
fn create_non_empty_list() -> List {
    List::Cons(1, Box::new(List::Cons(2, Box::new(List::Nil))))
    // 1->2->nil
}

在 Rust 中,Cons(i32, Box) 是一种递归类型定义的方式,通常用于构建链表等递归数据结构。递归类型是指一个类型的一部分可以包含它自己。由于 Rust 中的类型大小在编译时必须是已知的,如果直接定义递归类型,会导致类型的大小无法确定,从而无法编译。为了解决这个问题,使用了 Box 来避免动态大小类型(DST,Dynamically Sized Types)的问题。

Cons(i32, Box):
Cons 是链表的一个节点,它包含两个部分:一个 i32 类型的值和一个指向下一个 List 的 Box 指针。
Box 是一个智能指针,它指向堆上的下一个节点(或 Nil),使得递归定义的类型大小是固定的,从而可以通过编译。
Nil:Nil 表示链表的结束,它没有携带任何数据。

  1. rc1

Rc (Reference Counted):
功能: Rc 是一个引用计数的智能指针,用于在单线程环境中共享数据。当你需要多个所有者同时访问相同的数据时,可以使用 Rc。 所有权: Rc 允许多个指针同时拥有同一个数据。每个克隆的 Rc 都会增加引用计数,当所有克隆都被销毁后,数据才会被释放。

enum Planet {
    Mercury(Rc<Sun>),
    Venus(Rc<Sun>),
    Earth(Rc<Sun>),
    Mars(Rc<Sun>),
    Jupiter(Rc<Sun>),
    Saturn(Rc<Sun>),
    Uranus(Rc<Sun>),
    Neptune(Rc<Sun>),
}

let sun = Rc::new(Sun);
println!("reference count = {}", Rc::strong_count(&sun)); // 1 reference

let mercury = Planet::Mercury(Rc::clone(&sun));
println!("reference count = {}", Rc::strong_count(&sun)); // 2 references

drop(mercury);
println!("reference count = {}", Rc::strong_count(&sun)); // 1 reference
  1. arc1 Arc (Atomic Reference Counted):
    功能: Arc 类似于 Rc,但它是线程安全的,可以在多线程环境中使用。Arc 使用原子操作来管理引用计数,因此可以在多个线程之间安全地共享数据。
    所有权: 与 Rc 相同,Arc 允许多个指针同时拥有同一个数据,并且数据在所有克隆被释放后才会被释放。
fn main() {
    // 创建一个包含 0 到 99 的数字的 Vec
    let numbers: Vec<_> = (0..100u32).collect();

    // 使用 Arc 将 numbers 包裹起来,使其能够在多个线程之间共享
    let shared_numbers = Arc::new(numbers);
    let mut join_handles = Vec::new();

    // 创建 8 个线程,每个线程处理不同的偏移量
    for offset in 0..8 {
        // 克隆 Arc,使每个线程都有一个共享数据的引用
        let child_numbers = Arc::clone(&shared_numbers);
        
        let handle = thread::spawn(move || {
            // 计算偏移量对应的数字之和
            let sum: u32 = child_numbers.iter().filter(|&&n| n % 8 == offset).sum();
            println!("Sum of offset {offset} is {sum}");
        });

        // 将线程句柄保存在一个向量中,以便稍后等待线程完成
        join_handles.push(handle);
    }

    // 等待所有线程完成计算
    for handle in join_handles.into_iter() {
        handle.join().unwrap();
    }
}
  1. cow1

Cow,即 Clone-On-Write,是 Rust 标准库中的一个枚举类型,定义在 std::borrow 模块中。它的主要用途是延迟数据的克隆,直到必须进行修改为止。Cow 可以包含两种类型的数据:借用数据的引用(通常是 &str 或 &[T])或 拥有的克隆数据(通常是 String 或 Vec)。
Cow 的设计理念是优化那些可能需要复制(clone)但尽可能避免复制的情况。如果数据不需要修改,Cow 可以直接使用数据的引用,这样避免了不必要的复制操作;如果数据需要修改,Cow 会在修改之前自动克隆一份数据,确保修改不会影响原始数据。

// This exercise explores the `Cow` (Clone-On-Write) smart pointer. It can
// enclose and provide immutable access to borrowed data and clone the data
// lazily when mutation or ownership is required. The type is designed to work
// with general borrowed data via the `Borrow` trait.

use std::borrow::Cow;

fn abs_all(input: &mut Cow<[i32]>) {
    for ind in 0..input.len() {
        let value = input[ind];
        if value < 0 {
            // Clones into a vector if not already owned.
            input.to_mut()[ind] = -value;
        }
    }
}

fn main() {
    // You can optionally experiment here.
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn reference_mutation() {
        // Clone occurs because `input` needs to be mutated.
        let vec = vec![-1, 0, 1];
        let mut input = Cow::from(&vec);
        abs_all(&mut input);
        assert!(matches!(input, Cow::Owned(_)));
    }

    #[test]
    fn reference_no_mutation() {
        // No clone occurs because `input` doesn't need to be mutated.
        let vec = vec![0, 1, 2];
        let mut input = Cow::from(&vec);
        abs_all(&mut input);
        // TODO: Replace `todo!()` with `Cow::Owned(_)` or `Cow::Borrowed(_)`.
        assert!(matches!(input, Cow::Borrowed(_)));
    }

    #[test]
    fn owned_no_mutation() {
        // We can also pass `vec` without `&` so `Cow` owns it directly. In this
        // case, no mutation occurs (all numbers are already absolute) and thus
        // also no clone. But the result is still owned because it was never
        // borrowed or mutated.
        let vec = vec![0, 1, 2];
        let mut input = Cow::from(vec);
        abs_all(&mut input);
        // TODO: Replace `todo!()` with `Cow::Owned(_)` or `Cow::Borrowed(_)`.
        assert!(matches!(input, Cow::Owned(_)));
    }

    #[test]
    fn owned_mutation() {
        // Of course this is also the case if a mutation does occur (not all
        // numbers are absolute). In this case, the call to `to_mut()` in the
        // `abs_all` function returns a reference to the same data as before.
        let vec = vec![-1, 0, 1];
        let mut input = Cow::from(vec);
        abs_all(&mut input);
        // TODO: Replace `todo!()` with `Cow::Owned(_)` or `Cow::Borrowed(_)`.
        assert!(matches!(input, Cow::Owned(_)));
    }
}

20_threads

  1. threads1

我们定义一个可变的 handles向量来存储所有的线程句柄。接下来,使用一个循环来创建10个线程,它们执行相同的操作,即将当前时间戳存储在 start中,然后使线程进入休眠状态250毫秒,并打印信息以表明线程已完成。
最后,线程返回自它启动后经过的毫秒数,作为 u128类型的返回值。当线程启动时,它会获取当前时间戳并存储在 start变量中,当它完成时,它会再次获取当前时间戳,并计算时间差。然后,在vector中存储该时间差。
接下来,我们使用一个循环来等待每个线程完成,然后从其返回值中提取时间差,并将其存储在vector中。如果任何线程不能成功地join,则程序将崩溃并显示错误消息。

// This program spawns multiple threads that each run for at least 250ms, and
// each thread returns how much time they took to complete. The program should
// wait until all the spawned threads have finished and should collect their
// return values into a vector.

use std::{
    thread,
    time::{Duration, Instant},
};

fn main() {
    let mut handles = Vec::new();
    for i in 0..10 {
        let handle = thread::spawn(move || {
            let start = Instant::now();
            thread::sleep(Duration::from_millis(250));
            println!("Thread {i} done");
            start.elapsed().as_millis()
        });
        handles.push(handle);
    }

    let mut results = Vec::new();
    for handle in handles {
        // TODO: Collect the results of all threads into the `results` vector.
        // Use the `JoinHandle` struct which is returned by `thread::spawn`.
        results.push(handle.join().expect("not u128"));
    }

    if results.len() != 10 {
        panic!("Oh no! Some thread isn't done yet!");
    }

    println!();
    for (i, result) in results.into_iter().enumerate() {
        println!("Thread {i} took {result}ms");
    }
}
  1. threads2
use std::{sync::{Arc, Mutex}, thread, time::Duration};

// 定义一个结构体,用于跟踪完成的工作数量
struct JobStatus {
    jobs_done: u32,
}

fn main() {
    // 使用 Arc<Mutex<T>> 来创建一个可以在线程之间共享的可变状态
    // Arc 用于在多线程环境中共享所有权,而 Mutex 用于确保线程安全的可变访问
    let status = Arc::new(Mutex::new(JobStatus { jobs_done: 0 }));

    let mut handles = Vec::new(); // 存储所有线程的句柄
    for _ in 0..10 {
        let status_shared = Arc::clone(&status); // 克隆 Arc,使每个线程都有一个共享的状态引用
        let handle = thread::spawn(move || {
            thread::sleep(Duration::from_millis(250)); // 模拟一些工作,每个线程睡眠 250ms

            // 锁定 Mutex 以获得对共享状态的独占访问权
            let mut status1 = status_shared.lock().unwrap();
            // 增加已完成工作的计数
            status1.jobs_done += 1;
        });
        handles.push(handle); // 将线程句柄保存,以便稍后等待线程完成
    }

    // 等待所有线程完成工作
    for handle in handles {
        handle.join().unwrap(); // 使用 join 等待线程完成,并确保没有出现错误
    }

    // 打印所有线程完成后完成的工作总数
    println!("Jobs done: {}", status.lock().unwrap().jobs_done);
}
  1. threads3

在这个程序中,我们试图从两个线程向一个 mpsc::Sender 发送数据。然而,由于 tx 是一个 Sender,它在被传递给第一个线程后就被移动了,所以在第二个线程中无法再使用它。要解决这个问题,我们可以使用 Arc<Mutex<mpsc::Sender» 来共享 Sender,使多个线程能够安全地发送消息。

quiz

  1. quiz2

11_hashmap之后

mod my_module {
    use std::char::ToUppercase;

    use super::Command;

    // TODO: Complete the function.
    // pub fn transformer(input: ???) -> ??? { ??? }
    pub fn transformer(input: Vec<(String,Command)>) -> Vec<String> {
        let mut output = Vec::new();
        for (text, command) in input {
            let new_text = match command{
                Command::Uppercase => text.to_string().to_uppercase(),
                Command::Trim => text.trim().to_string(),
                Command::Append(n) => text + &"bar".repeat(n)
            };
            output.push(new_text);
        }
        output
    }

}
  1. quiz3

15_traits之后

struct ReportCard <T> {
    grade: T,
    student_name: String,
    student_age: u8,
}

// TODO: Adjust the impl block as described above.
impl <T: ToString> ReportCard <T> {//或者<T: ToString>
    fn print(&self) -> String {
        format!(
            "{} ({}) - achieved a grade of {}",
            &self.student_name, &self.student_age, &self.grade.to_string(),
        )
    }
}
Licensed under CC BY-NC-SA 4.0