基本使用
- 创建工程
cargo new greeting
- 更改配置文件的版本:
rustc --version
# rustc 1.79.0 (129f3b996 2024-06-10)
- 运行代码
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
variables2:变量出现必须与一个数据做绑定
variables6: 考察类似C语言中的全局变量的使用,const + 变量名(rust编译器强制要求全局变量名大写,是的,rust编译器会强制规定这种语言风格)+类型+初始值。
同时,const定义的变量不能是mut,也就是不可修改。这也是出于并发操作安全的考虑。
02_functions
- functions2:函数参数,在调用函数的括号内必须指定参数类型。
- functions5:一个语法糖,如果函数中的某个语句不以分号;结尾,那么就是将这个语句(其实是一个value节点)作为返回值来返回。
03_if
无
04_primitive_types
- primitive_types3:
let a = [0; 100];
,a.len()
- primitive_types4:
let a = [1, 2, 3, 4, 5];
let nice_slice = &a[1..4]; // 左闭右开
- primitive_types6:
let numbers = (1, 2, 3);
let second = numbers.1;
05_vecs
tuple: 大小固定,通常存储在栈上。
array: 大小固定,通常存储在栈上。
vec: 动态大小,元素存储在堆上,元数据(例如长度和容量)存储在栈上。
栈访问比堆快很多。
- vecs1:
let a = [10, 20, 30, 40]; // Array
let v1 = a.to_vec();
let v2 = vec![10, 20, 30, 40];
- 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
- move_sementics3:
//注意mut的位置
fn fill_vec(mut vec: Vec<i32>) -> Vec<i32> {
vec.push(88);
vec
}
07_structs
- 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;
- structs2
let your_order = Order {
name: String::from("Hacker in Rust"),
count: 1,
..order_template
};
08_enums
- 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
- strings1
"blue".to_string()
String::from("blue")
- strings3
input.trim()
input.to_string() + " world!"
input.replace("cars","balloons")
- 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
- modules2
pub use self::fruits::PEAR as fruit;
- modules3
use std::time::{SystemTime,UNIX_EPOCH};
11_hashmaps
- hashmaps1
let mut basket = HashMap::new();
// Two bananas are already given for you :)
basket.insert(String::from("banana"), 2);
- 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
- 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
- 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))
}
}
- errors2
let qty = item_quantity.parse::<i32>()?;
Ok(qty * cost_per_item + processing_fee)
item_quantity 是一个字符串(String 或者 &str)。
parse::
? 操作符用于错误处理。如果 parse 成功,qty 将是一个 i32 类型的整数;如果 parse 失败,? 会将错误传播给调用者,而不是继续执行代码。
? 相当于 match 语句中的 Result::Ok 或 Result::Err,在失败时直接返回错误。
- 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();
- 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))
}
}
- 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(())
}
- 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
- generics1
let mut numbers: Vec<i16> = Vec::new();
let n1: u8 = 42;
numbers.push(n1.into());
let n2: i8 = -1;
numbers.push(n2.into());
- 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());
}
- 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()
}
- traits5
fn some_func(item: impl SomeTrait + OtherTrait) -> bool {
item.some_function() && item.other_function()
}
16_lifetimes
- lifetimes1
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
- 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
- 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));
}
}
- tests2
assert_eq!(power_of_2(0), 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
- 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"));
- 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");
}
}
- 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
}
- iterators4
fn factorial(num: u64) -> u64 {
(1..=num).fold(1, |acc, x| acc * x)
}
- 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
- box1
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 表示链表的结束,它没有携带任何数据。
- rc1
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
- 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();
}
}
- 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
- 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");
}
}
- 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);
}
- threads3
在这个程序中,我们试图从两个线程向一个 mpsc::Sender
quiz
- 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
}
}
- 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(),
)
}
}