rustlings是學習rust的一個很優秀的題庫。2023年6月19日決定對rust做一個重新的梳理,整理今年4月份做完的rustlings,根據自己的理解來寫一份題解,記錄在此。 ...
2023年6月19日決定對rust做一個重新的梳理,整理今年4月份做完的rustlings,根據自己的理解來寫一份題解,記錄在此。
周折很久,因為中途經歷了推免的各種麻煩事,以及選擇資料庫作為未來研究方向後的一段適應過程,耽擱了很久。
2023年10月份秋冬季的開源操作系統訓練營又開始了,所以我回來繼續整理。繼續進行我的os大業。
rustlings 版本眾多,往往幾個月內可能就不完全一致,本份題解基於rustlings 5.5.1,rustlings list顯示95道題。梳理完成於2023年10月22日。Github倉庫
- Rust文檔網
- 以及為了避免註意力分散,我所選擇的路徑:
0. 熟悉rustlings
rustlings是一個rustOJ形式的學習平臺,通過90多道題目來測試rust語法的掌握程度,第一次接觸的時候會感覺非常新穎,通過rustlings進行學習也非常高效。
- 使用
rustlings watch
來開始闖關式學習 - 使用
rustlings hint intro1
來查看對應題解(如果有的話)。 - 如何在rustlings中使用rust-analyzer。
1. intro
- intro1:體驗rustlings的用法,去除
//I AM NOT DONE
即可。 - intro2:體驗rust的hello world,它的字元輸出比python嚴格,比C簡單。
2. variables
- variables1:變數定義用let,其實在rust里 是一個數據綁定到一個變數,這是這個語言記憶體管理方面的創新點。
- variables2:變數出現必須與一個數據做綁定,而綁定後預設不可修改,如果後續要對他修改,那就加上mut。
這裡還涉及一個if判斷邏輯,類似於python2,不需要括弧。 - variables3:報錯信息是變數x沒有被初始化,所以變數出現必須與一個數據做綁定,同時i32是可以取掉的,因為rust編譯器將自動將x推導為等式右邊數字的類型。
- variables4:後續數據修改,則第一次出現的數據定義(初始化)需要加上mut。
- variables5:這裡算是一個語法糖,當let過的變數再次被let,其實就是變數名的再利用,原來的變數名對應的數據將消失。
第二個number=3
修改為let number = 3
。 - variables6:考察類似C語言中的全局變數的使用,const + 變數名(rust編譯器強制要求全局變數名大寫,是的,rust編譯器會強制規定這種語言風格)+類型+初始值。
同時,const定義的變數不能是mut,也就是不可修改。這也是出於併發操作安全的考慮。
3. functions
- functions1:補一個函數即可,註意這裡不要求被調用函數必須在調用者之前聲明或者定義。
- functions2:函數參數,在調用函數的括弧內必須指定參數類型。
- functions3:調用者缺少參數,加一個就行。
- functions4:rust中返回值的表達
- functions5:一個語法糖,如果函數中的某個語句不以分號;結尾,那麼就是將這個語句(其實是一個value節點)作為返回值來返回。
哈哈學了編譯原理什麼都想往語法樹上想。
4. if
- if1:if語句的基本寫法,外加rust函數返回的特性。
- if2:if語句的基本寫法,外加rust函數的返回特性。
pub fn foo_if_fizz(fizzish: &str) -> &str { if fizzish == "fizz" { "foo" } else if fizzish == "fuzz"{ "bar" } else { "baz" } }
5. quiz1
還有測試是我沒想到的,quiz沒有hint,那這樣確實會比OJ體系更好玩一點。
寫的if雖然很明顯有問題,但測試用例就放在下麵,所以這麼寫能通過測試。
// Put your function here!
fn calculate_price_of_apples(nums:i32) -> i32{
if nums <= 40{
nums*2
} else {
nums
}
}
// Don't modify this function!
#[test]
fn verify_test() {
let price1 = calculate_price_of_apples(35);
let price2 = calculate_price_of_apples(40);
let price3 = calculate_price_of_apples(41);
let price4 = calculate_price_of_apples(65);
assert_eq!(70, price1);
assert_eq!(80, price2);
assert_eq!(41, price3);
assert_eq!(65, price4);
}
6. primitive_types
- primitive_types1:easy,變數初始化。
letis_evening=true;
- primitive_types2:easy,字元變數的初始化,這裡跟C的區別就是不需要指定類型,而由編譯器進行推理。
let your_character='D';
- primitive_types3:用慣了vec,突然提起數組竟然懷疑起來是否跟C語言一樣(結果確實是一樣的)。不過rust對於數組多了很多類似於類方法的機制,比如a數組
a.len()
就可以得到a數組的長度。 - primitive_types4:數組切片,左閉右開。
#[test] fn slice_out_of_array() { let a = [1, 2, 3, 4, 5]; let nice_slice = &a[1..4]; assert_eq!([2, 3, 4], nice_slice) }
- primitive_types5:元組,其實接觸過python的話,這裡很好理解,rust的語法是各家的雜糅。
fn main() { let cat = ("Furry McFurson", 3.5); let /* your pattern here */ (name,age)= cat; println!("{} is {} years old.", name, age); }
- primitive_types6:元組數據的訪問,可以通過
.
來訪問#[test] fn indexing_tuple() { let numbers = (1, 2, 3); // Replace below ??? with the tuple indexing syntax. let second = numbers.1; assert_eq!(2, second, "This is not the 2nd number in the tuple!") }
7. vecs
vecs存儲在堆上。與之相比,數組是分配在棧上的。
- vecs1:用巨集定義的方法聲明一個vector。
fn array_and_vec() -> ([i32; 4], Vec<i32>) { let a = [10, 20, 30, 40]; // a plain array let v = vec![10, 20, 30, 40];// TODO: declare your vector here with the macro for vectors (a, v) } #[cfg(test)] mod tests { use super::*; #[test] fn test_array_and_vec_similarity() { let (a, v) = array_and_vec(); assert_eq!(a, v[..]); } }
- vecs2:體驗vector的迭代器和閉包。
- 第一個函數體驗迭代器:註意遍歷可變引用時,需要使用
*
運算符來解引用指針以獲取可變引用所指向的值,解引用之後才修改的是引用指向的實際值。 - 第二個函數體驗閉包:閉包是一個編程特性。
- 下麵的實現中,使用
v.iter()
方法創建一個不可變的迭代器,該迭代器會產生&i32
類型的元素引用。 - 然後,使用
.map()
方法對每個元素進行轉換操作。.map()
方法接受一個閉包作為參數,該閉包定義了對每個元素執行的轉換操作。在這裡,閉包的輸入參數是element
,表示迭代器產生的每個元素的引用。 - 在閉包體內部,我們將
element
乘以2,並通過閉包體的最後一行作為返回值返回這個新的結果。最後,我們使用.collect()
方法收集迭代器產生的轉換結果,並返回一個新的Vec<i32>
類型的向量。因此,vec_map
函數實現了對傳入的向量中的每個元素進行乘以2的操作,並返回一個包含結果的新向量。原始向量v
不會被修改。
- 下麵的實現中,使用
- 第一個函數體驗迭代器:註意遍歷可變引用時,需要使用
fn vec_loop(mut v: Vec<i32>) -> Vec<i32> {
for element in v.iter_mut() {
// TODO: Fill this up so that each element in the Vec `v` is
// multiplied by 2.
*element = *element*2;
}
// At this point, `v` should be equal to [4, 8, 12, 16, 20].
v
}
fn vec_map(v: &Vec<i32>) -> Vec<i32> {
v.iter().map(|element| {
// TODO: Do the same thing as above - but instead of mutating the
// Vec, you can just return the new number!
element*2
}).collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_vec_loop() {
let v: Vec<i32> = (1..).filter(|x| x % 2 == 0).take(5).collect();
let ans = vec_loop(v.clone());
assert_eq!(ans, v.iter().map(|x| x * 2).collect::<Vec<i32>>());
}
#[test]
fn test_vec_map() {
let v: Vec<i32> = (1..).filter(|x| x % 2 == 0).take(5).collect();
let ans = vec_map(&v);
assert_eq!(ans, v.iter().map(|x| x * 2).collect::<Vec<i32>>());
}
}
知識補充1:vec和數組
會對vec和數組這中語言層面的機制的底層實現會有一點點好奇,比如說使用哪個更好這類話題:
- 所有編譯型語言都是數組快(但是有一些語言沒有普通的數組,把 C++/Rust 的 vec 叫做數組)。
- 數組是放在棧(stack)上的,
Vec
是放在堆(heap)上的。棧訪問比堆快很多。 - 所以如果是不可變長數組,且長度不是太大,肯定優先選棧上的數組。
知識補充2:閉包
閉包是 一種匿名函數,它可以賦值給變數,也可以作為參數傳遞給其它函數;不同於函數的是,它允許捕獲調用者作用域中的值 ,例如:
fn main() {
let x = 1;
let sum = |y| x + y;
assert_eq!(3, sum(2));
}
上面的代碼展示了非常簡單的閉包 sum
,它擁有一個入參 y
,同時捕獲了作用域中的 x
的值,因此調用 sum(2)
意味著將 2(參數 y
)跟 1(x
)進行相加,最終返回它們的和:3
。
可以看到 sum
非常符合閉包的定義:可以賦值給變數,允許捕獲調用者作用域中的值。
閉包的思想比較複雜,今天精神狀態不佳,看了聖經沒怎麼明白,這裡先不詳細介紹了。
知識補充3:迭代器
學了C++之後迭代器就有點好理解。
8. move_semantics
8.1 所有權
所有權是rust中很經典很經典的設計理念,堪稱提起rust就會想起所有權。我此前只簡略的知道一些,但是並不深刻(指各種情況下的所有權調用,以及這個語言特性如何在彙編機器級層面運作),我複習這個知識點主要參考:Rust聖經-所有權(第一版)。
2023年10月份,發現聖經已經有了第二版,兩版在這裡的講解並不相同。
這裡聖經(第一版)中講解的很細緻,首先提到C/C++的記憶體管理問題,比如懸空指針(dangling pointers)造成的安全問題,以及不再使用的字元常量在數據區無法被釋放的問題。
這裡講解了堆、棧,對於系統編程語言來說,理解記憶體模型是十分必要的。堆是缺乏組織規則的數據結構,而棧則規整很多,處理器分配棧比分配堆塊。
這裡我突然不理解了一個事情,那就是堆、棧這種東西顯然是一個記憶體模型,這個記憶體模型是由操作系統規定的嗎?且不談是否能夠有其他記憶體模型,這個記憶體模型是如何實現的、語言層面編譯器是如何翻譯這些記憶體規則的。
這裡可能還需要看看CSAPP。
學編譯原理會對語言的實現機制很敏感,可惜我的編譯原理還沒有看完。
所有權的規則:
- Rust中的每個值都被一個變數擁有,該變數稱為該值的所有者
- 一個值同時只能被一個變數擁有
- 當所有者(變數)離開作用域範圍時,這個值將被丟棄(drop)
8.2 題目
-
move_sementics1:由於vec1需要調用push修改vec1自己,所以聲明時應當聲明為mut。
不過我這裡想繼續弄清楚的是,實參、形參和所有權的機器級別的實現.
後來發現聖經也講了--函數傳值與返回 ,介紹了傳參和返回值過程中的所有權。 -
move_sementics2:
let mut vec1=fill_vec(vec0);
報錯,可見,這裡的fill_vec實現了一個移動(move),即將vec0的值轉移給了vec1,而vec0無效了。因此修改為let mut vec1 = fill_vec(vec0.clone());
即可。這個後面再看看,機制還是比較複雜的。
-
move_sementics3:跟2的差異在於fill_vec中沒有
let mut vec=vec;
然後報錯說vec這個東西不是mut的,在函數中聲明為mut即可。fnfill_vec(mutvec:Vec<i32>``)->Vec<i32>
-
move_sementics4:這道題提出將fill_vec的參數撤銷,與之匹配的修改應該是:main中調用適配;註釋vec0(因為不能通過上下文推斷vec的類型,3中是int整形);fill函數中新建一個mutable vec。
-
move_sementics5:借用。允許你使用值,但是不獲取所有權。這裡具體是在考察:可變引用同時只允許存在一個。調整一下借用和使用順序即可。
fn main() { let mut x = 100; let y = &mut x; *y += 100; let z = &mut x; *z += 1000; assert_eq!(x, 1200); }
-
move_sementics6:這道題有有多個修改方案,4月份我做的時候是對get_char的data修改為data.clone(),在string_uppercase函數中進行一個新變數的綁定,修改如下:
fn main() { let data = "Rust is great!".to_string(); get_char(data.clone()); string_uppercase(&data); } // Should not take ownership fn get_char(data: String) -> char { data.chars().last().unwrap() } // Should take ownership fn string_uppercase(data: &String) { let data = &data.to_uppercase(); println!("{}", data); }
但實際上,這麼修改並不符合題目的本意,10月份的修改如下:
fn main() { let data = "Rust is great!".to_string(); get_char(&data);// 不獲取所有權,只有使用權 string_uppercase(data); // 獲取所有權 } // Should not take ownership fn get_char(data: &String) -> char { data.chars().last().unwrap() } // Should take ownership fn string_uppercase(mut data: String) { data = data.to_uppercase(); println!("{}", data); }
9. structs
必須要將結構體實例聲明為可變的,才能修改其中的欄位,Rust 不支持將某個結構體某個欄位標記為可變。
-
structs1:熟悉三種結構體的聲明和定義(綁定),新鮮的是單元結構體,它適用於不關心屬性而關心行為/方法/函數的時候。
struct ColorClassicStruct { // TODO: Something goes here red: u64, green: u64, blue: u64, } struct ColorTupleStruct(u64,u64,u64); #[derive(Debug)] struct UnitLikeStruct; #[cfg(test)] mod tests { use super::*; #[test] fn classic_c_structs() { // TODO: Instantiate a classic c struct! let green = ColorClassicStruct { red: 0, green: 255, blue: 0, }; assert_eq!(green.red, 0); assert_eq!(green.green, 255); assert_eq!(green.blue, 0); } #[test] fn tuple_structs() { // TODO: Instantiate a tuple struct! let green = (0,255,0); assert_eq!(green.0, 0); assert_eq!(green.1, 255); assert_eq!(green.2, 0); } #[test] fn unit_structs() { // TODO: Instantiate a unit-like struct! let unit_like_struct = UnitLikeStruct; let message = format!("{:?}s are fun!", unit_like_struct); assert_eq!(message, "UnitLikeStructs are fun!"); } }
-
structs2:第一遍做的時候,按照下麵的斷言檢查一個一個欄位進行初始化來著,10月份仔細看了看聖經,發現有語法糖,可以直接簡潔實現:
let your_order = Order{ name: String::from("Hacker in Rust"), count:1, ..order_template };
聖經上這裡還需要考慮copy屬性,也就是如果order結構體中有複雜一些的數據類型,那麼order_template所有權會交給your_order,後續就不能再使用order_template了,所幸這裡沒有這個問題。
-
structs3:方法Method的簡單使用。考察了方法的書寫和調用。再加上一個Self的理解。
#[derive(Debug)] struct Package { sender_country: String, recipient_country: String, weight_in_grams: i32, } impl Package { fn new(sender_country: String, recipient_country: String, weight_in_grams: i32) -> Package { if weight_in_grams <= 0 { panic!("Can not ship a weightless package.") } else { Package { sender_country, recipient_country, weight_in_grams, } } } fn is_international(&self) -> bool { // Something goes here... return self.sender_country != self.recipient_country; } fn get_fees(&self, cents_per_gram: i32) -> i32{ // Something goes here... self.weight_in_grams * cents_per_gram } } #[cfg(test)] mod tests { use super::*; #[test] #[should_panic] fn fail_creating_weightless_package() { let sender_country = String::from("Spain"); let recipient_country = String::from("Austria"); Package::new(sender_country, recipient_country, -2210); } #[test] fn create_international_package() { let sender_country = String::from("Spain"); let recipient_country = String::from("Russia"); let package = Package::new(sender_country, recipient_country, 1200); assert!(package.is_international()); } #[test] fn create_local_package() { let sender_country = String::from("Canada"); let recipient_country = sender_country.clone(); let package = Package::new(sender_country, recipient_country, 1200); assert!(!package.is_international()); } #[test] fn calculate_transport_fees() { let sender_country = String::from("Spain"); let recipient_country = String::from("Spain"); let cents_per_gram = 3; let package = Package::new(sender_country, recipient_country, 1500); assert_eq!(package.get_fees(cents_per_gram), 4500); assert_eq!(package.get_fees(cents_per_gram * 2), 9000); } }
補充一個關聯函數的概念,定義在 impl
中且沒有 self
的函數被稱之為關聯函數,同時,由於沒有self,不能通過.來訪問,只能通過::來訪問。
rust 中有一個約定俗成的規則,使用 new
來作為構造器的名稱,出於設計上的考慮,Rust 特地沒有用 new
作為關鍵字。
10. enums
- enums1: 考察enum的簡單構造,這個概念跟C語言中一致,所以不再介紹。
#[derive(Debug)] enum Message { // TODO: define a few types of messages as used below Quit, Echo, Move, ChangeColor, } fn main() { println!("{:?}", Message::Quit); println!("{:?}", Message::Echo); println!("{:?}", Message::Move); println!("{:?}", Message::ChangeColor); }
- enums2:任何類型的數據都可以放入枚舉成員中 : 例如字元串、數值、結構體甚至另一個枚舉。
#[derive(Debug)] enum Message { // TODO: define the different variants used below Quit, // 沒有任何關聯數據 Echo(String),// String字元串 Move{ x: i32, y: i32 }, // 匿名結構體 ChangeColor(i32, i32, i32), //3個i32 } impl Message { fn call(&self) { println!("{:?}", self); } } fn main() { let messages = [ Message::Move { x: 10, y: 30 }, Message::Echo(String::from("hello world")), Message::ChangeColor(200, 255, 255), Message::Quit, ]; for message in &messages { message.call(); } }
- enums3:考察了枚舉複雜類型和匹配的結合,需要使用args來替代enums複雜成員的內容。
enum Message { // TODO: implement the message variant types based on their usage below Quit, // 沒有任何關聯數據 Echo(String),// String字元串 Move(Point), // 結構體 ChangeColor((u8, u8, u8)), //3個u8 } struct Point { x: u8, y: u8, } struct State { color: (u8, u8, u8), position: Point, quit: bool, message: String } impl State { fn change_color(&mut self, color: (u8, u8, u8)) { self.color = color; } fn quit(&mut self) { self.quit = true; } fn echo(&mut self, s: String) { self.message = s } fn move_position(&mut self, p: Point) { self.position = p; } fn process(&mut self, message: Message) { // TODO: create a match expression to process the different message // variants // Remember: When passing a tuple as a function argument, you'll need // extra parentheses: fn function((t, u, p, l, e)) match message { Message::Quit => {self.quit = true;}, Message::Echo(args) => {self.echo(args);}, Message::Move(args) => {self.move_position(args);}, Message::ChangeColor(args) => {self.change_color(args);}, } } } #[cfg(test)] mod tests { use super::*; #[test] fn test_match_message_call() { let mut state = State { quit: false, position: Point { x: 0, y: 0 }, color: (0, 0, 0), message: "hello world".to_string(), }; state.process(Message::ChangeColor((255, 0, 255))); state.process(Message::Echo(String::from("hello world"))); state.process(Message::Move(Point { x: 10, y: 15 })); state.process(Message::Quit); assert_eq!(state.color, (255, 0, 255)); assert_eq!(state.position.x, 10); assert_eq!(state.position.y, 15); assert_eq!(state.quit, true); assert_eq!(state.message, "hello world"); } }
11. strings
- strings1:很明顯,是函數的返回值類型(字元串字面量)和聲明的返回值類型(String)不匹配. 註意,字元串字面量是切片類型&str。
fn main() { let answer = current_favorite_color(); println!("My current favorite color is {}", answer); } fn current_favorite_color() -> String { String::from("blue") }
- strings2:修改起來還是比較簡單,形參實參類型保持一致。
fn main() { let word = String::from("green"); // Try not changing this line :) if is_a_color_word(&word) { println!("That is a color word I know!"); } else { println!("That is not a color word I know."); } } fn is_a_color_word(attempt: &str) -> bool { attempt == "green" || attempt == "blue" || attempt == "red" }
- string3:考察字元串的相關操作,分別是去除空格的trim,切片->字元串的to_string()以及字元串連接"+",還有字元串替換repalce。
fn trim_me(input: &str) -> String { // TODO: Remove whitespace from both ends of a string! String::from(input.trim()) } fn compose_me(input: &str) -> String { // TODO: Add " world!" to the string! There's multiple ways to do this! input.to_string()+" world!" } fn replace_me(input: &str) -> String { // TODO: Replace "cars" in the string with "balloons"! input.to_string().replace("cars", "balloons") }
- string4:進一步區分切片和字元串。
fn string_slice(arg: &str) { println!("{}", arg); } fn string(arg: String) { println!("{}", arg); } fn main() { string_slice("blue"); string("red".to_string()); string(String::from("hi")); string("rust is fun!".to_owned()); string("nice weather".into()); string(format!("Interpolation {}", "Station")); string_slice(&String::from("abc")[0..1]); string_slice(" hello there ".trim()); string("Happy Monday!".to_string().replace("Mon", "Tues")); string("mY sHiFt KeY iS sTiCkY".to_lowercase()); }
12. modules
這部分就開始介紹rust的項目框架了。分為package、crate、mod三層。題目主要是在說mod。
- moules1: 就是一個簡單的訪問許可權控制,代碼可見性
pub fn make_sausage() { get_secret_recipe(); println!("sausage!"); }
- modules2:use關鍵字及其受限可見性,由於已知main函數中是
delicious_snacks::fruit
,這樣使用的,所以可知應該這樣:mod delicious_snacks { // TODO: Fix these use statements pub use self::fruits::PEAR as fruit; pub use self::veggies::CUCUMBER as veggie; mod fruits { pub const PEAR: &'static str = "Pear"; pub const APPLE: &'static str = "Apple"; } mod veggies { pub const CUCUMBER: &'static str = "Cucumber"; pub const CARROT: &'static str = "Carrot"; } } fn main() { println!( "favorite snacks: {} and {}", delicious_snacks::fruit, delicious_snacks::veggie ); }
- modules3:使用use導入標準庫的包和成員,還有一個as別名。
use std::time::SystemTime; use std::time::UNIX_EPOCH; fn main() { match SystemTime::now().duration_since(UNIX_EPOCH) { Ok(n) => println!("1970-01-01 00:00:00 UTC was {} seconds ago!", n.as_secs()), Err(_) => panic!("SystemTime before UNIX EPOCH!"), } }
說實話這幾道題還是比較簡單的,項目中可能還會比較複雜。聖經中還有一個受限可見性,更細粒度的控制可見性 pub(in crate a)
,還沒認真理解。
13. hashmaps
HashMap
中存儲的是一一映射的 KV
鍵值對,並提供了平均複雜度為 O(1)
的查詢方法
-
hashmaps1:就考察HashMap的創建和更新。
fn fruit_basket() -> HashMap<String, u32> { let mut basket = HashMap::new();// TODO: declare your hash map here. // Two bananas are already given for you :) basket.insert(String::from("banana"), 2); // TODO: Put more fruits in your basket here. basket.insert(String::from("apple"),1); basket.insert(String::from("orange"),3); basket }
-
hashmaps2:有多種實現(因為提供的函數確實很多)
-
for fruit in fruit_kinds { // TODO: Insert new fruits if they are not already present in the // basket. Note that you are not allowed to put any type of fruit that's // already present! // 查詢Yellow對應的值,若不存在則插入新值 basket.entry(fruit).or_insert(1); }
-
for fruit in fruit_kinds { // TODO: Put new fruits if not already present. Note that you // are not allowed to put any type of fruit that's already // present! if !basket.contains_key(&fruit) { basket.insert(fruit, 1); } }
-
-
hashmaps3:利用hashmaps2中的entry or_insert就可以解決,這裡涉及到了這個函數的返回值:根據索引名找表項,如果沒有則插入後再返回該表項。這就很容易解決hashmap3了。
另外提一嘴,這裡很多解答都會在struct Team中增加一個team成員,可以不增加的。use std::collections::HashMap; // A structure to store the goal details of a team. struct Team { goals_scored: u8, goals_conceded: u8, } fn build_scores_table(results: String) -> HashMap<String, Team> { // The name of the team is the key and its associated struct is the value. let mut scores: HashMap<String, Team> = HashMap::new(); // 從用逗號和換行符組成的 字元串 構造 HashMap // for迴圈是每次處理一行,也就是處理換行符 for r in results.lines() { // split處理逗號 let v: Vec<&str> = r.split(',').collect(); let team_1_name = v[0].to_string(); let team_1_score: u8 = v[2].parse().unwrap(); let team_2_name = v[1].to_string(); let team_2_score: u8 = v[3].parse().unwrap(); // TODO: Populate the scores table with details extracted from the // current line. Keep in mind that goals scored by team_1 // will be the number of goals conceded from team_2, and similarly // goals scored by team_2 will be the number of goals conceded by // team_1. // 通過上述情況就可以知道是,現在要補充的是,將兩個隊伍及其比分情況放到HahsMap中 // 而這個小操作在hashmaps2的更新考查中已經用過了 basket.entry(fruit).or_insert(1); // Update the team 1 score let team_1 = scores.entry(team_1_name).or_insert( Team { goals_scored: 0, goals_conceded: 0, } ); team_1.goals_scored += team_1_score; team_1.goals_conceded += team_2_score; // Update the team 2 score let team_2 = scores.entry(team_2_name.clone()).or_insert(Team { goals_scored: 0, goals_conceded: 0, }); team_2.goals_scored += team_2_score; team_2.goals_conceded += team_1_score; } scores } #[cfg(test)] mod tests { use super::*; fn get_results() -> String { let results = "".to_string() + "England,France,4,2\n" + "France,Italy,3,1\n" + "Poland,Spain,2,0\n" + "Germany,England,2,1\n"; results } #[test] fn build_scores() { let scores = build_scores_table(get_results()); let mut keys: Vec<&String> = scores.keys().collect(); keys.sort(); assert_eq!( keys, vec!["England", "France", "Germany", "Italy", "Poland", "Spain"] ); } #[test] fn validate_team_score_1() { let scores = build_scores_table(get_results()); let team = scores.get("England").unwrap(); assert_eq!(team.goals_scored, 5); assert_eq!(team.goals_conceded, 4); } #[test] fn validate_team_score_2() { let scores = build_scores_table(get_results()); let team = scores.get("Spain").unwrap(); assert_eq!(team.goals_scored, 0); assert_eq!(team.goals_conceded, 2); } }
14. quiz2
pub enum Command {
Uppercase,
Trim,
Append(usize),
}
mod my_module {
use super::Command;
// TODO: Complete the function signature!
pub fn transformer(input: Vec<(String, Command)>) -> Vec<String> {
// TODO: Complete the output declaration!
let mut output: Vec<String> = vec![];
// 就是將 string及其指令 轉化的過程
for (string, command) in input.iter() {
// TODO: Complete the function body. You can do it!
let member = match command{
Command::Uppercase => string.to_uppercase(),
Command::Trim => string.trim().to_string(),//into()也可以,to_owned()也可以
Command::Append(nums) => string.to_owned()+&"bar".repeat(*nums),//想尋找一個簡單的寫法,repeat就滿足
};
output.push(member);
}
output
}
}
#[cfg(test)]
mod tests {
// TODO: What do we need to import to have `transformer` in scope?
use super::my_module::transformer;
use super::Command;
#[test]
fn it_works() {
let output = transformer(vec![
("hello".into(), Command::Uppercase),
(" all roads lead to rome! ".into(), Command::Trim),
("foo".into(), Command::Append(1)),
("bar".into(), Command::Append(5)),
]);
assert_eq!(output[0], "HELLO");
assert_eq!(output[1], "all roads lead to rome!");
assert_eq!(output[2], "foobar");
assert_eq!(output[3], "barbarbarbarbarbar");
}
}
盡在不言中。討論to_string into to_owned,簡要總結,用to_owned就好。
15. options
- options1:Option如何構造和調用。
fn maybe_icecream(time_of_day: u16) -> Option<u16> { // We use the 24-hour system here, so 10PM is a value of 22 and 12AM is a // value of 0 The Option output should gracefully handle cases where // time_of_day > 23. // TODO: Complete the function body - remember to return an Option! if time_of_day > 23 { Option::None } else { if time_of_day < 22 { Option::Some(5) } else { Option::Some(0) } } } #[cfg(test)] mod tests { use super::*; #[test] fn check_icecream() { assert_eq!(maybe_icecream(9), Some(5)); assert_eq!(maybe_icecream(10), Some(5)); assert_eq!(maybe_icecream(23), Some(0)); assert_eq!(maybe_icecream(22), Some(0)); assert_eq!(maybe_icecream(25), None); } #[test] fn raw_value() { // TODO: Fix this test. How do you get at the value contained in the // Option? let icecreams = maybe_icecream(12); assert_eq!(icecreams, Some(5)); } }
- options2:調用
pop
方法時,它會從數組的末尾移除一個元素,並返回被移除的元素作為Option<T>
。因此,在這個例子中,由於數組的類型是Vec<Option<i8>>
,所以pop
方法返回的類型是Option<Option<i8>>
。外層的Option
表示從數組中獲取到的值,內層的Option
表示數組中原本存儲的Option<i8>
類型的值。
不過這一點在代碼註釋里也已經交代過了。#[cfg(test)] mod tests { #[test] fn simple_option() { let target = "rustlings"; let optional_target = Some(target); // TODO: Make this an if let statement whose value is "Some" type if let Some(word) = optional_target { assert_eq!(word, target); } } #[test] fn layered_option() { let range = 10; let mut optional_integers: Vec<Option<i8>> = vec![None]; for i in 1..(range + 1) { optional_integers.push(Some(i)); } let mut cursor = range; // TODO: make this a while let statement - remember that vector.pop also // adds another layer of Option<T>. You can stack `Option<T>`s into // while let and if let. while let Some(Some(integer)) = optional_integers.pop() { assert_eq!(integer, cursor); cursor -= 1; } assert_eq!(cursor, 0); } }
- options3:match語句的所有權問題,編譯器報錯說最後一行的y的value已經被moved了,很明顯是match使用後,離開作用域y就失效了。解決是在
match&y
加上&即可。當然也可以let y = match y{...}
enum MyEnum { SomeValue(String), AnotherValue(u32), } fn main() { let my_var = MyEnum::SomeValue(String::from("Hello, Rust")); match my_var { MyEnum::SomeValue(s) => { println!("Got ownership of string: {}", s); // 在此分支中,我們獲取了字元串的所有權,可以自由地使用它 } MyEnum::AnotherValue(n) => { println!("Got ownership of u32: {}", n); // 在此分支中,我們獲取了 u32 的所有權,可以自由地使用它 } } // 註意,在 `match` 表達式之後,`my_var` 的所有權並未返回,因為在所有可能的分支中都已經被取走。 }
16. error_handlings
錯誤處理,聖經相關內容,只不過題目更傾向於Result而不是panic
-
errors1:考察錯誤處理強相關的數據結構enum Result
<T,E>
在函數返回值中的應用。pub fn generate_nametag_text(name: String) -> Result<String,String> { if name.is_empty() { // Empty names aren't allowed. Err("`name` was empty; it must be nonempty.".into()) } else { Ok(format!("Hi! My name is {}", name)) } } #[cfg(test)] mod tests { use super::*; #[test] fn generates_nametag_text_for_a_nonempty_name() { assert_eq!( generate_nametag_text("Beyoncé".into()), Ok("Hi! My name is Beyoncé".into()) ); } #[test] fn explains_why_generating_nametag_text_fails() { assert_eq!( generate_nametag_text("".into()), // Don't change this line Err("`name` was empty; it must be nonempty.".into()) ); } }
-
errors2: 介紹了 Result 和 ?運算符的使用。
摘錄:其實
?
就是一個巨集,它的作用跟上面的match
幾乎一模一樣:let mut f = match f { // 打開文件成功,將file句柄賦值給f Ok(file) =>> file, // 打開文件失敗,將錯誤返回(向上傳播) Err(e) =>> return Err(e), };
如果結果是
Ok(T)
,則把T
賦值給f
,如果結果是Err(E)
,則返回該錯誤,所以?
特別適合用來傳播錯誤。
這裡我還嘗試了一下expect/unwrap/?/的區別,前兩者是,如果到了Err的範疇,就直接panic推出了,前兩者的區別是expect可以在報錯中顯示一些信息。而問號則是如果到了Err則返回一個Err,如果Ok則取出Ok其中的內容。所以這裡只能用?use std::num::ParseIntError; pub fn total_cost(item_quantity: &str) -> Result<i32, ParseIntError> { let processing_fee = 1; let cost_per_item = 5; let qty = item_quantity.parse::<i32>()?; Ok(qty * cost_per_item + processing_fee) } #[cfg(test)] mod tests { use super::*; #[test] fn item_quantity_is_a_valid_number() { assert_eq!(total_cost("34"), Ok(171)); } #[test] fn item_quantity_is_an_invalid_number() { assert_eq!( total_cost("beep boop").unwrap_err().to_string(), // unwrap_err()的意思是,將ok()或者Err()中的值取出來並報錯。 "invalid digit found in string" ); } }
-
errors3:跟errors2很像,但是報錯了,百思不得其解,但是看到編譯器的報錯信息就大致明白了,因為main函數預設沒有返回值,或者說返回值類型是(),在這種函數中不能使用?,故而有兩種解決方案:
- 給main函數增加返回值類型,
fn main() -> Result<(), ParseIntError>
- 不使用?,用unwrap代替:
letcost=total_cost(pretend_user_input).unwrap();
- 給main函數增加返回值類型,
-
errors4:考察在一個具體程式如何用Result枚舉類型來處理一些不合業務邏輯的問題。
// Hmm...? Why is this only returning an Ok value? if value > 0{ Ok(PositiveNonzeroInteger(value as u64)) } else if value == 0 { Err(CreationError::Zero) } else { Err(CreationError::Negative) }
-
errors5:給出了一個比我上面errors4更優雅的解決方案,這道題的問題跟errors3類似,即main函數的返回值。因為 ? 要求 Result<T, E> 形式的返回值,而 main 函數的返回是 (),因此無法滿足,那是不是就無解了呢?實際上 Rust 還支持另外一種形式的 main 函數:
use std::error::Error; use std::fs::File; fn main() -> Result<(), Box<dyn Error>> { let f = File::open("hello.txt")?; Ok(()) }
-
errors6:這部分已經不在前面說的基礎部分內容里了,而在聖經後面的進階部分自定義錯誤類型。首先介紹了組合器,這個概念對我來說比較新,filter我不是很理解,重點我是來看map()和map_err()的。map用於修改Some() 或者 Ok() 中的值,對應map_err修改Err中的值。
按照我的理解,對於一些錯誤系統的error.rs會給出自動的報錯類型,如果我們不想讓他直接pannic崩掉,而是做出一些合適的錯誤處理,就需要捕獲這些報錯類型並且處理掉,在這一行:
let x: i64 = s.parse().map_err(ParsePosNonzeroError::from_parseint)?;
原本是parse().unwrap(),這將會導致如果s不能解析為整數i64,就會直接panic掉,而使用map_err對Err()進行一些修改,捕獲原本的ParseInt類型錯誤,而將它轉化成ParsePosNonzeroError::ParseInt,這就符合下麵的測試邏輯了。
use std::num::ParseIntError; // This is a custom error type that we will be using in `parse_pos_nonzero()`. #[derive(PartialEq, Debug)] enum ParsePosNonzeroError { Creation(CreationError), ParseInt(ParseIntError), } impl ParsePosNonzeroError { fn from_creation(err: CreationError) -> ParsePosNonzeroError { ParsePosNonzeroError::Creation(err) } // TODO: add another error conversion function here. fn from_parseint(err: ParseIntError) -> ParsePosNonzeroError { ParsePosNonzeroError::ParseInt(err) } } fn parse_pos_nonzero(s: &str) -> Result<PositiveNonzeroInteger, 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)?; PositiveNonzeroInteger::new(x).map_err(ParsePosNonzeroError::from_creation) } // Don't change anything below this line. #[derive(PartialEq, Debug)] struct PositiveNonzeroInteger(u64); #[derive(PartialEq, Debug)] enum CreationError { Negative, Zero, } impl PositiveNonzeroInteger { fn new(value: i64) -> Result<PositiveNonzeroInteger, CreationError> { match value { x if x < 0 => Err(CreationError::Negative), x if x == 0 => Err(CreationError::Zero), x => Ok(PositiveNonzeroInteger(x as u64)), } } } #[cfg(test)] mod test { use super::*; #[test] fn test_parse_error() { // We can't construct a ParseIntError, so we have to pattern match. assert!(matches!( parse_pos_nonzero("not a number"), Err(ParsePosNonzeroError::ParseInt(_)) )); } #[test] fn test_negative() { assert_eq!( parse_pos_nonzero("-555"), Err(ParsePosNonzeroError::Creation(CreationError::Negative)) ); } #[test] fn test_zero() { assert_eq!( parse_pos_nonzero("0"), Err(ParsePosNonzeroError::Creation(CreationError::Zero)) ); } #[test] fn test_positive() { let x = PositiveNonzeroInteger::new(42); assert!(x.is_ok()); assert_eq!(parse_pos_nonzero("42"), Ok(x.unwrap())); } }
再解釋一下 let x: i64 = s.parse().map_err(ParsePosNonzeroError::from_parseint)?;
為什麼是這麼個順序,雖然它表達的意思我們大致能夠理解。
這行代碼的意思是,如果 s.parse()
解析成功,則將解析後的整數值賦值給 x;如果解析失敗,? 操作符會立即返回並將 ParseIntError
轉換為 ParsePosNonzeroError::ParseInt
錯誤,並將其作為 parse_pos_nonzero
函數的返回結果。
在 Rust 中,? 操作符用於簡化錯誤處理的過程。它只能在返回 Result 或 Option 的函數中使用。當使用 ? 操作符時,編譯器會自動為你處理錯誤的傳播。
具體到代碼中,s.parse()
返回的是一個 Result<i64, ParseIntError>
,該結果表示解析字元串為整數的過程。如果解析成功,返回 Ok 包含解析後的整數值;如果解析失敗,則返回 Err 包含一個 ParseIntError
錯誤。
17. generics
大名鼎鼎的泛型,我在C++那邊都還沒有好好學習,就過來rust這邊再看一遍了。泛型對編程語言是極其重要的,它意味著可以用同一功能的函數處理不同類型的數據。
-
generics1: vector的建立罷了,為什麼提vector啊,因為vector不會預設創建哪個數據類型,必須指定一個類型來建立,這是一個泛型的雛形吧。
fn main() { let mut shopping_list: Vec<&str> = Vec::new(); shopping_list.push("milk"); }
-
generics2:考察結構體及其方法泛型的使用。註意這裡impl後也要加T,這樣 Rust 就知道
Point
的尖括弧中的類型是泛型而不是具體類型。這裡的Point<T>
不再是泛型聲明,而是一個完整的結構體類型,因為我們定義的結構體就是Point<T>
而不再是Point
。struct Point<T> { x: T, y: T, } impl<T> Point<T> { fn x(&self) -> &T { &self.x } }
因為已經這麼說了,所以是存在impl塊中直接定義具體類型結構體的對應方法的。
impl Point `<f32>` { fn distance_from_origin(&self) -> f32 { (self.x.powi(2) + self.y.powi(2)).sqrt() } }
解答如下:
struct Wrapper<T> { value: T, } impl<T> Wrapper<T> { pub fn new(value: T) -> Self { Wrapper { value } } }
剩下的東西就是const泛型和泛型的性能,本來不打算看了,但是看到const泛型適用於記憶體較小,所以我繼續看了一下。"在泛型參數之前,Rust 完全不適合複雜矩陣的運算,自從有了 const 泛型,一切即將改變。"
18. traits
特征Trait--聖經相關,特征跟介面的作用類似。跟大二學的java的抽象介面很像,以及C++的抽象類,就是特征trait中只定義共同行為,但是不描述具體行為的實現,具體行為的實現交給符合該特征的具體類型中來實現。
特征看起來好難。
題目雖然做完了,但是對特征的高級一點的用法還是不太理解它的邏輯。
-
traits1:考察impl trait_name for struct的簡單語法。這裡註意self是引用類型,不要把self當string。
impl AppendBar for String { // TODO: Implement `AppendBar` for type `String`. fn append_bar(self) -> Self { self+"Bar" } }
-
traits2:還是很簡單,並且跟traits1很像,但是這裡突然讓我想起來考慮一下vector的push方法的返回值類型和所有權的相關情況,見後面的知識補充。
// TODO: Implement trait `AppendBar` for a vector of strings. impl AppendBar for Vec<String> { fn append_bar(mut self) -> Vec<String> { self.push("Bar".to_owned()); self } }
-
traits3:考察的是特征中可以實現一個預設方法,這樣實現這個特征的類型,既可以重載這個方法自己寫,也可以直接使用這個預設方法。
pub trait Licensed { fn licensing_info(&self) -> String{ String::from("Some information") } }
-
traits4:一個很驚艷的rust語言特性,使用特征作為函數參數,這裡聖經用的是引用類型,而這裡要靈活變通,因為調用實參限制是傳入本身所有權而不是引用。
// YOU MAY ONLY CHANGE THE NEXT LINE fn compare_license_types(software: impl Licensed, software_two: impl Licensed) -> bool { software.licensing_info() == software_two.licensing_info() }
-
traits5:特征作為函數參數只是一個語法糖,它真正的本質是特征約束 ,上面traits4的語法糖就是簡單的一重約束,展開寫為形如:
pub fn notify(item1: &impl Summary, item2: &impl Summary) {} //展開寫為: pub fn notify<T: Summary>(item1: &T, item2: &T) {}
而多重約束時,就是如下形式:
pub fn notify(item: &(impl Summary + Display)) {} // 展開 pub fn notify<T: Summary + Display>(item: &T) {}
這道題也就可以據此解答為:可以同時調傭兩個特征中的方法。
// YOU MAY ONLY CHANGE THE NEXT LINE fn some_func(item: impl OtherTrait + SomeTrait) -> bool { item.some_function() && item.other_function() }
知識補充:vector.push()返回值與所有權
在 Rust 中,Vec 的 push 方法用於將一個元素添加到向量(Vec)的末尾。讓我們來詳細講解一下它的返回值類型和所有權。
push 方法的簽名如下:
pub fn push(&mut self, value: T)
返回值類型是 (),也被稱為“單元類型”或“空元組”。這意味著 push 方法不返回具體的值,僅用於修改 Vec 本身。
關於所有權,當調用 push
方法時,向量會獲取傳入元素(value
)的所有權。也就是說,在調用 push
方法之後,傳入的元素將成為向量的一部分,向量會負責管理它的生命周期和記憶體。這意味著傳入的元素不再屬於原來的所有者,而是屬於向量。
此外,需要註意的是,為了能夠修改向量,我們需要將 &mut self
作為方法的第一個參數。這表示我們需要擁有向量的可變引用,以便能夠對其進行修改。
下麵是一個使用 push
方法的示例:
let mut vec = Vec::new();
vec.push(10);
在上述代碼中,我們創建了一個空的 Vec
,然後調用 push
方法將整數 10
添加到向量的末尾。在調用 push
方法之後,vec
將會擁有整數 10
的所有權。
19. quiz3
這裡是多重特征的一個實現實例。註意這裡由於impl塊中使用了format!這種巨集,所以還要聲明使用了Display特征。通過在 impl
聲明中指定 T: std::fmt::Display
,表明泛型類型 T
必須實現 std::fmt::Display
trait。
pub struct ReportCard<T> {
pub grade: T,
pub student_name: String,
pub student_age: u8,
}
impl<T: std::fmt::Display> ReportCard<T> {
pub fn print(&self) -> String {
format!("{} ({}) - achieved a grade of {}",
&self.student_name, &self.student_age, &self.grade)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn generate_numeric_report_card() {
let report_card = ReportCard {
grade: 2.1,
student_name: "Tom Wriggle".to_string(),
student_age: 12,
};
assert_eq!(
report_card.print(),
"Tom Wriggle (12) - achieved a grade of 2.1"
);
}
#[test]
fn generate_alphabetic_report_card() {
// TODO: Make sure to change the grade here after you finish the exercise.
let report_card = ReportCard {
grade: "A+",
student_name: "Gary Plotter".to_string(),
student_age: 11,
};
assert_eq!(
report_card.print(),
"Gary Plotter (11) - achieved a grade of A+"
);
}
}
20. lifetimes
生命周期!用rust寫編譯器的時候最煩這方面的問題,這次來好好看看。"生命周期很可能是 Rust 中最難的部分."聖經書上相關部分
簡單來說,程式員如果對生命周期判斷不好,就會引發程式的隱藏問題,並且很難被髮現。而rust在編譯器層次實現了生命周期的檢查。與之適配的,為了通過生命周期檢查,寫rust的時候有時候需要手動標註生命周期(其他語言和此前的rust都是編譯器自動推導生命周期)。
生命周期主要是解決懸垂引用問題。可以對生命周期進行下總結:生命周期語法用來將函數的多個引用參數和返回值的作用域關聯到一起,一旦關聯到一起後,Rust 就擁有充分的信息來確保我們的操作是記憶體安全的。
- lifetimes1:就是聖經書上這部分的原例。
- lifetimes2:還是書上原例,原因是result的生命周期跟函數longest返回的最短生命周期一樣長,而longest返回的最短聲明周期是string2,string2由於在大括弧內定義,到大括弧結束就死了,所以result在大括弧外也無法使用。修改就是只需要將string2的定義/綁定放到大括弧外。
- lifetimes3:很簡單,就是結構體的生命周期如何聲明,沒有涉及邏輯問題。
剩下的內容就是rust編譯器更貼心一點的生命周期消除規則、方法中的生命周期(主要在說self的第三規則和生命周期約束)、靜態生命周期,差不多看明白了,感覺比特征更好理解一些,特征還要回去看看。
到這裡我不太熟悉的是 modules 和 generics 這兩部分,其他的感覺還好。生命周期仔細看完聖經覺得還可以接受。
21. tests
- tests1:介紹一下test模塊是長什麼樣的。
assert!(true);
讓它無事發生即可。 - tests2:
assert_eq!(1,1);
這個斷言就是檢查是否相等,前面rustlings很多題都出現過了。 - tests3:剛開始預設是assert_eq!,然後發現是assert!.
pub fn is_even(num: i32) -> bool { num % 2 == 0 } #[cfg(test)] mod tests { use super::*; #[test] fn is_true_when_even() { assert!(is_even(2)); } #[test] fn is_false_when_odd() { assert!(!is_even(5)); } }
- tests4: #[should_panic]特性。當被測試代碼panic時來進行處理。
22. Iterators
迭代器。
-
iterators1: 迭代器的惰性初始化.iter()和next() 訪問方法。
fn main() { let my_fav_fruits = vec!["banana", "custard apple", "avocado", "peach", "raspberry"]; let mut my_iterable_fav_fruits = my_fav_fruits.iter(); // TODO: Step 1 assert_eq!(my_iterable_fav_fruits.next(), Some(&"banana")); assert_eq!(my_iterable_fav_fruits.next(), Some(&"custard apple")); // TODO: Step 2 assert_eq!(my_iterable_fav_fruits.next(), Some(&"avocado")); assert_eq!(my_iterable_fav_fruits.next(), Some(&"peach")); // TODO: Step 3 assert_eq!(my_iterable_fav_fruits.next(), Some(&"raspberry")); assert_eq!(my_iterable_fav_fruits.next(), None); // TODO: Step 4 }
-
iterators2: 這道題沒有很好寫,因為發現自己的字元串那一節也沒有好好看完,很多方法都還不會。
// Step 1. // Complete the `capitalize_first` function. // "hello" -> "Hello" pub fn capitalize_first(input: &str) -> String { let mut c = input.chars(); match c.next() { None => String::new(), // 返回空: "" Some(first) => first.to_uppercase().collect::<String>() + c.as_str(), } } // Step 2. // Apply the `capitalize_first` function to a slice of string slices. // Return a vector of strings. // ["hello", "world"] -> ["Hello", "World"] pub fn capitalize_words_vector(words: &[&str]) -> Vec<String> { // vec![] words.iter().map(|&word| capitalize_first(word)).collect() } // Step 3. // Apply the `capitalize_first` function again to a slice of string slices. // Return a single string. // ["hello", " ", "world"] -> "Hello World" pub fn capitalize_words_string(words: &[&str]) -> String { // String::new() words.iter().map(|&word| capitalize_first(word)).collect() } #[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: test比較多,考察的也比較多,首先困擾了我一下的是Err的return問題,為什麼必須要return才行,為什麼不能通過rust不加分號的語法糖直接返回,後面覺得Err()是一個表達式。接著就是重點的一個Result數組和數組Result,這個例子就體現了collect的功能強大,可以自動收集為目標類型。
#[derive(Debug, PartialEq, Eq)] pub enum DivisionError { NotDivisible(NotDivisibleError), DivideByZero, } #[derive(Debug, PartialEq, Eq)] pub struct NotDivisibleError { dividend: i32, divisor: i32, } // Calculate `a` divided by `b` if `a` is evenly divisible by `b`. // Otherwise, return a suitable error. pub fn divide(a: i32, b: i32) -> Result<i32, DivisionError> { // todo!(); if b == 0 { return Err(DivisionError::DivideByZero); } if a % b != 0 { return Err(DivisionError::NotDivisible(NotDivisibleError{ dividend: a, divisor: b, })); } Ok(a/b) } // Complete the function and return a value of the correct type so the test // passes. // Desired output: Ok([1, 11, 1426, 3]) fn result_with_list() -> Result<Vec<i32>, DivisionError> { let numbers = vec![27, 297, 38502, 81]; let division_results = numbers.into_iter().map(|n| divide(n, 27)).collect(); // numbers.into_iter().map(|n| divide(n, 27)).collect() division_results } // Complete the function and return a value of the correct type so the test // passes. // Desired output: [Ok(1), Ok(11), Ok(1426), Ok(3)] fn list_of_results() -> Vec<Result<i32, DivisionError>>{ let numbers = vec![27, 297, 38502, 81]; let division_results = numbers.into_iter().map(|n| divide(n, 27)).collect(); division_results } #[cfg(test)] mod tests { use super::*; #[test] fn test_success() { assert_eq!(divide(81, 9), Ok(9)); } #[test] fn test_not_divisible() { assert_eq!( divide(81, 6), Err(DivisionError::NotDivisible(NotDivisibleError { dividend: 81, divisor: 6 })) ); } #[test] fn test_divide_by_0() { assert_eq!(divide(81, 0), Err(DivisionError::DivideByZero)); } #[test] fn test_divide_0_by_something() { assert_eq!(divide(0, 81), Ok(0)); } #[test] fn test_result_with_list() { assert_eq!(format!("{:?}", result_with