Rust错题本
缘起
灵感来自:rust-lang/rustlings: Small exercises to get you used to reading and writing Rust code!
余学Rust一载有余,深感其“一说就会,一写就废“的特点。在整理了几本电子书后,受到rustlings启发,觉得确实可以起一本“Rust错题本“.
rustlings不错,更需要自己整理
rustlings使用起来确实很方便,其代码也值得研究,是常见的rust入门demo类型-命令行工具。但是它必须在电脑上运行,不适合随时练习,所以觉得mdbook在线运行会更方便。
admonish-mdbook
Reference - The mdbook-admonish book
单元测试与main函数
rustlings默认代码中大都使用单元测试进行验证,这也和其本身用意相符:通过编译器的测试。但是我都手动转为main函数,这是为了方便在网页上可以在rust playground中执行。
Exercise to Book Chapter mapping
| Exercise | Book Chapter |
|---|---|
| variables | §3.1 |
| functions | §3.3 |
| if | §3.5 |
| primitive_types | §3.2, §4.3 |
| vecs | §8.1 |
| move_semantics | §4.1-2 |
| structs | §5.1, §5.3 |
| enums | §6, §18.3 |
| strings | §8.2 |
| modules | §7 |
| hashmaps | §8.3 |
| options | §10.1 |
| error_handling | §9 |
| generics | §10 |
| traits | §10.2 |
| tests | §11.1 |
| lifetimes | §10.3 |
| iterators | §13.2-4 |
| threads | §16.1-3 |
| smart_pointers | §15, §16.3 |
| macros | §19.6 |
| clippy | §21.4 |
| conversions | n/a |
Intro
Rust uses the print! and println! macros to print text to the console.
Further information
Rustlings Quiz
Primitive Types
Rust has a couple of basic types that are directly implemented into the compiler. In this section, we’ll go through the most important ones.
Further information
Rustlings
primitive_types1: Fill in the rest of the line that has code missing!
// primitive_types1.rs // Fill in the rest of the line that has code missing! // No hints, there's no tricks, just get used to typing these :) // I AM NOT DONE fn main() { // Booleans (`bool`) let is_morning = true; if is_morning { println!("Good morning!"); } let // Finish the rest of this line like the example! Or make it be false! if is_evening { println!("Good evening!"); } }
primitive_types2: Fill in the rest of the line that has code missing!
// primitive_types2.rs // Fill in the rest of the line that has code missing! // No hints, there's no tricks, just get used to typing these :) // I AM NOT DONE fn main() { // Characters (`char`) // Note the _single_ quotes, these are different from the double quotes // you've been seeing around. let my_first_initial = 'C'; if my_first_initial.is_alphabetic() { println!("Alphabetical!"); } else if my_first_initial.is_numeric() { println!("Numerical!"); } else { println!("Neither alphabetic nor numeric!"); } let // Finish this line like the example! What's your favorite character? // Try a letter, try a number, try a special character, try a character // from a different language than your own, try an emoji! if your_character.is_alphabetic() { println!("Alphabetical!"); } else if your_character.is_numeric() { println!("Numerical!"); } else { println!("Neither alphabetic nor numeric!"); } }
primitive_types3: Create an array with at least 100 elements in it where the ??? is.
// primitive_types3.rs // Create an array with at least 100 elements in it where the ??? is. // Execute `rustlings hint primitive_types3` or use the `hint` watch subcommand for a hint. // I AM NOT DONE fn main() { let a = ??? if a.len() >= 100 { println!("Wow, that's a big array!"); } else { println!("Meh, I eat arrays like that for breakfast."); } }
Hint
There’s a shorthand to initialize Arrays with a certain size that does not require you to type in 100 items (but you certainly can if you want!). For example, you can do: let array = [“Are we there yet?”; 10];
Bonus: what are some other things you could have that would return true
for a.len() >= 100?
solution
// primitive_types3.rs // Create an array with at least 100 elements in it where the ??? is. // Execute `rustlings hint primitive_types3` or use the `hint` watch subcommand for a hint. fn main() { let a = ["a"; 110]; if a.len() >= 100 { println!("Wow, that's a big array!"); } else { println!("Meh, I eat arrays like that for breakfast."); } }
primitive_types4: Get a slice out of Array a
// primitive_types4.rs // Get a slice out of Array a where the ??? is so that the test passes. // Execute `rustlings hint primitive_types4` or use the `hint` watch subcommand for a hint. // I AM NOT DONE fn main() { let a = [1, 2, 3, 4, 5]; let nice_slice = ??? assert_eq!([2, 3, 4], nice_slice) }
primitive_types5: Destructure the cat tuple
// primitive_types5.rs // Destructure the `cat` tuple so that the println will work. // Execute `rustlings hint primitive_types5` or use the `hint` watch subcommand for a hint. // I AM NOT DONE fn main() { let cat = ("Furry McFurson", 3.5); let /* your pattern here */ = cat; println!("{} is {} years old.", name, age); }
primitive_types6: Use a tuple index to access the second element of numbers.
// primitive_types6.rs // Use a tuple index to access the second element of `numbers`. // You can put the expression for the second element where ??? is so that the test passes. // Execute `rustlings hint primitive_types6` or use the `hint` watch subcommand for a hint. // I AM NOT DONE #[test] fn indexing_tuple() { let numbers = (1, 2, 3); // Replace below ??? with the tuple indexing syntax. let second = ???; assert_eq!(2, second, "This is not the 2nd number in the tuple!") }
Strings: &str for string slice and String for owned string
Rust has two string types, a string slice (&str) and an owned string (String).
We’re not going to dictate when you should use which one, but we’ll show you how
to identify and create them, as well as use them.
Further information
- Strings: Storing UTF-8 Encoded Text - The Rust Programming Language
- rustlings-solutions-5/strings at main · gaveen/rustlings-solutions-5
Rustlings
strings1: String -> to_string()
// strings1.rs // Make me compile without changing the function signature! // Execute `rustlings hint strings1` or use the `hint` watch subcommand for a hint. // I AM NOT DONE fn main() { let answer = current_favorite_color(); println!("My current favorite color is {}", answer); } fn current_favorite_color() -> String { "blue" }
Hint
The current_favorite_color function is currently returning a string slice with the 'static
lifetime.
We know this because the data of the string lives in our code itself – it doesn’t come from a file or user input or another program – so it will live as long as our program lives.
But it is still a string slice. There’s one way to create a String by converting a
string slice covered in the Strings chapter of the book, and another way that uses the From
trait.
strings2: &str -> as_str()
// strings2.rs // Make me compile without changing the function signature! // Execute `rustlings hint strings2` or use the `hint` watch subcommand for a hint. // I AM NOT DONE 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" }
Hint
Yes, it would be really easy to fix this by just changing the value bound to word to be a
string slice instead of a String, wouldn’t it?? There is a way to add one character to line
9, though, that will coerce the String into a string slice.
strings3
// strings3.rs // Execute `rustlings hint strings3` or use the `hint` watch subcommand for a hint. // I AM NOT DONE fn trim_me(input: &str) -> String { // TODO: Remove whitespace from both ends of a string! ? ? ? } fn compose_me(input: &str) -> String { // TODO: Add " world!" to the string! There's multiple ways to do this! ? ? ? } fn replace_me(input: &str) -> String { // TODO: Replace "cars" in the string with "balloons"! ? ? ? } // convert unit tests to main fn main() { fn trim_a_string() { assert_eq!(trim_me("Hello! "), "Hello!"); assert_eq!(trim_me(" What's up!"), "What's up!"); assert_eq!(trim_me(" Hola! "), "Hola!"); } fn compose_a_string() { assert_eq!(compose_me("Hello"), "Hello world!"); assert_eq!(compose_me("Goodbye"), "Goodbye world!"); } fn replace_a_string() { assert_eq!(replace_me("I think cars are cool"), "I think balloons are cool"); assert_eq!(replace_me("I love to look at cars"), "I love to look at balloons"); } trim_a_string(); compose_a_string(); replace_a_string(); } #[cfg(test)] mod tests { use super::*; #[test] fn trim_a_string() { assert_eq!(trim_me("Hello! "), "Hello!"); assert_eq!(trim_me(" What's up!"), "What's up!"); assert_eq!(trim_me(" Hola! "), "Hola!"); } #[test] fn compose_a_string() { assert_eq!(compose_me("Hello"), "Hello world!"); assert_eq!(compose_me("Goodbye"), "Goodbye world!"); } #[test] fn replace_a_string() { assert_eq!(replace_me("I think cars are cool"), "I think balloons are cool"); assert_eq!(replace_me("I love to look at cars"), "I love to look at balloons"); } }
Hint
There’s tons of useful standard library functions for strings. Let’s try and use some of them: String in std::string - Rust!
For the compose_me method: You can either use the format! macro, or convert the string
slice into an owned string, which you can then freely extend.
solution
fn trim_me(input: &str) -> String { // Remove whitespace from the beginning and the end of a string! input.trim().to_string() } fn compose_me(input: &str) -> String { // Add to the string! There's multiple ways to do this! input.to_owned() + " world!" } fn replace_me(input: &str) -> String { // Replace "cars" in the string with "balloons"! input.replace("cars", "balloons") }
strings4
// strings4.rs // Ok, here are a bunch of values-- some are `String`s, some are `&str`s. Your // task is to call one of these two functions on each value depending on what // you think each value is. That is, add either `string_slice` or `string` // before the parentheses on each line. If you're right, it will compile! // No hints this time! // I AM NOT DONE fn string_slice(arg: &str) { println!("{}", arg); } fn string(arg: String) { println!("{}", arg); } fn main() { ???("blue"); ???("red".to_string()); ???(String::from("hi")); ???("rust is fun!".to_owned()); ???("nice weather".into()); ???(format!("Interpolation {}", "Station")); ???(&String::from("abc")[0..1]); ???(" hello there ".trim()); ???("Happy Monday!".to_string().replace("Mon", "Tues")); ???("mY sHiFt KeY iS sTiCkY".to_lowercase()); }
solution: distinguish String and &str
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()); }
Vectors
Vectors are one of the most-used Rust data structures. In other programming languages, they’d simply be called Arrays, but since Rust operates on a bit of a lower level, an array in Rust is stored on the stack (meaning it can’t grow or shrink, and the size needs to be known at compile time), and a Vector is stored in the heap (where these restrictions do not apply).
Vectors are a bit of a later chapter in the book, but we think that they’re useful enough to talk about them a bit earlier. We shall be talking about the other useful data structure, hash maps, later.
Further information
- Vectors: Storing Lists of The Same Type of Values - The Rust Programming Language
iter_mutmap- rustlings-solutions-5/vecs at main · gaveen/rustlings-solutions-5
Rustlings
vecs1: [i32; 4] or vec!
// vecs1.rs // Your task is to create a `Vec` which holds the exact same elements // as in the array `a`. // Make me compile and pass the test! // Execute `rustlings hint vecs1` or use the `hint` watch subcommand for a hint. // I AM NOT DONE fn array_and_vec() -> ([i32; 4], Vec<i32>) { let a = [10, 20, 30, 40]; // a plain array let v = // TODO: declare your vector here with the macro for vectors (a, v) } // convert unit tests to main here fn main() { let (a, v) = array_and_vec(); assert_eq!(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[..]); } }
Hint
In Rust, there are two ways to define a Vector.
- One way is to use the
Vec::new()function to create a new vector and fill it with thepush()method. - The second way, which is simpler is to use the
vec![]macro and define your elements inside the square brackets. Check this chapter: Vectors: Storing Lists of The Same Type of Values - The Rust Programming Language of the Rust book to learn more.
vecs2
// vecs2.rs // A Vec of even numbers is given. Your task is to complete the loop // so that each number in the Vec is multiplied by 2. // // Make me pass the test! // // Execute `rustlings hint vecs2` or use the `hint` watch subcommand for a hint. // I AM NOT DONE fn vec_loop(mut v: Vec<i32>) -> Vec<i32> { for i in v.iter_mut() { // TODO: Fill this up so that each element in the Vec `v` is // multiplied by 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(|num| { // TODO: Do the same thing as above - but instead of mutating the // Vec, you can just return the new number! ??? }).collect() } // convert unit tests to main here fn main(){ 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>>()); } 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>>()); } test_vec_loop(); test_vec_map(); } #[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>>()); } }
Hint
Hint 1: i is each element from the Vec as they are being iterated. Can you try
multiplying this?
Hint 2: For the first function, there’s a way to directly access the numbers stored in the Vec, using the * dereference operator. You can both access and write to the number that way.
After you’ve completed both functions, decide for yourself which approach you like better. What do you think is the more commonly used pattern under Rust developers?
solution1
fn vec_loop(mut v: Vec<i32>) -> Vec<i32> { for i in v.iter_mut() { *i *= 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(|num| { num*2 }).collect() }
Hashmaps
A hash map allows you to associate a value with a particular key. You may also know this by the names unordered map in C++, dictionary in Python or an associative array in other languages.
This is the other data structure that we’ve been talking about before, when talking about Vecs.
Further information
Rustlings
hashmaps1
// hashmaps1.rs // A basket of fruits in the form of a hash map needs to be defined. // The key represents the name of the fruit and the value represents // how many of that particular fruit is in the basket. You have to put // at least three different types of fruits (e.g apple, banana, mango) // in the basket and the total count of all the fruits should be at // least five. // // Make me compile and pass the tests! // // Execute `rustlings hint hashmaps1` or use the `hint` watch subcommand for a hint. // I AM NOT DONE use std::collections::HashMap; fn fruit_basket() -> HashMap<String, u32> { let mut basket = // 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 } #[cfg(test)] mod tests { use super::*; #[test] fn at_least_three_types_of_fruits() { let basket = fruit_basket(); assert!(basket.len() >= 3); } #[test] fn at_least_five_fruits() { let basket = fruit_basket(); assert!(basket.values().sum::<u32>() >= 5); } }
hashmaps2
// hashmaps2.rs // A basket of fruits in the form of a hash map is given. The key // represents the name of the fruit and the value represents how many // of that particular fruit is in the basket. You have to put *MORE // THAN 11* fruits in the basket. Three types of fruits - Apple (4), // Mango (2) and Lychee (5) are already given in the basket. You are // not allowed to insert any more of these fruits! // // Make me pass the tests! // // Execute `rustlings hint hashmaps2` or use the `hint` watch subcommand for a hint. // I AM NOT DONE use std::collections::HashMap; #[derive(Hash, PartialEq, Eq)] enum Fruit { Apple, Banana, Mango, Lychee, Pineapple, } fn fruit_basket(basket: &mut HashMap<Fruit, u32>) { let fruit_kinds = vec![ Fruit::Apple, Fruit::Banana, Fruit::Mango, Fruit::Lychee, Fruit::Pineapple, ]; 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! } } #[cfg(test)] mod tests { use super::*; fn get_fruit_basket() -> HashMap<Fruit, u32> { let mut basket = HashMap::<Fruit, u32>::new(); basket.insert(Fruit::Apple, 4); basket.insert(Fruit::Mango, 2); basket.insert(Fruit::Lychee, 5); basket } #[test] fn test_given_fruits_are_not_modified() { let mut basket = get_fruit_basket(); fruit_basket(&mut basket); assert_eq!(*basket.get(&Fruit::Apple).unwrap(), 4); assert_eq!(*basket.get(&Fruit::Mango).unwrap(), 2); assert_eq!(*basket.get(&Fruit::Lychee).unwrap(), 5); } #[test] fn at_least_five_types_of_fruits() { let mut basket = get_fruit_basket(); fruit_basket(&mut basket); let count_fruit_kinds = basket.len(); assert!(count_fruit_kinds >= 5); } #[test] fn greater_than_eleven_fruits() { let mut basket = get_fruit_basket(); fruit_basket(&mut basket); let count = basket.values().sum::<u32>(); assert!(count > 11); } }
hashmaps3
// hashmaps3.rs // A list of scores (one per line) of a soccer match is given. Each line // is of the form : // <team_1_name>,<team_2_name>,<team_1_goals>,<team_2_goals> // Example: England,France,4,2 (England scored 4 goals, France 2). // You have to build a scores table containing the name of the team, goals // the team scored, and goals the team conceded. One approach to build // the scores table is to use a Hashmap. The solution is partially // written to use a Hashmap, complete it to pass the test. // Make me pass the tests! // Execute `rustlings hint hashmaps3` or use the `hint` watch subcommand for a hint. // I AM NOT DONE use std::collections::HashMap; // A structure to store team name and its goal details. struct Team { name: String, 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(); for r in results.lines() { 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. } 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); } }
Structs
Rust has three struct types: a classic C struct, a tuple struct, and a unit struct.
Further information
Rustlings
structs1
// structs1.rs // Address all the TODOs to make the tests pass! // Execute `rustlings hint structs1` or use the `hint` watch subcommand for a hint. // I AM NOT DONE struct ColorClassicStruct { // TODO: Something goes here } struct ColorTupleStruct(/* TODO: Something goes here */); #[derive(Debug)] struct UnitLikeStruct; #[cfg(test)] mod tests { use super::*; #[test] fn classic_c_structs() { // TODO: Instantiate a classic c struct! // let green = 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 = 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 = let message = format!("{:?}s are fun!", unit_like_struct); assert_eq!(message, "UnitLikeStructs are fun!"); } }
structs2
// structs2.rs // Address all the TODOs to make the tests pass! // Execute `rustlings hint structs2` or use the `hint` watch subcommand for a hint. // I AM NOT DONE #[derive(Debug)] struct Order { name: String, year: u32, made_by_phone: bool, made_by_mobile: bool, made_by_email: bool, item_number: u32, count: u32, } fn create_order_template() -> Order { Order { name: String::from("Bob"), year: 2019, made_by_phone: false, made_by_mobile: false, made_by_email: true, item_number: 123, count: 0, } } #[cfg(test)] mod tests { use super::*; #[test] fn your_order() { let order_template = create_order_template(); // TODO: Create your own order using the update syntax and template above! // let your_order = assert_eq!(your_order.name, "Hacker in Rust"); assert_eq!(your_order.year, order_template.year); assert_eq!(your_order.made_by_phone, order_template.made_by_phone); assert_eq!(your_order.made_by_mobile, order_template.made_by_mobile); assert_eq!(your_order.made_by_email, order_template.made_by_email); assert_eq!(your_order.item_number, order_template.item_number); assert_eq!(your_order.count, 1); } }
structs3
// structs3.rs // Structs contain data, but can also have logic. In this exercise we have // defined the Package struct and we want to test some logic attached to it. // Make the code compile and the tests pass! // Execute `rustlings hint structs3` or use the `hint` watch subcommand for a hint. // I AM NOT DONE #[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) -> ??? { // Something goes here... } fn get_fees(&self, cents_per_gram: i32) -> ??? { // Something goes here... } } #[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); } }
Enums
Rust allows you to define types called “enums” which enumerate possible values. Enums are a feature in many languages, but their capabilities differ in each language. Rust’s enums are most similar to algebraic data types in functional languages, such as F#, OCaml, and Haskell. Useful in combination with enums is Rust’s “pattern matching” facility, which makes it easy to run different code for different values of an enumeration.
Further information
Rustlings
enums1
// enums1.rs // No hints this time! ;) // I AM NOT DONE #[derive(Debug)] enum Message { // TODO: define a few types of messages as used below } fn main() { println!("{:?}", Message::Quit); println!("{:?}", Message::Echo); println!("{:?}", Message::Move); println!("{:?}", Message::ChangeColor); }
enums2
// enums2.rs // Execute `rustlings hint enums2` or use the `hint` watch subcommand for a hint. // I AM NOT DONE #[derive(Debug)] enum Message { // TODO: define the different variants used below } 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
// enums3.rs // Address all the TODOs to make the tests pass! // Execute `rustlings hint enums3` or use the `hint` watch subcommand for a hint. // I AM NOT DONE enum Message { // TODO: implement the message variant types based on their usage below } struct Point { x: u8, y: u8, } struct State { color: (u8, u8, u8), position: Point, quit: bool, } impl State { fn change_color(&mut self, color: (u8, u8, u8)) { self.color = color; } fn quit(&mut self) { self.quit = true; } fn echo(&self, s: String) { println!("{}", 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)) } } #[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), }; 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); } }
Iterators
This section will teach you about Iterators.
Further information
rustlings-solutions-5/standard_library_types at main · gaveen/rustlings-solutions-5
Rustlings
iterators1
// iterators1.rs // // Make me compile by filling in the `???`s // // When performing operations on elements within a collection, iterators are essential. // This module helps you get familiar with the structure of using an iterator and // how to go through elements within an iterable collection. // // Execute `rustlings hint iterators1` or use the `hint` watch subcommand for a hint. // I AM NOT DONE fn main () { let my_fav_fruits = vec!["banana", "custard apple", "avocado", "peach", "raspberry"]; let mut my_iterable_fav_fruits = ???; // TODO: Step 1 assert_eq!(my_iterable_fav_fruits.next(), Some(&"banana")); assert_eq!(my_iterable_fav_fruits.next(), ???); // TODO: Step 2 assert_eq!(my_iterable_fav_fruits.next(), Some(&"avocado")); assert_eq!(my_iterable_fav_fruits.next(), ???); // TODO: Step 3 assert_eq!(my_iterable_fav_fruits.next(), Some(&"raspberry")); assert_eq!(my_iterable_fav_fruits.next(), ???); // TODO: Step 4 }
iterators2
// iterators2.rs // In this exercise, you'll learn some of the unique advantages that iterators // can offer. Follow the steps to complete the exercise. // Execute `rustlings hint iterators2` or use the `hint` watch subcommand for a hint. // I AM NOT DONE // 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) => ???, } } // 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![] } // 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() } #[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
// iterators3.rs // This is a bigger exercise than most of the others! You can do it! // Here is your mission, should you choose to accept it: // 1. Complete the divide function to get the first four tests to pass. // 2. Get the remaining tests to pass by completing the result_with_list and // list_of_results functions. // Execute `rustlings hint iterators3` or use the `hint` watch subcommand for a hint. // I AM NOT DONE #[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!(); } // 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() -> () { let numbers = vec![27, 297, 38502, 81]; let division_results = numbers.into_iter().map(|n| divide(n, 27)); } // 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() -> () { let numbers = vec![27, 297, 38502, 81]; let division_results = numbers.into_iter().map(|n| divide(n, 27)); } #[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_list()), "Ok([1, 11, 1426, 3])"); } #[test] fn test_list_of_results() { assert_eq!( format!("{:?}", list_of_results()), "[Ok(1), Ok(11), Ok(1426), Ok(3)]" ); } }
iterators4
// iterators4.rs // Execute `rustlings hint iterators4` or use the `hint` watch subcommand for a hint. // I AM NOT DONE pub fn factorial(num: u64) -> u64 { // Complete this function to return the factorial of num // Do not use: // - return // Try not to use: // - imperative style loops (for, while) // - additional variables // For an extra challenge, don't use: // - recursion // Execute `rustlings hint iterators4` for hints. } #[cfg(test)] mod tests { use super::*; #[test] fn factorial_of_0() { assert_eq!(1, factorial(0)); } #[test] fn factorial_of_1() { assert_eq!(1, factorial(1)); } #[test] fn factorial_of_2() { assert_eq!(2, factorial(2)); } #[test] fn factorial_of_4() { assert_eq!(24, factorial(4)); } }
iterators5
// iterators5.rs // Let's define a simple model to track Rustlings exercise progress. Progress // will be modelled using a hash map. The name of the exercise is the key and // the progress is the value. Two counting functions were created to count the // number of exercises with a given progress. These counting functions use // imperative style for loops. Recreate this counting functionality using // iterators. Only the two iterator methods (count_iterator and // count_collection_iterator) need to be modified. // Execute `rustlings hint iterators5` or use the `hint` watch subcommand for a hint. // // Make the code compile and the tests pass. // I AM NOT DONE use std::collections::HashMap; #[derive(Clone, Copy, PartialEq, Eq)] enum Progress { None, Some, Complete, } fn count_for(map: &HashMap<String, Progress>, value: Progress) -> usize { let mut count = 0; for val in map.values() { if val == &value { count += 1; } } count } fn count_iterator(map: &HashMap<String, Progress>, value: Progress) -> usize { // map is a hashmap with String keys and Progress values. // map = { "variables1": Complete, "from_str": None, ... } todo!(); } fn count_collection_for(collection: &[HashMap<String, Progress>], value: Progress) -> usize { let mut count = 0; for map in collection { for val in map.values() { if val == &value { count += 1; } } } count } fn count_collection_iterator(collection: &[HashMap<String, Progress>], value: Progress) -> usize { // collection is a slice of hashmaps. // collection = [{ "variables1": Complete, "from_str": None, ... }, // { "variables2": Complete, ... }, ... ] todo!(); } #[cfg(test)] mod tests { use super::*; #[test] fn count_complete() { let map = get_map(); assert_eq!(3, count_iterator(&map, Progress::Complete)); } #[test] fn count_equals_for() { let map = get_map(); assert_eq!( count_for(&map, Progress::Complete), count_iterator(&map, Progress::Complete) ); } #[test] fn count_collection_complete() { let collection = get_vec_map(); assert_eq!( 6, count_collection_iterator(&collection, Progress::Complete) ); } #[test] fn count_collection_equals_for() { let collection = get_vec_map(); assert_eq!( count_collection_for(&collection, Progress::Complete), count_collection_iterator(&collection, Progress::Complete) ); } fn get_map() -> HashMap<String, Progress> { use Progress::*; let mut map = HashMap::new(); map.insert(String::from("variables1"), Complete); map.insert(String::from("functions1"), Complete); map.insert(String::from("hashmap1"), Complete); map.insert(String::from("arc1"), Some); map.insert(String::from("as_ref_mut"), None); map.insert(String::from("from_str"), None); map } fn get_vec_map() -> Vec<HashMap<String, Progress>> { use Progress::*; let map = get_map(); let mut other = HashMap::new(); other.insert(String::from("variables2"), Complete); other.insert(String::from("functions2"), Complete); other.insert(String::from("if1"), Complete); other.insert(String::from("from_into"), None); other.insert(String::from("try_from_into"), None); vec![map, other] } }
Smart Pointers
In Rust, smart pointers are variables that contain an address in memory and reference some other data, but they also have additional metadata and capabilities. Smart pointers in Rust often own the data they point to, while references only borrow data.
Further Information
- 智能指针 - Anatomy In First Rust Programming Class 🦀
- ✨Smart Pointers: Heap、Deref、Drop、Rc、RefCell、Reference Cycle - The Rust Programming Language
- Heap: Using Box
to Point to Data on the Heap - The Rust Programming Language - Rc
(Reference Counting): single-threaded scenarios, immutable references of multiple owners, the Reference Counted Smart Pointer - The Rust Programming Language - Shared-State Concurrency - The Rust Programming Language
- Cow Documentation
rustlings-solutions-5/standard_library_types at main · gaveen/rustlings-solutions-5
Rustlings
Arc
- In this exercise, we are given a Vec of u32 called “numbers” with values ranging from 0 to 99 – [ 0, 1, 2, …, 98, 99 ]
- We would like to use this set of numbers within 8 different threads simultaneously.
- Each thread is going to get the sum of every eighth value, with an offset.
- The first thread (offset 0), will sum 0, 8, 16, …
- The second thread (offset 1), will sum 1, 9, 17, …
- The third thread (offset 2), will sum 2, 10, 18, …
- …
- The eighth thread (offset 7), will sum 7, 15, 23, …
Because we are using threads, our values need to be
thread-safe. Therefore, we are using Arc. We need to make a change in each of the two TODOs.
- Make this code compile by filling in a value for
shared_numberswhere the first TODO comment is - and create an initial binding for
child_numberswhere the second TODO comment is.
Try not to create any copies of the
numbersVec!
arc1: Using Arc to keep thread-safe
// arc1.rs // In this exercise, we are given a Vec of u32 called "numbers" with values ranging // from 0 to 99 -- [ 0, 1, 2, ..., 98, 99 ] // We would like to use this set of numbers within 8 different threads simultaneously. // Each thread is going to get the sum of every eighth value, with an offset. // The first thread (offset 0), will sum 0, 8, 16, ... // The second thread (offset 1), will sum 1, 9, 17, ... // The third thread (offset 2), will sum 2, 10, 18, ... // ... // The eighth thread (offset 7), will sum 7, 15, 23, ... // Because we are using threads, our values need to be thread-safe. Therefore, // we are using Arc. We need to make a change in each of the two TODOs. // Make this code compile by filling in a value for `shared_numbers` where the // first TODO comment is, and create an initial binding for `child_numbers` // where the second TODO comment is. Try not to create any copies of the `numbers` Vec! // Execute `rustlings hint arc1` or use the `hint` watch subcommand for a hint. // I AM NOT DONE #![forbid(unused_imports)] // Do not change this, (or the next) line. use std::sync::Arc; use std::thread; fn main() { let numbers: Vec<_> = (0..100u32).collect(); let shared_numbers = // TODO let mut joinhandles = Vec::new(); for offset in 0..8 { let child_numbers = // TODO joinhandles.push(thread::spawn(move || { let sum: u32 = child_numbers.iter().filter(|n| *n % 8 == offset).sum(); println!("Sum of offset {} is {}", offset, sum); })); } for handle in joinhandles.into_iter() { handle.join().unwrap(); } }
Hint
- Make
shared_numbersbe anArcfrom the numbers vector. - Then, in order
to avoid creating a copy of
numbers, you’ll need to createchild_numbersinside the loop but still in the main thread. child_numbersshould be a clone of the Arc of the numbers instead of a thread-local copy of the numbers.
This is a simple exercise if you understand the underlying concepts, but if this is too much of a struggle, consider reading through all of Concurrency Chapter in the book: Fearless Concurrency - The Rust Programming Language
solution: Arc::new()
#![forbid(unused_imports)] // Do not change this, (or the next) line. use std::sync::Arc; use std::thread; fn main() { let numbers: Vec<_> = (0..100u32).collect(); let shared_numbers = Arc::new(numbers); // filling in a value for `shared_numbers` let mut joinhandles = Vec::new(); for offset in 0..8 { let child_numbers = shared_numbers.clone(); // create an initial binding for `child_numbers` joinhandles.push(thread::spawn(move || { let sum: u32 = child_numbers.iter().filter(|n| *n % 8 == offset).sum(); println!("Sum of offset {} is {}", offset, sum); })); } for handle in joinhandles.into_iter() { handle.join().unwrap(); } }
Box
At compile time, Rust needs to know
how much space a type takes up. This becomes problematic forrecursive types, where a value can have as part of itself another value of the same type.
To get around the issue, we can use a Box - a smart pointer used to store data on the heap,
which also allows us to wrap a recursive type.
The recursive type we’re implementing in this exercise is the cons list - a data structure
frequently found in functional programming languages. Each item in a cons list contains two
elements: the value of the current item and the next item. The last item is a value called Nil.
- Step 1: use a
Boxin the enum definition to make the code compile - Step 2: create both empty and non-empty cons lists by replacing
todo!()
box1
// box1.rs // // At compile time, Rust needs to know how much space a type takes up. This becomes problematic // for recursive types, where a value can have as part of itself another value of the same type. // To get around the issue, we can use a `Box` - a smart pointer used to store data on the heap, // which also allows us to wrap a recursive type. // // The recursive type we're implementing in this exercise is the `cons list` - a data structure // frequently found in functional programming languages. Each item in a cons list contains two // elements: the value of the current item and the next item. The last item is a value called `Nil`. // // Step 1: use a `Box` in the enum definition to make the code compile // Step 2: create both empty and non-empty cons lists by replacing `todo!()` // // Note: the tests should not be changed // // Execute `rustlings hint box1` or use the `hint` watch subcommand for a hint. // I AM NOT DONE #[derive(PartialEq, Debug)] pub enum List { Cons(i32, List), Nil, } fn main() { println!("This is an empty cons list: {:?}", create_empty_list()); println!( "This is a non-empty cons list: {:?}", create_non_empty_list() ); // convert unit tests to main fn test_create_empty_list() { assert_eq!(List::Nil, create_empty_list()) } fn test_create_non_empty_list() { assert_ne!(create_empty_list(), create_non_empty_list()) } test_create_empty_list(); test_create_non_empty_list(); } pub fn create_empty_list() -> List { todo!() } pub fn create_non_empty_list() -> List { todo!() } #[cfg(test)] mod tests { use super::*; #[test] fn test_create_empty_list() { assert_eq!(List::Nil, create_empty_list()) } #[test] fn test_create_non_empty_list() { assert_ne!(create_empty_list(), create_non_empty_list()) } }
Hint
Step 1
The compiler’s message should help: since we cannot store the value of the actual type
when working with recursive types, we need to store a reference (pointer) to its value.
We should, therefore, place our List inside a Box.
More details in the book here: Heap: Using Box
to Point to Data on the Heap - The Rust Programming Language
Step 2
Creating an empty list should be fairly straightforward (hint: peek at the assertions). For a non-empty list keep in mind that we want to use our Cons “list builder”. Although the current list is one of integers (i32), feel free to change the definition and try other types!
solution: Box::new()
#[derive(PartialEq, Debug)] pub enum List { Cons(i32, Box<List>), Nil, } pub fn create_empty_list() -> List { List::Nil } pub fn create_non_empty_list() -> List { List::Cons(1, Box::new(List::Cons(2, Box::new(List::Cons(3, Box::new(List::Nil)))))) }
Cow: Clone-On-Write
This exercise explores the Cow, or Clone-On-Write type.
- Cow is a 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.
This exercise is meant to show you what to expect when passing data to Cow.
cow1
// cow1.rs // This exercise explores the Cow, or Clone-On-Write type. // Cow is a 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. // // This exercise is meant to show you what to expect when passing data to Cow. // Fix the unit tests by checking for Cow::Owned(_) and Cow::Borrowed(_) at the TODO markers. // I AM NOT DONE use std::borrow::Cow; fn abs_all<'a, 'b>(input: &'a mut Cow<'b, [i32]>) -> &'a mut Cow<'b, [i32]> { for i in 0..input.len() { let v = input[i]; if v < 0 { // Clones into a vector if not already owned. input.to_mut()[i] = -v; } } input } // convert unit tests to main fn main() { fn reference_mutation() -> Result<(), &'static str> { // Clone occurs because `input` needs to be mutated. let slice = [-1, 0, 1]; let mut input = Cow::from(&slice[..]); match abs_all(&mut input) { Cow::Owned(_) => Ok(()), _ => Err("Expected owned value"), } } fn reference_no_mutation() -> Result<(), &'static str> { // No clone occurs because `input` doesn't need to be mutated. let slice = [0, 1, 2]; let mut input = Cow::from(&slice[..]); match abs_all(&mut input) { // TODO } } fn owned_no_mutation() -> Result<(), &'static str> { // We can also pass `slice` without `&` so Cow owns it directly. // In this case no mutation occurs and thus also no clone, // but the result is still owned because it always was. let slice = vec![0, 1, 2]; let mut input = Cow::from(slice); match abs_all(&mut input) { // TODO } } fn owned_mutation() -> Result<(), &'static str> { // Of course this is also the case if a mutation does occur. // In this case the call to `to_mut()` returns a reference to // the same data as before. let slice = vec![-1, 0, 1]; let mut input = Cow::from(slice); match abs_all(&mut input) { // TODO } } reference_mutation(); reference_no_mutation(); owned_no_mutation(); owned_mutation(); } #[cfg(test)] mod tests { use super::*; #[test] fn reference_mutation() -> Result<(), &'static str> { // Clone occurs because `input` needs to be mutated. let slice = [-1, 0, 1]; let mut input = Cow::from(&slice[..]); match abs_all(&mut input) { Cow::Owned(_) => Ok(()), _ => Err("Expected owned value"), } } #[test] fn reference_no_mutation() -> Result<(), &'static str> { // No clone occurs because `input` doesn't need to be mutated. let slice = [0, 1, 2]; let mut input = Cow::from(&slice[..]); match abs_all(&mut input) { // TODO } } #[test] fn owned_no_mutation() -> Result<(), &'static str> { // We can also pass `slice` without `&` so Cow owns it directly. // In this case no mutation occurs and thus also no clone, // but the result is still owned because it always was. let slice = vec![0, 1, 2]; let mut input = Cow::from(slice); match abs_all(&mut input) { // TODO } } #[test] fn owned_mutation() -> Result<(), &'static str> { // Of course this is also the case if a mutation does occur. // In this case the call to `to_mut()` returns a reference to // the same data as before. let slice = vec![-1, 0, 1]; let mut input = Cow::from(slice); match abs_all(&mut input) { // TODO } } }
Hint
Since the vector is already owned, the Cow type doesn’t need to clone it.
Checkout Cow in std::borrow - Rust for documentation
on the Cow type.
solution: Cow::Borrowed()/Cow::Owned()
// cow1.rs // This exercise explores the Cow, or Clone-On-Write type. // Cow is a 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<'a, 'b>(input: &'a mut Cow<'b, [i32]>) -> &'a mut Cow<'b, [i32]> { for i in 0..input.len() { let v = input[i]; if v < 0 { // Clones into a vector if not already owned. input.to_mut()[i] = -v; } } input } fn main() { // No clone occurs because `input` doesn't need to be mutated. let slice = [0, 1, 2]; let mut input = Cow::from(&slice[..]); match abs_all(&mut input) { Cow::Borrowed(_) => println!("I borrowed the slice!"), _ => panic!("expected borrowed value"), } // Clone occurs because `input` needs to be mutated. let slice = [-1, 0, 1]; let mut input = Cow::from(&slice[..]); match abs_all(&mut input) { Cow::Owned(_) => println!("I modified the slice and now own it!"), _ => panic!("expected owned value"), } // No clone occurs because `input` is already owned. let slice = vec![-1, 0, 1]; let mut input = Cow::from(slice); match abs_all(&mut input) { Cow::Owned(_) => println!("I own this slice!"), _ => panic!("expected borrowed value"), } }
Rc
In this exercise, we want to express the concept of multiple owners via the Rc
type.
- This is a model of our solar system - there is a Sun type and multiple Planets.
- The Planets take ownership of the sun, indicating that they revolve around the sun.
Make this code compile by using the proper Rc primitives to express that the sun has multiple owners.
rc1
// rc1.rs // In this exercise, we want to express the concept of multiple owners via the Rc<T> type. // This is a model of our solar system - there is a Sun type and multiple Planets. // The Planets take ownership of the sun, indicating that they revolve around the sun. // Make this code compile by using the proper Rc primitives to express that the sun has multiple owners. // I AM NOT DONE use std::rc::Rc; #[derive(Debug)] struct Sun {} #[derive(Debug)] 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>), } impl Planet { fn details(&self) { println!("Hi from {:?}!", self) } } fn main() { 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 mercury.details(); let venus = Planet::Venus(Rc::clone(&sun)); println!("reference count = {}", Rc::strong_count(&sun)); // 3 references venus.details(); let earth = Planet::Earth(Rc::clone(&sun)); println!("reference count = {}", Rc::strong_count(&sun)); // 4 references earth.details(); let mars = Planet::Mars(Rc::clone(&sun)); println!("reference count = {}", Rc::strong_count(&sun)); // 5 references mars.details(); let jupiter = Planet::Jupiter(Rc::clone(&sun)); println!("reference count = {}", Rc::strong_count(&sun)); // 6 references jupiter.details(); // TODO let saturn = Planet::Saturn(Rc::new(Sun {})); println!("reference count = {}", Rc::strong_count(&sun)); // 7 references saturn.details(); // TODO let uranus = Planet::Uranus(Rc::new(Sun {})); println!("reference count = {}", Rc::strong_count(&sun)); // 8 references uranus.details(); // TODO let neptune = Planet::Neptune(Rc::new(Sun {})); println!("reference count = {}", Rc::strong_count(&sun)); // 9 references neptune.details(); assert_eq!(Rc::strong_count(&sun), 9); drop(neptune); println!("reference count = {}", Rc::strong_count(&sun)); // 8 references drop(uranus); println!("reference count = {}", Rc::strong_count(&sun)); // 7 references drop(saturn); println!("reference count = {}", Rc::strong_count(&sun)); // 6 references drop(jupiter); println!("reference count = {}", Rc::strong_count(&sun)); // 5 references drop(mars); println!("reference count = {}", Rc::strong_count(&sun)); // 4 references // TODO println!("reference count = {}", Rc::strong_count(&sun)); // 3 references // TODO println!("reference count = {}", Rc::strong_count(&sun)); // 2 references // TODO println!("reference count = {}", Rc::strong_count(&sun)); // 1 reference assert_eq!(Rc::strong_count(&sun), 1); }
Hint
This is a straightforward exercise to use the Rc
- Each Planet has ownership of the Sun, and uses Rc::clone() to increment the reference count of the Sun.
- After using drop() to move the Planets out of scope individually, the reference count goes down.
- In the end the sun only has one reference again, to itself.
- Unfortunately Pluto is no longer considered a planet :(
solution: Arc::new()
use std::rc::Rc; #[derive(Debug)] struct Sun {} #[derive(Debug)] 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>), } impl Planet { fn details(&self) { println!("Hi from {:?}!", self) } } fn main() { 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 mercury.details(); let venus = Planet::Venus(Rc::clone(&sun)); println!("reference count = {}", Rc::strong_count(&sun)); // 3 references venus.details(); let earth = Planet::Earth(Rc::clone(&sun)); println!("reference count = {}", Rc::strong_count(&sun)); // 4 references earth.details(); let mars = Planet::Mars(Rc::clone(&sun)); println!("reference count = {}", Rc::strong_count(&sun)); // 5 references mars.details(); let jupiter = Planet::Jupiter(Rc::clone(&sun)); println!("reference count = {}", Rc::strong_count(&sun)); // 6 references jupiter.details(); let saturn = Planet::Saturn(Rc::clone(&sun)); println!("reference count = {}", Rc::strong_count(&sun)); // 7 references saturn.details(); let uranus = Planet::Uranus(Rc::clone(&sun)); println!("reference count = {}", Rc::strong_count(&sun)); // 8 references uranus.details(); let neptune = Planet::Neptune(Rc::clone(&sun)); println!("reference count = {}", Rc::strong_count(&sun)); // 9 references neptune.details(); assert_eq!(Rc::strong_count(&sun), 9); drop(neptune); println!("reference count = {}", Rc::strong_count(&sun)); // 8 references drop(uranus); println!("reference count = {}", Rc::strong_count(&sun)); // 7 references drop(saturn); println!("reference count = {}", Rc::strong_count(&sun)); // 6 references drop(jupiter); println!("reference count = {}", Rc::strong_count(&sun)); // 5 references drop(mars); println!("reference count = {}", Rc::strong_count(&sun)); // 4 references drop(earth); println!("reference count = {}", Rc::strong_count(&sun)); // 3 references drop(venus); println!("reference count = {}", Rc::strong_count(&sun)); // 2 references drop(mercury); println!("reference count = {}", Rc::strong_count(&sun)); // 1 reference assert_eq!(Rc::strong_count(&sun), 1); }
Memory Leak
memory_leak1
use std::rc::Rc; struct Node { value: i32, parent: Option<Rc<Node>>, children: Vec<Rc<Node>>, } impl Node { fn new(value: i32) -> Rc<Self> { Rc::new(Self { value, parent: None, children: Vec::new(), }) } fn add_child(&mut self, child: Rc<Self>) { self.children.push(child); child.parent = Some(Rc::clone(&self)); } } fn main() { let root = Node::new(0); let child1 = Node::new(1); let child2 = Node::new(2); child1.add_child(Rc::clone(&child2)); child2.add_child(Rc::clone(&child1)); root.add_child(child1); root.add_child(child2); println!("{}", root.value); }
Type conversions
Rust offers a multitude of ways to convert a value of a given type into another type.
- as: The simplest form of type conversion is a type cast expression.
It is denoted with the binary operator
as.
For instance, println!("{}", 1 + 1.0); would not compile, since 1 is an integer while 1.0 is a float. However, println!("{}", 1 as f32 + 1.0) should compile. The
exercise using_as tries to cover this.
- from/into/AsRef: Rust also offers traits that
facilitate type conversionsupon implementation.
These traits can be found under the
convertmodule.
The traits are the following:
FromandIntocovered infrom_intoTryFromandTryIntocovered intry_from_intoAsRefandAsMutcovered inas_ref_mut
- from_str: Furthermore, the
std::strmodule offers a trait calledFromStrwhich helps with converting strings into target types via theparsemethod on strings.
If properly implemented for a given type
Person, thenlet p: Person = "Mark,20".parse().unwrap()should both compile and run without panicking.
These should be the main ways within the standard library to convert data into your desired types.
Further information
These are not directly covered in the book, but the standard library has a great documentation for it.
Rustlings
using as: type casting
- Type casting in Rust is done via the usage of the
asoperator. - Please note that the
asoperator is not only used when type casting. - It also helps with renaming imports.
The goal is to make sure that the division does not fail to compile and returns the proper type.
using_as
// Type casting in Rust is done via the usage of the `as` operator. // Please note that the `as` operator is not only used when type casting. // It also helps with renaming imports. // // The goal is to make sure that the division does not fail to compile // and returns the proper type. // Execute `rustlings hint using_as` or use the `hint` watch subcommand for a hint. // I AM NOT DONE fn average(values: &[f64]) -> f64 { let total = values.iter().sum::<f64>(); total / values.len() } fn main() { let values = [3.5, 0.3, 13.0, 11.7]; println!("{}", average(&values)); // convert unit tests to here to run in the rust playground fn returns_proper_type_and_value() { assert_eq!(average(&[3.5, 0.3, 13.0, 11.7]), 7.125); } returns_proper_type_and_value(); } #[cfg(test)] mod tests { use super::*; #[test] fn returns_proper_type_and_value() { assert_eq!(average(&[3.5, 0.3, 13.0, 11.7]), 7.125); } }
Hint
Use the as operator to cast one of the operands in the last line of the
average function into the expected return type.
solution
fn average(values: &[f64]) -> f64 { let total = values.iter().sum::<f64>(); total / values.len() as f64 }
from_into: value-to-value
- The From trait is used for
value-to-valueconversions.
If From is implemented correctly for a type, the Into trait should work conversely.
- You can read more about it at: From in std::convert - Rust
Your task is:
- to complete this implementation in order for the line
let p = Person::from("Mark,20")to compile - Please note that you’ll need to parse the age component into a
usizewith something like"4".parse::<usize>(). - The outcome of this needs to be handled appropriately.
Steps:
- If the length of the provided string is 0, then return the default of Person
- Split the given string on the commas present in it
- Extract the first element from the split operation and use it as the name
- If the name is empty, then return the default of Person
- Extract the other element from the split operation and parse it into a
usizeas the age - If while parsing the age, something goes wrong, then return the default of Person
- Otherwise, then return an instantiated Person object with the results
🌟from_into: We implement the Default trait to use it as a fallback when the provided string is not convertible into a Person object
// The From trait is used for value-to-value conversions. // If From is implemented correctly for a type, the Into trait should work conversely. // You can read more about it at https://doc.rust-lang.org/std/convert/trait.From.html // Execute `rustlings hint from_into` or use the `hint` watch subcommand for a hint. #[derive(Debug)] struct Person { name: String, age: usize, } // We implement the Default trait to use it as a fallback // when the provided string is not convertible into a Person object impl Default for Person { fn default() -> Person { Person { name: String::from("John"), age: 30, } } } // Your task is to complete this implementation // in order for the line `let p = Person::from("Mark,20")` to compile // Please note that you'll need to parse the age component into a `usize` // with something like `"4".parse::<usize>()`. The outcome of this needs to // be handled appropriately. // // Steps: // 1. If the length of the provided string is 0, then return the default of Person // 2. Split the given string on the commas present in it // 3. Extract the first element from the split operation and use it as the name // 4. If the name is empty, then return the default of Person // 5. Extract the other element from the split operation and parse it into a `usize` as the age // If while parsing the age, something goes wrong, then return the default of Person // Otherwise, then return an instantiated Person object with the results // I AM NOT DONE impl From<&str> for Person { fn from(s: &str) -> Person {} } fn main() { // Use the `from` function let p1 = Person::from("Mark,20"); // Since From is implemented for Person, we should be able to use Into let p2: Person = "Gerald,70".into(); println!("{:?}", p1); println!("{:?}", p2); // convert unit tests to here to run in the rust playground // Test that the default person is 30 year old John let dp = Person::default(); assert_eq!(dp.name, "John"); assert_eq!(dp.age, 30); // Test that John is returned when bad string is provided let p = Person::from(""); assert_eq!(p.name, "John"); assert_eq!(p.age, 30); // Test that "Mark,20" works let p = Person::from("Mark,20"); assert_eq!(p.name, "Mark"); assert_eq!(p.age, 20); // Test that "Mark,twenty" will return the default person due to an error in parsing age let p = Person::from("Mark,twenty"); assert_eq!(p.name, "John"); assert_eq!(p.age, 30); let p: Person = Person::from("Mark"); assert_eq!(p.name, "John"); assert_eq!(p.age, 30); let p: Person = Person::from("Mark,"); assert_eq!(p.name, "John"); assert_eq!(p.age, 30); let p: Person = Person::from(",1"); assert_eq!(p.name, "John"); assert_eq!(p.age, 30); let p: Person = Person::from(","); assert_eq!(p.name, "John"); assert_eq!(p.age, 30); let p: Person = Person::from(",one"); assert_eq!(p.name, "John"); assert_eq!(p.age, 30); let p: Person = Person::from("Mike,32,"); assert_eq!(p.name, "John"); assert_eq!(p.age, 30); let p: Person = Person::from("Mike,32,man"); assert_eq!(p.name, "John"); assert_eq!(p.age, 30); } #[cfg(test)] mod tests { use super::*; #[test] fn test_default() { // Test that the default person is 30 year old John let dp = Person::default(); assert_eq!(dp.name, "John"); assert_eq!(dp.age, 30); } #[test] fn test_bad_convert() { // Test that John is returned when bad string is provided let p = Person::from(""); assert_eq!(p.name, "John"); assert_eq!(p.age, 30); } #[test] fn test_good_convert() { // Test that "Mark,20" works let p = Person::from("Mark,20"); assert_eq!(p.name, "Mark"); assert_eq!(p.age, 20); } #[test] fn test_bad_age() { // Test that "Mark,twenty" will return the default person due to an error in parsing age let p = Person::from("Mark,twenty"); assert_eq!(p.name, "John"); assert_eq!(p.age, 30); } #[test] fn test_missing_comma_and_age() { let p: Person = Person::from("Mark"); assert_eq!(p.name, "John"); assert_eq!(p.age, 30); } #[test] fn test_missing_age() { let p: Person = Person::from("Mark,"); assert_eq!(p.name, "John"); assert_eq!(p.age, 30); } #[test] fn test_missing_name() { let p: Person = Person::from(",1"); assert_eq!(p.name, "John"); assert_eq!(p.age, 30); } #[test] fn test_missing_name_and_age() { let p: Person = Person::from(","); assert_eq!(p.name, "John"); assert_eq!(p.age, 30); } #[test] fn test_missing_name_and_invalid_age() { let p: Person = Person::from(",one"); assert_eq!(p.name, "John"); assert_eq!(p.age, 30); } #[test] fn test_trailing_comma() { let p: Person = Person::from("Mike,32,"); assert_eq!(p.name, "John"); assert_eq!(p.age, 30); } #[test] fn test_trailing_comma_and_some_string() { let p: Person = Person::from("Mike,32,man"); assert_eq!(p.name, "John"); assert_eq!(p.age, 30); } }
solution: match -> split_once -> parse:: -> into() or default()
impl From<&str> for Person { fn from(s: &str) -> Person { // 2. Split the given string on the commas present in it match s.split_once(',') { Some((first, second)) => { if first.is_empty() { // 4. If the name is empty, then return the default of Person Person::default() } else if let Ok(a) = second.parse::<usize>() { // 5. Extract the other element from the split operation and parse it into a `usize` as the age Person { name: first.into(), // 3. Extract the first element from the split operation and use it as the name age: a, // 6. If while parsing the age, something goes wrong, then return the default of Person } } else { Person::default() // 7. Otherwise, then return an instantiated Person object with the results } }, _ => Person::default(), // 1. If the length of the provided string is 0, then return the default of Person } } }
try_from_into: try value-to-value
- TryFrom is a simple and safe type conversion that may fail in
a controlled wayunder some circumstances. - Basically, this is the same as From.
The main difference is that this should return a Result type instead of the target type itself.
- You can read more about it at: TryFrom in std::convert - Rust
Your task is to complete this implementation:
- return an Ok result of inner type Color.
- You need to create an implementation for a
tupleof three integers, anarrayof three integers, and asliceof integers. - Note that the implementation for tuple and array will be checked at compile time, but the slice implementation needs to check the slice length!
- Also note that correct RGB color values must be integers in the 0..=255 range.
⚡️try_from_into: with challenge
// try_from_into.rs // TryFrom is a simple and safe type conversion that may fail in a controlled way under some circumstances. // Basically, this is the same as From. The main difference is that this should return a Result type // instead of the target type itself. // You can read more about it at https://doc.rust-lang.org/std/convert/trait.TryFrom.html // Execute `rustlings hint try_from_into` or use the `hint` watch subcommand for a hint. use std::convert::{TryFrom, TryInto}; #[derive(Debug, PartialEq)] struct Color { red: u8, green: u8, blue: u8, } // We will use this error type for these `TryFrom` conversions. #[derive(Debug, PartialEq)] enum IntoColorError { // Incorrect length of slice BadLen, // Integer conversion error IntConversion, } // I AM NOT DONE // Your task is to complete this implementation // and return an Ok result of inner type Color. // You need to create an implementation for a tuple of three integers, // an array of three integers, and a slice of integers. // // Note that the implementation for tuple and array will be checked at compile time, // but the slice implementation needs to check the slice length! // Also note that correct RGB color values must be integers in the 0..=255 range. // Tuple implementation impl TryFrom<(i16, i16, i16)> for Color { type Error = IntoColorError; fn try_from(tuple: (i16, i16, i16)) -> Result<Self, Self::Error> { } } // Array implementation impl TryFrom<[i16; 3]> for Color { type Error = IntoColorError; fn try_from(arr: [i16; 3]) -> Result<Self, Self::Error> { } } // Slice implementation impl TryFrom<&[i16]> for Color { type Error = IntoColorError; fn try_from(slice: &[i16]) -> Result<Self, Self::Error> { } } fn main() { // Use the `try_from` function let c1 = Color::try_from((183, 65, 14)); println!("{:?}", c1); // Since TryFrom is implemented for Color, we should be able to use TryInto let c2: Result<Color, _> = [183, 65, 14].try_into(); println!("{:?}", c2); let v = vec![183, 65, 14]; // With slice we should use `try_from` function let c3 = Color::try_from(&v[..]); println!("{:?}", c3); // or take slice within round brackets and use TryInto let c4: Result<Color, _> = (&v[..]).try_into(); println!("{:?}", c4); // convert unit tests to here to run in the rust playground fn test_tuple_out_of_range_positive() { assert_eq!( Color::try_from((256, 1000, 10000)), Err(IntoColorError::IntConversion) ); } fn test_tuple_out_of_range_negative() { assert_eq!( Color::try_from((-1, -10, -256)), Err(IntoColorError::IntConversion) ); } fn test_tuple_sum() { assert_eq!( Color::try_from((-1, 255, 255)), Err(IntoColorError::IntConversion) ); } fn test_tuple_correct() { let c: Result<Color, _> = (183, 65, 14).try_into(); assert!(c.is_ok()); assert_eq!( c.unwrap(), Color { red: 183, green: 65, blue: 14 } ); } fn test_array_out_of_range_positive() { let c: Result<Color, _> = [1000, 10000, 256].try_into(); assert_eq!(c, Err(IntoColorError::IntConversion)); } fn test_array_out_of_range_negative() { let c: Result<Color, _> = [-10, -256, -1].try_into(); assert_eq!(c, Err(IntoColorError::IntConversion)); } fn test_array_sum() { let c: Result<Color, _> = [-1, 255, 255].try_into(); assert_eq!(c, Err(IntoColorError::IntConversion)); } fn test_array_correct() { let c: Result<Color, _> = [183, 65, 14].try_into(); assert!(c.is_ok()); assert_eq!( c.unwrap(), Color { red: 183, green: 65, blue: 14 } ); } fn test_slice_out_of_range_positive() { let arr = [10000, 256, 1000]; assert_eq!( Color::try_from(&arr[..]), Err(IntoColorError::IntConversion) ); } fn test_slice_out_of_range_negative() { let arr = [-256, -1, -10]; assert_eq!( Color::try_from(&arr[..]), Err(IntoColorError::IntConversion) ); } fn test_slice_sum() { let arr = [-1, 255, 255]; assert_eq!( Color::try_from(&arr[..]), Err(IntoColorError::IntConversion) ); } fn test_slice_correct() { let v = vec![183, 65, 14]; let c: Result<Color, _> = Color::try_from(&v[..]); assert!(c.is_ok()); assert_eq!( c.unwrap(), Color { red: 183, green: 65, blue: 14 } ); } fn test_slice_excess_length() { let v = vec![0, 0, 0, 0]; assert_eq!(Color::try_from(&v[..]), Err(IntoColorError::BadLen)); } fn test_slice_insufficient_length() { let v = vec![0, 0]; assert_eq!(Color::try_from(&v[..]), Err(IntoColorError::BadLen)); } test_tuple_out_of_range_positive(); test_tuple_out_of_range_negative(); test_tuple_sum(); test_tuple_correct(); test_array_out_of_range_positive(); test_array_out_of_range_negative(); test_array_sum(); test_array_correct(); test_slice_out_of_range_positive(); test_slice_out_of_range_negative(); test_slice_sum(); test_slice_correct(); test_slice_excess_length(); test_slice_insufficient_length(); } #[cfg(test)] mod tests { use super::*; #[test] fn test_tuple_out_of_range_positive() { assert_eq!( Color::try_from((256, 1000, 10000)), Err(IntoColorError::IntConversion) ); } #[test] fn test_tuple_out_of_range_negative() { assert_eq!( Color::try_from((-1, -10, -256)), Err(IntoColorError::IntConversion) ); } #[test] fn test_tuple_sum() { assert_eq!( Color::try_from((-1, 255, 255)), Err(IntoColorError::IntConversion) ); } #[test] fn test_tuple_correct() { let c: Result<Color, _> = (183, 65, 14).try_into(); assert!(c.is_ok()); assert_eq!( c.unwrap(), Color { red: 183, green: 65, blue: 14 } ); } #[test] fn test_array_out_of_range_positive() { let c: Result<Color, _> = [1000, 10000, 256].try_into(); assert_eq!(c, Err(IntoColorError::IntConversion)); } #[test] fn test_array_out_of_range_negative() { let c: Result<Color, _> = [-10, -256, -1].try_into(); assert_eq!(c, Err(IntoColorError::IntConversion)); } #[test] fn test_array_sum() { let c: Result<Color, _> = [-1, 255, 255].try_into(); assert_eq!(c, Err(IntoColorError::IntConversion)); } #[test] fn test_array_correct() { let c: Result<Color, _> = [183, 65, 14].try_into(); assert!(c.is_ok()); assert_eq!( c.unwrap(), Color { red: 183, green: 65, blue: 14 } ); } #[test] fn test_slice_out_of_range_positive() { let arr = [10000, 256, 1000]; assert_eq!( Color::try_from(&arr[..]), Err(IntoColorError::IntConversion) ); } #[test] fn test_slice_out_of_range_negative() { let arr = [-256, -1, -10]; assert_eq!( Color::try_from(&arr[..]), Err(IntoColorError::IntConversion) ); } #[test] fn test_slice_sum() { let arr = [-1, 255, 255]; assert_eq!( Color::try_from(&arr[..]), Err(IntoColorError::IntConversion) ); } #[test] fn test_slice_correct() { let v = vec![183, 65, 14]; let c: Result<Color, _> = Color::try_from(&v[..]); assert!(c.is_ok()); assert_eq!( c.unwrap(), Color { red: 183, green: 65, blue: 14 } ); } #[test] fn test_slice_excess_length() { let v = vec![0, 0, 0, 0]; assert_eq!(Color::try_from(&v[..]), Err(IntoColorError::BadLen)); } #[test] fn test_slice_insufficient_length() { let v = vec![0, 0]; assert_eq!(Color::try_from(&v[..]), Err(IntoColorError::BadLen)); } }
Hint
- Follow the steps provided right before the
TryFromimplementation. You can also use the example at: TryFrom in std::convert - Rust
Is there an implementation of TryFrom in the standard library that
can both do the required integer conversion and check the range of the input?
-
Another hint: Look at the test cases to see which error variants to return.
-
Yet another hint: You can use the
map_errorormethods ofResultto convert errors. -
Yet another hint: If you would like to propagate errors by using the
?operator in your solution, you might want to look at: Other uses of ? - The Rust Programming Language
solution: essentially return to run the implementation of tuple
// Tuple implementation impl TryFrom<(i16, i16, i16)> for Color { type Error = IntoColorError; fn try_from(tuple: (i16, i16, i16)) -> Result<Self, Self::Error> { for i in [tuple.0, tuple.1, tuple.2] { if i < 0 || i > 255 { return Err(IntoColorError::IntConversion) } } // return an Ok result of inner type Color. Ok(Color { red: tuple.0 as u8, green: tuple.1 as u8, blue: tuple.2 as u8 }) } } // Array implementation impl TryFrom<[i16; 3]> for Color { type Error = IntoColorError; fn try_from(arr: [i16; 3]) -> Result<Self, Self::Error> { // return to run the implementation of tuple above Self::try_from((arr[0], arr[1], arr[2])) } } // Slice implementation impl TryFrom<&[i16]> for Color { type Error = IntoColorError; fn try_from(slice: &[i16]) -> Result<Self, Self::Error> { if slice.len() != 3 { Err(IntoColorError::BadLen) } else { // return to run the implementation of tuple above Self::try_from((slice[0], slice[1], slice[2])) } } }
as_ref_mut: reference-to-reference
AsRefandAsMutallow for cheap reference-to-reference conversions.
Read more about them at AsRef in std::convert - Rust and AsMut in std::convert - Rust, respectively.
as_ref_mut: Three ways to add appropriate trait bound
// AsRef and AsMut allow for cheap reference-to-reference conversions. // Read more about them at https://doc.rust-lang.org/std/convert/trait.AsRef.html // and https://doc.rust-lang.org/std/convert/trait.AsMut.html, respectively. // Execute `rustlings hint as_ref_mut` or use the `hint` watch subcommand for a hint. // I AM NOT DONE // Obtain the number of bytes (not characters) in the given argument. // TODO: Add the AsRef trait appropriately as a trait bound. fn byte_counter<T>(arg: T) -> usize { arg.as_ref().as_bytes().len() } // Obtain the number of characters (not bytes) in the given argument. // TODO: Add the AsRef trait appropriately as a trait bound. fn char_counter<T>(arg: T) -> usize { arg.as_ref().chars().count() } // Squares a number using as_mut(). // TODO: Add the appropriate trait bound. fn num_sq<T>(arg: &mut T) { // TODO: Implement the function body. ? ? ? } #[cfg(test)] mod tests { use super::*; #[test] fn different_counts() { let s = "Café au lait"; assert_ne!(char_counter(s), byte_counter(s)); } #[test] fn same_counts() { let s = "Cafe au lait"; assert_eq!(char_counter(s), byte_counter(s)); } #[test] fn different_counts_using_string() { let s = String::from("Café au lait"); assert_ne!(char_counter(s.clone()), byte_counter(s)); } #[test] fn same_counts_using_string() { let s = String::from("Cafe au lait"); assert_eq!(char_counter(s.clone()), byte_counter(s)); } #[test] fn mult_box() { let mut num: Box<u32> = Box::new(3); num_sq(&mut num); assert_eq!(*num, 9); } } // convert the unit test to main fn main() { // different_counts() let s = "Café au lait"; assert_ne!(char_counter(s), byte_counter(s)); // same_counts let s = "Cafe au lait"; assert_eq!(char_counter(s), byte_counter(s)); // different_counts_using_string let s = String::from("Café au lait"); assert_ne!(char_counter(s.clone()), byte_counter(s)); // same_counts_using_string let s = String::from("Cafe au lait"); assert_eq!(char_counter(s.clone()), byte_counter(s)); // mult_box let mut num: Box<u32> = Box::new(3); num_sq(&mut num); assert_eq!(*num, 9); }
compare to traits4
solution1: generic trait bounds
// Obtain the number of bytes (not characters) in the given argument // Add the AsRef trait appropriately as a trait bound fn byte_counter<T: AsRef<str>>(arg: T) -> usize { arg.as_ref().as_bytes().len() } // Obtain the number of characters (not bytes) in the given argument // Add the AsRef trait appropriately as a trait bound fn char_counter<T: AsRef<str>>(arg: T) -> usize { arg.as_ref().chars().count() } // Squares a number using AsMut. Add the trait bound as is appropriate and // implement the function body. fn num_sq<T: AsMut<u32>>(arg: &mut T) { *arg.as_mut() *= *arg.as_mut() }
solution2: impl trait bounds
// Obtain the number of bytes (not characters) in the given argument // Add the AsRef trait appropriately as a trait bound fn byte_counter(arg: impl AsRef<str>) -> usize { arg.as_ref().as_bytes().len() } // Obtain the number of characters (not bytes) in the given argument // Add the AsRef trait appropriately as a trait bound fn char_counterd(arg: impl AsRef<str>) -> usize { arg.as_ref().chars().count() } // Squares a number using AsMut. Add the trait bound as is appropriate and // implement the function body. fn num_sq(arg: &mut impl AsRef<str>) { *arg.as_mut() *= *arg.as_mut() }
solution3: where clause
// Obtain the number of bytes (not characters) in the given argument // Add the AsRef trait appropriately as a trait bound fn byte_counter<T>(arg: T) -> usize where T: AsRef<str> { arg.as_ref().as_bytes().len() }
from_str: convert str to target type
- This is similar to from_into.rs, but this time we’ll implement
FromStrand return errors instead of falling back to a default value. - Additionally, upon implementing FromStr, you can use the
parsemethod on strings to generate an object of the implementor type. - You can read more about it at FromStr in std::str - Rust
Steps:
- If the length of the provided string is 0, an error should be returned
- Split the given string on the commas present in it
- Only 2 elements should be returned from the split, otherwise return an error
- Extract the first element from the split operation and use it as the name
- Extract the other element from the split operation and parse it into a
usizeas the age with something like"4".parse::<usize>() - If while extracting the name and the age something goes wrong, an error should be returned
- If everything goes well, then return a Result of a Person object
As an aside: Box<dyn Error> implements From<&'_ str>. This means that if you want to return a
string error message, you can do so via just using return Err("my error message".into()).
from_str
// from_str.rs // This is similar to from_into.rs, but this time we'll implement `FromStr` // and return errors instead of falling back to a default value. // Additionally, upon implementing FromStr, you can use the `parse` method // on strings to generate an object of the implementor type. // You can read more about it at https://doc.rust-lang.org/std/str/trait.FromStr.html // Execute `rustlings hint from_str` or use the `hint` watch subcommand for a hint. use std::num::ParseIntError; use std::str::FromStr; #[derive(Debug, PartialEq)] struct Person { name: String, age: usize, } // We will use this error type for the `FromStr` implementation. #[derive(Debug, PartialEq)] enum ParsePersonError { // Empty input string Empty, // Incorrect number of fields BadLen, // Empty name field NoName, // Wrapped error from parse::<usize>() ParseInt(ParseIntError), } // I AM NOT DONE // Steps: // 1. If the length of the provided string is 0, an error should be returned // 2. Split the given string on the commas present in it // 3. Only 2 elements should be returned from the split, otherwise return an error // 4. Extract the first element from the split operation and use it as the name // 5. Extract the other element from the split operation and parse it into a `usize` as the age // with something like `"4".parse::<usize>()` // 6. If while extracting the name and the age something goes wrong, an error should be returned // If everything goes well, then return a Result of a Person object // // As an aside: `Box<dyn Error>` implements `From<&'_ str>`. This means that if you want to return a // string error message, you can do so via just using return `Err("my error message".into())`. impl FromStr for Person { type Err = ParsePersonError; fn from_str(s: &str) -> Result<Person, Self::Err> {} } fn main() { let p = "Mark,20".parse::<Person>().unwrap(); println!("{:?}", p); // convert unit tests to here to run in the rust playground assert_eq!("".parse::<Person>(), Err(ParsePersonError::Empty)); let p = "John,32".parse::<Person>(); assert!(p.is_ok()); let p = p.unwrap(); assert_eq!(p.name, "John"); assert_eq!(p.age, 32); assert!(matches!( "John,".parse::<Person>(), Err(ParsePersonError::ParseInt(_)) )); assert!(matches!( "John,twenty".parse::<Person>(), Err(ParsePersonError::ParseInt(_)) )); assert_eq!("John".parse::<Person>(), Err(ParsePersonError::BadLen)); assert_eq!(",1".parse::<Person>(), Err(ParsePersonError::NoName)); assert!(matches!( ",".parse::<Person>(), Err(ParsePersonError::NoName | ParsePersonError::ParseInt(_)) )); assert!(matches!( ",one".parse::<Person>(), Err(ParsePersonError::NoName | ParsePersonError::ParseInt(_)) )); assert_eq!("John,32,".parse::<Person>(), Err(ParsePersonError::BadLen)); assert_eq!( "John,32,man".parse::<Person>(), Err(ParsePersonError::BadLen) ); } #[cfg(test)] mod tests { use super::*; #[test] fn empty_input() { assert_eq!("".parse::<Person>(), Err(ParsePersonError::Empty)); } #[test] fn good_input() { let p = "John,32".parse::<Person>(); assert!(p.is_ok()); let p = p.unwrap(); assert_eq!(p.name, "John"); assert_eq!(p.age, 32); } #[test] fn missing_age() { assert!(matches!( "John,".parse::<Person>(), Err(ParsePersonError::ParseInt(_)) )); } #[test] fn invalid_age() { assert!(matches!( "John,twenty".parse::<Person>(), Err(ParsePersonError::ParseInt(_)) )); } #[test] fn missing_comma_and_age() { assert_eq!("John".parse::<Person>(), Err(ParsePersonError::BadLen)); } #[test] fn missing_name() { assert_eq!(",1".parse::<Person>(), Err(ParsePersonError::NoName)); } #[test] fn missing_name_and_age() { assert!(matches!( ",".parse::<Person>(), Err(ParsePersonError::NoName | ParsePersonError::ParseInt(_)) )); } #[test] fn missing_name_and_invalid_age() { assert!(matches!( ",one".parse::<Person>(), Err(ParsePersonError::NoName | ParsePersonError::ParseInt(_)) )); } #[test] fn trailing_comma() { assert_eq!("John,32,".parse::<Person>(), Err(ParsePersonError::BadLen)); } #[test] fn trailing_comma_and_some_string() { assert_eq!( "John,32,man".parse::<Person>(), Err(ParsePersonError::BadLen) ); } }
Hint
- The implementation of FromStr should return an Ok with a Person object, or an Err with an error if the string is not valid.
This is almost like the from_into exercise, but returning errors instead
of falling back to a default value.
Look at the test cases to see which error variants to return.
-
Another hint: You can use the
map_errmethod ofResultwith a function or a closure to wrap the error fromparse::<usize>. -
Yet another hint: If you would like to propagate errors by using the
?operator in your solution, you might want to look at Other uses of ? - The Rust Programming Language
solution1
// Using ParsePersonError::{Empty, BadLen, NoName, ParseInt} above impl FromStr for Person { type Err = ParsePersonError; fn from_str(s: &str) -> Result<Person, Self::Err> { if s.is_empty() { Err(Empty) // i.e., Err(ParsePersonError::Empty) } else { let p: Vec<&str> = s.split(',').collect(); if p.len() != 2 { Err(BadLen) } else if p[0].len() == 0 { Err(NoName) } else { match p[1].parse::<usize>() { Ok(a) => Ok(Person { name: p[0].to_string(), age: a }), Err(a) => Err(ParseInt(a)), } } } } }
solution2: Err::Empty -> Err is a variant, not a module
impl FromStr for Person { type Err = ParsePersonError; fn from_str(s: &str) -> Result<Person, Self::Err> { if s.is_empty() { Err::Empty // i.e., Err(ParsePersonError::Empty) } else { let p: Vec<&str> = s.split(',').collect(); if p.len() != 2 { Err::BadLen } else if p[0].len() == 0 { Err::NoName } else { match p[1].parse::<usize>() { Ok(a) => Ok(Person { name: p[0].to_string(), age: a }), Err(a) => Err::ParseInt(a), } } } } }
solution3: Err.Empty()
impl FromStr for Person { type Err = ParsePersonError; fn from_str(s: &str) -> Result<Person, Self::Err> { if s.is_empty() { Err.Empty // i.e., Err(ParsePersonError::Empty) } else { let p: Vec<&str> = s.split(',').collect(); if p.len() != 2 { Err.BadLen } else if p[0].len() == 0 { Err.NoName } else { match p[1].parse::<usize>() { Ok(a) => Ok(Person { name: p[0].to_string(), age: a }), Err(a) => Err.ParseInt(a), } } } } }
solution4: ParsePersonError::{ErrorType}
// Using ParsePersonError::{Empty, BadLen, NoName, ParseInt} above impl FromStr for Person { type Err = ParsePersonError; fn from_str(s: &str) -> Result<Person, Self::Err> { if s.is_empty() { // 1. If the length of the provided string is 0, an error should be returned Err(ParsePersonError::Empty) // i.e., Err(ParsePersonError::Empty) } else { let p: Vec<&str> = s.split(',').collect(); // 2. Split the given string on the commas present in it if p.len() != 2 { // 3. Only 2 elements should be returned from the split, otherwise return an error Err(ParsePersonError::BadLen) } else if p[0].len() == 0 { // 4. Extract the first element from the split operation and use it as the name Err(ParsePersonError::NoName) } else { match p[1].parse::<usize>() { // 5. Extract the other element from the split operation and parse it into a `usize` as the age with something like `"4".parse::<usize>()` Ok(a) => Ok(Person { name: p[0].to_string(), age: a }), // 7. If everything goes well, then return a Result of a Person object Err(a) => Err(ParsePersonError::ParseInt(a)), // 6. If while extracting the name and the age something goes wrong, an error should be returned } } } } }
Error handling
- Most errors aren’t serious enough to require the program to stop entirely.
- Sometimes, when a function fails, it’s for a reason that you can easily interpret and respond to.
For example, if you try to open a file and that operation fails because the file doesn’t exist, you might want to create the file instead of terminating the process.
Is Option part of error handling
In Rust, an “option” is not technically part of error handling, but it is often used in combination with error handling to represent the possibility of a value being absent or “None.”
- An “option” is a type that represents either Some value or None.
- When a function may not return a value, it can return an Option
where T is the type of the value that might be returned. - If the function succeeds and returns a value, it returns Some(value); otherwise, it returns None.
Error handling in Rust typically uses the Result<T, E> type
- where T is the type of the value that is returned if the operation succeeds
- and E is the type of the error that may occur.
- When an error occurs, a value of type E is returned, and when the operation succeeds, a value of type T is returned.
So while Option and Result are different types in Rust, they are often used in combination to handle situations where a value may or may not be present or when an operation may or may not succeed.
Further information
- Rust 使用 Result 的错误处理方式与 Golang 使用 error 的方式有什么本质区别? - 知乎: dt link
- 错误处理内容和主流方法 - Anatomy In First Rust Programming Class 🦀
- ⭐️Recoverable Errors with Result - The Rust Programming Language
- ✨Generic Data Types - The Rust Programming Language
- ⭐️Error handling: Panic、Option and Result - The Rust Programming Language
rustlings-solutions-5/error_handling at main · gaveen/rustlings-solutions-5
Rustlings
errors1: change Option to Result<T, E>
// errors1.rs // This function refuses to generate text to be printed on a nametag if // you pass it an empty string. It'd be nicer if it explained what the problem // was, instead of just sometimes returning `None`. Thankfully, Rust has a similar // construct to `Option` that can be used to express error conditions. Let's use it! // Execute `rustlings hint errors1` or use the `hint` watch subcommand for a hint. // I AM NOT DONE pub 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)) } } // convert unit tests to main fn main() { fn generates_nametag_text_for_a_nonempty_name() { assert_eq!( generate_nametag_text("Beyoncé".into()), Ok("Hi! My name is Beyoncé".into()) ); } 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()) ); } generates_nametag_text_for_a_nonempty_name(); explains_why_generating_nametag_text_fails(); } #[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()) ); } }
Hint
Ok and Err are one of the variants of Result, so what the tests are saying
is that generate_nametag_text should return a Result instead of an
Option.
To make this change, you’ll need to:
- update the return type in the function signature to be a Result<String, String> that
could be the variants
Ok(String)andErr(String) - change the body of the function to return
Ok(stuff)where it currently returnsSome(stuff) - change the body of the function to return
Err(error message)where it currently returnsNone
solution: Result<String, String>
pub fn generate_nametag_text(name: String) -> Result<String, String> { if name.is_empty() { // Empty names aren't allowed. Err(format!("`name` was empty; it must be nonempty.")) } else { Ok(format!("Hi! My name is {}", name)) } }
- 这里其实单元测试代码已经指出要报错的内容
- Result<T, E>中,E其实就是各种Err
- Say we’re writing a game where you can buy items with tokens. All items cost 5 tokens, and whenever you purchase items there is a processing fee of 1 token.
- A player of the game will type in how many items they want to buy,
and the
total_costfunction will calculate the total number of tokens. - Since the player typed in the quantity, though, we get it as a string– and they might have typed anything, not just numbers!
Right now, this function isn’t handling the error case at all (and isn’t handling the success case properly either). What we want to do is:
- if we call the
parsefunction on a string that is not a number, that function will return aParseIntError, - and in that case, we want to immediately return that error from our function and not try to multiply and add.
There are at least two ways to implement this that are both correct– but one is a lot shorter!
⭐️errors2: unwrap_err()
// errors2.rs // Say we're writing a game where you can buy items with tokens. All items cost // 5 tokens, and whenever you purchase items there is a processing fee of 1 // token. A player of the game will type in how many items they want to buy, // and the `total_cost` function will calculate the total number of tokens. // Since the player typed in the quantity, though, we get it as a string-- and // they might have typed anything, not just numbers! // Right now, this function isn't handling the error case at all (and isn't // handling the success case properly either). What we want to do is: // if we call the `parse` function on a string that is not a number, that // function will return a `ParseIntError`, and in that case, we want to // immediately return that error from our function and not try to multiply // and add. // There are at least two ways to implement this that are both correct-- but // one is a lot shorter! // Execute `rustlings hint errors2` or use the `hint` watch subcommand for a hint. // I AM NOT DONE 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) } // convert unit tests to main fn main() { fn item_quantity_is_a_valid_number() { assert_eq!(total_cost("34"), Ok(171)); } fn item_quantity_is_an_invalid_number() { assert_eq!( total_cost("beep boop").unwrap_err().to_string(), "invalid digit found in string" ); } item_quantity_is_a_valid_number(); item_quantity_is_an_invalid_number() } #[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(), "invalid digit found in string" ); } }
Hint
- One way to handle this is using a
matchstatement onitem_quantity.parse::<i32>()where the cases areOk(something)andErr(something). This pattern is very common in Rust, though, so there’s a?operator that does pretty much what you would make that match statement do for you! - Take a look at this section of the
match expressionpart in Error Handling chapter:
solution1: match err to panic! return
pub fn total_cost(item_quantity: &str) -> Result<i32, ParseIntError> { let processing_fee = 1; let cost_per_item = 5; let qty = match item_quantity.parse::<i32>(){ Ok(iqty) => iqty, Err(error) => panic!("Problem parsing the item_quantity: {:?}", error), }; Ok(qty * cost_per_item + processing_fee) }
solution2: match err to early return
pub fn total_cost(item_quantity: &str) -> Result<i32, ParseIntError> { let processing_fee = 1; let cost_per_item = 5; let qty = match item_quantity.parse::<i32>(){ Ok(iqty) => iqty, Err(e) => return Err(e), }; Ok(qty * cost_per_item + processing_fee) }
⌛️solution3: return custom error message
todo() // return Err(String::from("invalid digit found in string")),
errors3: ? -> return Result or Option to accept ?
// errors3.rs // This is a program that is trying to use a completed version of the // `total_cost` function from the previous exercise. It's not working though! // Why not? What should we do to fix it? // Execute `rustlings hint errors3` or use the `hint` watch subcommand for a hint. // I AM NOT DONE use std::num::ParseIntError; fn main() { let mut tokens = 100; let pretend_user_input = "8"; let cost = total_cost(pretend_user_input)?; if cost > tokens { println!("You can't afford that many!"); } else { tokens -= cost; println!("You now have {} tokens.", tokens); } } 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) }
Hint
If other functions can return a
Result, why shouldn’tmain?
It’s a fairly common
convention to return something like Result<(), ErrorType> from your main function.
The unit (()) type is there because nothing is really needed in terms of positive
results.
⚡️️solution: just like async fn to accept await -> return Result<T, E> to chain ?
fn main() -> Result<(), ParseIntError>{ // main function also could return Result<T, E> let mut tokens = 100; let pretend_user_input = "8"; let cost = total_cost(pretend_user_input)?; // should return Result to accept ?, if got error, here will return ParseIntError if cost > tokens { println!("You can't afford that many!"); } else { tokens -= cost; println!("You now have {} tokens.", tokens); } Ok(()) // if successed, here return the OK result () }
errors4
// errors4.rs // Execute `rustlings hint errors4` or use the `hint` watch subcommand for a hint. // I AM NOT DONE #[derive(PartialEq, Debug)] struct PositiveNonzeroInteger(u64); #[derive(PartialEq, Debug)] enum CreationError { Negative, Zero, } impl PositiveNonzeroInteger { fn new(value: i64) -> Result<PositiveNonzeroInteger, CreationError> { // Hmm...? Why is this only returning an Ok value? Ok(PositiveNonzeroInteger(value as u64)) } } // convert unit tests to main fn main() { fn test_creation() { assert!(PositiveNonzeroInteger::new(10).is_ok()); assert_eq!( Err(CreationError::Negative), PositiveNonzeroInteger::new(-10) ); assert_eq!(Err(CreationError::Zero), PositiveNonzeroInteger::new(0)); } test_creation(); } #[test] fn test_creation() { assert!(PositiveNonzeroInteger::new(10).is_ok()); assert_eq!( Err(CreationError::Negative), PositiveNonzeroInteger::new(-10) ); assert_eq!(Err(CreationError::Zero), PositiveNonzeroInteger::new(0)); }
Hint
PositiveNonzeroInteger::newis always creating a new instance and returning anOkresult.
It should be doing some checking, returning an Err result if those checks fail, and only
returning an Ok result if those checks determine that everything is… okay :)
solution1: use if to catch err
impl PositiveNonzeroInteger { fn new(value: i64) -> Result<PositiveNonzeroInteger, CreationError> { // Hmm...? Why is this only returning an Ok value? if value < 0 { Err(CreationError::Negative) } else if value == 0 { Err(CreationError::Zero) } else { Ok(PositiveNonzeroInteger(value as u64)) } } }
solution2: use match to catch err
impl PositiveNonzeroInteger { fn new(value: i64) -> Result<PositiveNonzeroInteger, CreationError> { // Hmm...? Why is this only returning an Ok value? match value { v if v < 0 => return Err(CreationError::Negative), v if v == 0 => return Err(CreationError::Zero), _ => return Ok(PositiveNonzeroInteger(value as u64)) } } }
- This exercise uses some concepts that we won’t get to until later in the course, like
Boxand theFromtrait. - It’s not important to understand them in detail right now, but you can read ahead if you like.
For now, think of the
Box<dyn ...>type as an “I want anything that does ???” type, which, given Rust’s usual standards for runtime safety, should strike you as somewhat lenient!
- In short, this particular use case for boxes is for when you want to own a value and you care only that it is a type which implements a particular trait.
- To do so, The Box is declared as of type
Box<dyn Trait>where Trait is the trait the compiler looks for on any value used in that context. - For this exercise, that context is the potential errors which can be returned in a Result.
What can we use to describe both errors? In other words, is there a trait which both errors implement?
errors5
// errors5.rs // This program uses an altered version of the code from errors4. // This exercise uses some concepts that we won't get to until later in the course, like `Box` and the // `From` trait. It's not important to understand them in detail right now, but you can read ahead if you like. // For now, think of the `Box<dyn ...>` type as an "I want anything that does ???" type, which, given // Rust's usual standards for runtime safety, should strike you as somewhat lenient! // In short, this particular use case for boxes is for when you want to own a value and you care only that it is a // type which implements a particular trait. To do so, The Box is declared as of type Box<dyn Trait> where Trait is the trait // the compiler looks for on any value used in that context. For this exercise, that context is the potential errors // which can be returned in a Result. // What can we use to describe both errors? In other words, is there a trait which both errors implement? // Execute `rustlings hint errors5` or use the `hint` watch subcommand for a hint. // I AM NOT DONE use std::error; use std::fmt; use std::num::ParseIntError; // TODO: update the return type of `main()` to make this compile. fn main() -> Result<(), Box<dyn ???>> { let pretend_user_input = "42"; let x: i64 = pretend_user_input.parse()?; println!("output={:?}", PositiveNonzeroInteger::new(x)?); Ok(()) } // 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)), } } } // This is required so that `CreationError` can implement `error::Error`. impl fmt::Display for CreationError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let description = match *self { CreationError::Negative => "number is negative", CreationError::Zero => "number is zero", }; f.write_str(description) } } impl error::Error for CreationError {}
Hint
There are two different possible Result types produced within main(), which are
propagated using ? operators.
How do we declare a return type from
main()that allows both?
- Under the hood, the
?operator callsFrom::fromon the error value to convert it to a boxed trait object, aBox<dyn error::Error>. - This boxed trait object is
polymorphic, and since all errors implement theerror::Errortrait, we can capture lots of different errors in one “Box” object.
Check out this section of the book: https://doc.rust-lang.org/book/ch09-02-recoverable-errors-with-result.html#a-shortcut-for-propagating-errors-the–operator
Read more about boxing errors: ⭐️Recoverable Errors with Result - The Rust Programming Language
Read more about using the ? operator with boxed errors: Other uses of ? - The Rust Programming Language
Using catch-all error types like Box<dyn error::Error> isn’t recommended
for library code:
where callers might want to make decisions based on the error content, instead of printing it out or propagating it further.
Here, we define a custom error type to make it possible for callers to decide what to do next when our function returns an error.
errors6
// errors6.rs // Using catch-all error types like `Box<dyn error::Error>` isn't recommended // for library code, where callers might want to make decisions based on the // error content, instead of printing it out or propagating it further. Here, // we define a custom error type to make it possible for callers to decide // what to do next when our function returns an error. // Execute `rustlings hint errors6` or use the `hint` watch subcommand for a hint. // I AM NOT DONE 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... } 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().unwrap(); 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)), } } } // convert unit tests to main fn main() { 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(_)) )); } fn test_negative() { assert_eq!( parse_pos_nonzero("-555"), Err(ParsePosNonzeroError::Creation(CreationError::Negative)) ); } fn test_zero() { assert_eq!( parse_pos_nonzero("0"), Err(ParsePosNonzeroError::Creation(CreationError::Zero)) ); } fn test_positive() { let x = PositiveNonzeroInteger::new(42); assert!(x.is_ok()); assert_eq!(parse_pos_nonzero("42"), Ok(x.unwrap())); } test_parse_error(); test_negative(); test_zero(); test_positive(); } #[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())); } }
Hint
This exercise uses a completed version of
PositiveNonzeroIntegerfrom errors4.
- Below the line that TODO asks you to change, there is an example of using the
map_err()method on aResultto transform one type of error into another. - Try using something similar on the
Resultfromparse(). - You might use the
?operator to return early from the function - or you might use a
matchexpression, or maybe there’s another way!
You can create another function inside
impl ParsePosNonzeroErrorto use withmap_err().
Read more about map_err() in the std::result documentation:
Result in std::result - Rust
solution: define a custom error type
impl ParsePosNonzeroError { fn from_creation(err: CreationError) -> ParsePosNonzeroError { ParsePosNonzeroError::Creation(err) } // TODO: add another error conversion function here. // fn from_parseint... 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().unwrap(); let x: i64 = s.parse().map_err(ParsePosNonzeroError::from_parseint)?; PositiveNonzeroInteger::new(x) .map_err(ParsePosNonzeroError::from_creation) }
Options
Type Option represents an optional value: every Option is either Some and contains a value, or None, and does not.
Option types are very common in Rust code, as they have a number of uses:
- Initial values
- Return values for functions that are not defined over their entire input range (partial functions)
- Return value for otherwise reporting simple errors, where None is returned on error
- Optional struct fields
- Struct fields that can be loaned or “taken”
- Optional function arguments
- Nullable pointers
- Swapping things out of difficult situations
Further Information
- ✨Generic Data Types->Option Enum Format - The Rust Programming Language
- Option Module Documentation
- Option Enum Documentation
- if let - The Rust Programming Language
- while let - The Rust Programming Language
rustlings-solutions-5/options at main · gaveen/rustlings-solutions-5
Rustlings
options1
// options1.rs // Execute `rustlings hint options1` or use the `hint` watch subcommand for a hint. // I AM NOT DONE // This function returns how much icecream there is left in the fridge. // If it's before 10PM, there's 5 pieces left. At 10PM, someone eats them // all, so there'll be no more left :( 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! ? ? ? } // convert unit tests to main fn main() { 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); } 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, 5); } check_icecream(); raw_value(); } #[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, 5); } }
Hint
Options can have a Some value, with an inner value, or a None value, without an inner value.
There’s multiple ways to get at the inner value, you can use unwrap, or pattern match.
Unwrapping is the easiest, but how do you do it safely so that it doesn’t panic in your face later?
solution1: pattern match
fn maybe_icecream(time_of_day: u16) -> Option<u16> { // We use the 24-hour system here, so 10PM is a value of 22 // The Option output should gracefully handle cases where time_of_day > 24. match time_of_day { 0..=21 => Some(5), 22..=24 => Some(0), _ => None, } // Exclusive range (e.g., 0..22) pattern use here is experimental // on rustc 1.62.1 }
assert_eq!(icecreams.unwrap_or(0), 5); // Use unwrapped Some or 0
options2
// options2.rs // Execute `rustlings hint options2` or use the `hint` watch subcommand for a hint. // I AM NOT DONE // convert unit tests to main fn main() { fn simple_option() { let target = "rustlings"; let optional_target = Some(target); // TODO: Make this an if let statement whose value is "Some" type word = optional_target { assert_eq!(word, target); } } fn layered_option() { let mut range = 10; let mut optional_integers: Vec<Option<i8>> = Vec::new(); for i in 0..(range + 1) { optional_integers.push(Some(i)); } // 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 integer = optional_integers.pop() { assert_eq!(integer, range); range -= 1; } } simple_option(); layered_option(); } #[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 word = optional_target { assert_eq!(word, target); } } #[test] fn layered_option() { let mut range = 10; let mut optional_integers: Vec<Option<i8>> = Vec::new(); for i in 0..(range + 1) { optional_integers.push(Some(i)); } // 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 integer = optional_integers.pop() { assert_eq!(integer, range); range -= 1; } } }
Hint
check out:
Remember that Options can be stacked in if let and while let. For example: Some(Some(variable)) = variable2 Also see Option::flatten
solution: if let & while let
if let Some(word) = optional_target { assert_eq!(word, target); }
while let Some(integer) = optional_integers.pop().flatten() { assert_eq!(integer, range); range -= 1; }
options3: ref -> value partially moved here
// options3.rs // Execute `rustlings hint options3` or use the `hint` watch subcommand for a hint. // I AM NOT DONE struct Point { x: i32, y: i32, } fn main() { let y: Option<Point> = Some(Point { x: 100, y: 200 }); match y { Some(p) => println!("Co-ordinates are {},{} ", p.x, p.y), _ => println!("no match"), } y; // Fix without deleting this line. }
Hint
The compiler says a partial move happened in the match
statement.
How can this be avoided? The compiler shows the correction needed. After making the correction as suggested by the compiler, do read: ref - Rust
Generics
Generics is the topic of generalizing types and functionalities to broader cases. This is extremely useful for reducing code duplication in many ways, but can call for rather involving syntax. Namely, being generic requires taking great care to specify over which types a generic type is actually considered valid. The simplest and most common use of generics is for type parameters.
Further information
Rustlings
generics1
// This shopping list program isn't compiling! // Use your knowledge of generics to fix it. // Execute `rustlings hint generics1` or use the `hint` watch subcommand for a hint. // I AM NOT DONE fn main() { let mut shopping_list: Vec<?> = Vec::new(); shopping_list.push("milk"); }
rustlings-solutions-5/generics at main · gaveen/rustlings-solutions-5
Hint
Vectors in Rust make use of generics to create dynamically sized arrays of any type. You need to tell the compiler what type we are pushing onto this vector.
solution1: &str
// This shopping list program isn't compiling! // Use your knowledge of generics to fix it. // Execute `rustlings hint generics1` or use the `hint` watch subcommand for a hint. fn main() { let mut shopping_list: Vec<&str> = Vec::new(); shopping_list.push("milk"); }
solution2: String
// This shopping list program isn't compiling! // Use your knowledge of generics to fix it. // Execute `rustlings hint generics1` or use the `hint` watch subcommand for a hint. fn main() { let mut shopping_list: Vec<String> = Vec::new(); shopping_list.push("milk"); // fix here }
generics2
// This powerful wrapper provides the ability to store a positive integer value. // Rewrite it using generics so that it supports wrapping ANY type. // Execute `rustlings hint generics2` or use the `hint` watch subcommand for a hint. // I AM NOT DONE struct Wrapper { value: u32, } impl Wrapper { pub fn new(value: u32) -> Self { Wrapper { value } } } // #[cfg(test)] // mod tests { // use super::*; // // #[test] // fn store_u32_in_wrapper() { // assert_eq!(Wrapper::new(42).value, 42); // } // // #[test] // fn store_str_in_wrapper() { // assert_eq!(Wrapper::new("Foo").value, "Foo"); // } // } // change to run in playground fn main() { assert_eq!(Wrapper::new(42).value, 42); assert_eq!(Wrapper::new("Foo").value, "Foo"); }
Hint
Currently we are wrapping only values of type ‘u32’. Maybe we could update the explicit references to this data type somehow?
If you are still stuck, please read:
solution1: convert to use generics
// This powerful wrapper provides the ability to store a positive integer value. // Rewrite it using generics so that it supports wrapping ANY type. struct Wrapper { // fix here value: T, } impl<T> Wrapper<T> { pub fn new(value: T) -> Self { Wrapper { value } } } fn main() { assert_eq!(Wrapper::new(42).value, 42); assert_eq!(Wrapper::new("Foo").value, "Foo"); }
Traits
A trait is a collection of methods.
Data types can implement traits. To do so, the methods making up the trait are defined for the data type. For example, the String data type implements the From<&str> trait. This allows a user to write String::from("hello").
In this way, traits are somewhat similar to Java interfaces and C++ abstract classes.
Some additional common Rust traits include:
Clone(theclonemethod)Display(which allows formatted display via{})Debug(which allows formatted display via{:?})
Because traits indicate shared behavior between data types, they are useful when writing generics.
Further information
Rustlings
traits1: Time to implement some traits!
// traits1.rs // Time to implement some traits! // // Your task is to implement the trait // `AppendBar` for the type `String`. // // The trait AppendBar has only one function, // which appends "Bar" to any object // implementing this trait. // Execute `rustlings hint traits1` or use the `hint` watch subcommand for a hint. // I AM NOT DONE trait AppendBar { fn append_bar(self) -> Self; } impl AppendBar for String { // TODO: Implement `AppendBar` for type `String`. } fn main() { let s = String::from("Foo"); let s = s.append_bar(); println!("s: {}", s); // convert unit test to play in playground assert_eq!( String::from("Foo").append_bar(), String::from("FooBar") ); assert_eq!( String::from("").append_bar().append_bar(), String::from("BarBar") ); } // #[cfg(test)] // mod tests { // use super::*; // // #[test] // fn is_foo_bar() { // assert_eq!(String::from("Foo").append_bar(), String::from("FooBar")); // } // // #[test] // fn is_bar_bar() { // assert_eq!( // String::from("").append_bar().append_bar(), // String::from("BarBar") // ); // } // }
Hint
A discussion about Traits in Rust can be found at: 🪐Traits: Defining Shared Behavior - The Rust Programming Language
solution: String
impl AppendBar for String { //Add your code here fn append_bar(self) -> Self { format!("{}Bar", self) // or // format!("{self}Bar") // or // self + "Bar" } }
traits2: implement the trait for a vector of strings.
// traits2.rs // // Your task is to implement the trait // `AppendBar` for a vector of strings. // // To implement this trait, consider for // a moment what it means to 'append "Bar"' // to a vector of strings. // // No boiler plate code this time, // you can do this! // Execute `rustlings hint traits2` or use the `hint` watch subcommand for a hint. // I AM NOT DONE trait AppendBar { fn append_bar(self) -> Self; } // TODO: Implement trait `AppendBar` for a vector of strings. // #[cfg(test)] // mod tests { // use super::*; // // #[test] // fn is_vec_pop_eq_bar() { // let mut foo = vec![String::from("Foo")].append_bar(); // assert_eq!(foo.pop().unwrap(), String::from("Bar")); // assert_eq!(foo.pop().unwrap(), String::from("Foo")); // } // } fn main() { // convert unit test to main to play in playground let mut foo = vec![String::from("Foo")].append_bar(); assert_eq!(foo.pop().unwrap(), String::from("Bar")); assert_eq!(foo.pop().unwrap(), String::from("Foo")); }
Hint
Notice how the trait takes ownership of ‘self’,and returns Self.
Try mutating the incoming string vector. Have a look at the tests to see
what the result should look like!
Vectors provide suitable methods for adding an element at the end. See the documentation at: Vec in std::vec - Rust
solution: Vec
// Add your code here impl AppendBar for Vec<String> { fn append_bar(mut self) -> Self { // Borrow self as `mut` self.push("Bar".to_string()); self } }
traits3: implement the Licensed trait for both structures and have them return the same information without writing the same function twice.
// traits3.rs // // Your task is to implement the Licensed trait for // both structures and have them return the same // information without writing the same function twice. // // Consider what you can add to the Licensed trait. // Execute `rustlings hint traits3` or use the `hint` watch subcommand for a hint. // I AM NOT DONE pub trait Licensed { fn licensing_info(&self) -> String; } struct SomeSoftware { version_number: i32, } struct OtherSoftware { version_number: String, } impl Licensed for SomeSoftware {} // Don't edit this line impl Licensed for OtherSoftware {} // Don't edit this line // #[cfg(test)] // mod tests { // use super::*; // // #[test] // fn is_licensing_info_the_same() { // let licensing_info = String::from("Some information"); // let some_software = SomeSoftware { version_number: 1 }; // let other_software = OtherSoftware { // version_number: "v2.0.0".to_string(), // }; // assert_eq!(some_software.licensing_info(), licensing_info); // assert_eq!(other_software.licensing_info(), licensing_info); // } // } fn main() { // convert unit test to main to play in playground let licensing_info = String::from("Some information"); let some_software = SomeSoftware { version_number: 1 }; let other_software = OtherSoftware { version_number: "v2.0.0".to_string(), }; assert_eq!(some_software.licensing_info(), licensing_info); }
Hint
Traits can have a default implementation for functions. Structs that implement the trait can then use the default version of these functions if they choose not implement the function themselves.
See the documentation at: 🪐Traits: Defining Shared Behavior >> Default Implementations - The Rust Programming Language
solution: Default Trait Method
pub trait Licensed { fn licensing_info(&self) -> String { String::from("Some information") } }
traits4: trait bounds
// traits4.rs // // Your task is to replace the '??' sections so the code compiles. // Don't change any line other than the marked one. // Execute `rustlings hint traits4` or use the `hint` watch subcommand for a hint. // I AM NOT DONE pub trait Licensed { fn licensing_info(&self) -> String { "some information".to_string() } } struct SomeSoftware {} struct OtherSoftware {} impl Licensed for SomeSoftware {} impl Licensed for OtherSoftware {} // YOU MAY ONLY CHANGE THE NEXT LINE fn compare_license_types(software: ? ?, software_two: ? ?) -> bool { software.licensing_info() == software_two.licensing_info() } // #[cfg(test)] // mod tests { // use super::*; // // #[test] // fn compare_license_information() { // let some_software = SomeSoftware {}; // let other_software = OtherSoftware {}; // // assert!(compare_license_types(some_software, other_software)); // } // // #[test] // fn compare_license_information_backwards() { // let some_software = SomeSoftware {}; // let other_software = OtherSoftware {}; // // assert!(compare_license_types(other_software, some_software)); // } // } fn main() { // convert unit test to main to play in playground // compare_license_information let some_software = SomeSoftware {}; let other_software = OtherSoftware {}; assert!(compare_license_types(some_software, other_software)); // compare_license_information_backwards let some_software = SomeSoftware {}; let other_software = OtherSoftware {}; assert!(compare_license_types(other_software, some_software)); }
Hint
Instead of using concrete types as parameters you can use traits. Try replacing the ‘??’ with ‘impl <what goes here?>’
See the documentation at: 🪐Traits: Defining Shared Behavior >> Traits as Parameters - The Rust Programming Language
solution: impl
fn compare_license_types(software: impl Licensed, software_two: impl Licensed) -> bool { software.licensing_info() == software_two.licensing_info() }
traits5: impl multiple traits
// traits5.rs // // Your task is to replace the '??' sections so the code compiles. // Don't change any line other than the marked one. // Execute `rustlings hint traits5` or use the `hint` watch subcommand for a hint. // I AM NOT DONE pub trait SomeTrait { fn some_function(&self) -> bool { true } } pub trait OtherTrait { fn other_function(&self) -> bool { true } } struct SomeStruct {} struct OtherStruct {} impl SomeTrait for SomeStruct {} impl OtherTrait for SomeStruct {} impl SomeTrait for OtherStruct {} impl OtherTrait for OtherStruct {} // YOU MAY ONLY CHANGE THE NEXT LINE fn some_func(item: ??) -> bool { item.some_function() && item.other_function() } fn main() { some_func(SomeStruct {}); some_func(OtherStruct {}); }
Hint
To ensure a parameter implements multiple traits use the ‘+ syntax’. Try replacing the ‘??’ with ‘impl <> + <>’.
See the documentation at: Specifying Multiple Trait Bounds with the + Syntax
solution: impl or where
fn some_func<T>(item: T) -> bool where T: SomeTrait + OtherTrait { item.some_function() && item.other_function() } fn some_func(item: impl SomeTrait + OtherTrait) -> bool { item.some_function() && item.other_function() }
Macros
Rust’s macro system is very powerful, but also kind of difficult to wrap your head around. We’re not going to teach you how to write your own fully-featured macros. Instead, we’ll show you how to use and create them.
If you’d like to learn more about writing your own macros, the macrokata project has a similar style of exercises to Rustlings, but is all about learning to write Macros.
Further information
Rustlings
macros1
// macros1.rs // Execute `rustlings hint macros1` or use the `hint` watch subcommand for a hint. // I AM NOT DONE macro_rules! my_macro { () => { println!("Check out my macro!"); }; } fn main() { my_macro(); }
macros2
// macros2.rs // Execute `rustlings hint macros2` or use the `hint` watch subcommand for a hint. // I AM NOT DONE fn main() { my_macro!(); } macro_rules! my_macro { () => { println!("Check out my macro!"); }; }
macros3
// macros3.rs // Make me compile, without taking the macro out of the module! // Execute `rustlings hint macros3` or use the `hint` watch subcommand for a hint. // I AM NOT DONE mod macros { macro_rules! my_macro { () => { println!("Check out my macro!"); }; } } fn main() { my_macro!(); }
macros4
// macros4.rs // Execute `rustlings hint macros4` or use the `hint` watch subcommand for a hint. // I AM NOT DONE macro_rules! my_macro { () => { println!("Check out my macro!"); } ($val:expr) => { println!("Look at this other macro: {}", $val); } } fn main() { my_macro!(); my_macro!(7777); }
Clippy
The Clippy tool is a collection of lints to analyze your code so you can catch common mistakes and improve your Rust code.
If you used the installation script for Rustlings, Clippy should be already installed.
If not you can install it manually via rustup component add clippy.
Further information
Rustlings
clippy1
// clippy1.rs // The Clippy tool is a collection of lints to analyze your code // so you can catch common mistakes and improve your Rust code. // // For these exercises the code will fail to compile when there are clippy warnings // check clippy's suggestions from the output to solve the exercise. // Execute `rustlings hint clippy1` or use the `hint` watch subcommand for a hint. // I AM NOT DONE use std::f32; fn main() { let pi = 3.14f32; let radius = 5.00f32; let area = pi * f32::powi(radius, 2); println!( "The area of a circle with radius {:.2} is {:.5}!", radius, area ) }
clippy2
// clippy2.rs // Execute `rustlings hint clippy2` or use the `hint` watch subcommand for a hint. // I AM NOT DONE fn main() { let mut res = 42; let option = Some(12); for x in option { res += x; } println!("{}", res); }
clippy3
// clippy3.rs // Here's a couple more easy Clippy fixes, so you can see its utility. // I AM NOT DONE #[allow(unused_variables, unused_assignments)] fn main() { let my_option: Option<()> = None; if my_option.is_none() { my_option.unwrap(); } let my_arr = &[ -1, -2, -3 -4, -5, -6 ]; println!("My array! Here it is: {:?}", my_arr); let my_empty_vec = vec![1, 2, 3, 4, 5].resize(0, 5); println!("This Vec is empty, see? {:?}", my_empty_vec); let mut value_a = 45; let mut value_b = 66; // Let's swap these two! value_a = value_b; value_b = value_a; println!("value a: {}; value b: {}", value_a, value_b); }
Functions
Here, you’ll learn how to write functions and how the Rust compiler can help you debug errors even in more complex code.
Further information
Rustlings
functions1
// functions1.rs // Execute `rustlings hint functions1` or use the `hint` watch subcommand for a hint. // I AM NOT DONE fn main() { call_me(); }
functions2
// functions2.rs // Execute `rustlings hint functions2` or use the `hint` watch subcommand for a hint. // I AM NOT DONE fn main() { call_me(3); } fn call_me(num:) { for i in 0..num { println!("Ring! Call number {}", i + 1); } }
functions3
// functions3.rs // Execute `rustlings hint functions3` or use the `hint` watch subcommand for a hint. // I AM NOT DONE fn main() { call_me(); } fn call_me(num: u32) { for i in 0..num { println!("Ring! Call number {}", i + 1); } }
functions4
// functions4.rs // Execute `rustlings hint functions4` or use the `hint` watch subcommand for a hint. // This store is having a sale where if the price is an even number, you get // 10 Rustbucks off, but if it's an odd number, it's 3 Rustbucks off. // (Don't worry about the function bodies themselves, we're only interested // in the signatures for now. If anything, this is a good way to peek ahead // to future exercises!) // I AM NOT DONE fn main() { let original_price = 51; println!("Your sale price is {}", sale_price(original_price)); } fn sale_price(price: i32) -> { if is_even(price) { price - 10 } else { price - 3 } } fn is_even(num: i32) -> bool { num % 2 == 0 }
functions5
// functions5.rs // Execute `rustlings hint functions5` or use the `hint` watch subcommand for a hint. // I AM NOT DONE fn main() { let answer = square(3); println!("The square of 3 is {}", answer); } fn square(num: i32) -> i32 { num * num; }
If
if, the most basic (but still surprisingly versatile!) type of control flow, is what you’ll learn here.
Further information
Rustlings
if1
// if1.rs // Execute `rustlings hint if1` or use the `hint` watch subcommand for a hint. // I AM NOT DONE pub fn bigger(a: i32, b: i32) -> i32 { // Complete this function to return the bigger number! // Do not use: // - another function call // - additional variables } // Don't mind this for now :) #[cfg(test)] mod tests { use super::*; #[test] fn ten_is_bigger_than_eight() { assert_eq!(10, bigger(10, 8)); } #[test] fn fortytwo_is_bigger_than_thirtytwo() { assert_eq!(42, bigger(32, 42)); } }
if2
// if2.rs // Step 1: Make me compile! // Step 2: Get the bar_for_fuzz and default_to_baz tests passing! // Execute `rustlings hint if2` or use the `hint` watch subcommand for a hint. // I AM NOT DONE pub fn foo_if_fizz(fizzish: &str) -> &str { if fizzish == "fizz" { "foo" } else { 1 } } // No test changes needed! #[cfg(test)] mod tests { use super::*; #[test] fn foo_for_fizz() { assert_eq!(foo_if_fizz("fizz"), "foo") } #[test] fn bar_for_fuzz() { assert_eq!(foo_if_fizz("fuzz"), "bar") } #[test] fn default_to_baz() { assert_eq!(foo_if_fizz("literally anything"), "baz") } }
Lifetimes
Lifetimes tell the compiler how to check:
whether
referenceslive long enough to be valid in any given situation.
For example lifetimes say “make sure parameter ‘a’ lives as long as parameter ‘b’ so that the return value is valid”.
They are only necessary on borrows, i.e. references
- Since copied parameters or moves are owned in their scope and cannot be referenced outside.
- Lifetimes mean that calling code of e.g. functions can be checked to make sure their arguments are valid.
- Lifetimes are restrictive of their callers.
Further information
- Validating References with Lifetimes
- Lifetimes (in Rust By Example)
- Lifetimes - The Rust Programming Language
Rustlings
returns a value referencing data owned by the current function
expected named lifetime parameter
function
// lifetimes1.rs // // The Rust compiler needs to know how to check whether supplied references are // valid, so that it can let the programmer know if a reference is at risk // of going out of scope before it is used. Remember, references are borrows // and do not own their own data. What if their owner goes out of scope? // // Execute `rustlings hint lifetimes1` or use the `hint` watch subcommand for a hint. // I AM NOT DONE fn longest(x: &str, y: &str) -> &str { if x.len() > y.len() { x } else { y } } fn main() { let string1 = String::from("abcd"); let string2 = "xyz"; let result = longest(string1.as_str(), string2); println!("The longest string is '{}'", result); }
Hint
Let the compiler guide you. Also take a look at the book if you need help:
struct
// lifetimes3.rs // // Lifetimes are also needed when structs hold references. // // Execute `rustlings hint lifetimes3` or use the `hint` watch subcommand for a hint. // I AM NOT DONE struct Book { author: &str, title: &str, } fn main() { let name = String::from("Jill Smith"); let title = String::from("Fish Flying"); let book = Book { author: &name, title: &title }; println!("{} by {}", book.title, book.author); }
borrowed value does not live long enough
lifetimes2
// lifetimes2.rs // // So if the compiler is just validating the references passed // to the annotated parameters and the return type, what do // we need to change? // // Execute `rustlings hint lifetimes2` or use the `hint` watch subcommand for a hint. // I AM NOT DONE fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y } } fn main() { let string1 = String::from("long string is long"); let result; { let string2 = String::from("xyz"); result = longest(string1.as_str(), string2.as_str()); } println!("The longest string is '{}'", result); }
Hint: 将块看作生命周期隐式声明
Remember that the generic lifetime ’a will get the concrete lifetime that is equal to the smaller of the lifetimes of x and y. You can take at least two paths to achieve the desired result while keeping the inner block:
- Move the string2 declaration to make it live as long as string1 (how is result declared?)
- Move println! into the inner block
Q&A
Q: 为什么不能改成
#![allow(unused)] fn main() { fn longest<'a:'b, 'b>(x: &'a str, y: &'b str) -> &'b str { if x.len() > y.len() { x } else { y } } }
A: 因为可能返回’a x, 也可能返回’b y. 这里主要问题在于返回的引用有两种生命周期。
lifetimes4
extern crate regex; // just for running in rust playground use regex::Regex; fn get_publish_date(title: String) -> String{ let date_re = Regex::new(r"(\d{4}-\d{2}-\d{2})").unwrap(); // let publish_date = date_re.captures(title.as_str()).unwrap().get(1).unwrap().as_str(); // ----------------------------------------------------^^^^^^ here to match. let publish_date = match date_re.captures(title.as_str()) { Some(captured) => captured.get(1).unwrap().as_str(), // 这里unwrap()之后只有as_str()方法, 没有to_string() // None => format!("Unable to extract date from {}", title).as_str() // temporary value is freed at the end of this statement // None => "Unable to extract date from {title}" // str is equal to &'static str ? None => { let temp = format!("Unable to extract date from {}", title); temp.as_str() } }; publish_date.to_string() } fn main() { let title = "【Rust Daily】2023-01-21"; // &str let publish_date = get_publish_date(title); println!("Title: {}\nPublish_data: {}\n", title, publish_date); }
Hint: 函数返回引用才会考虑live long enough
extern crate regex; // just for running in rust playground use regex::Regex; fn get_publish_date(title: &str) -> String{ let date_re = Regex::new(r"(\d{4}-\d{2}-\d{2})").unwrap(); // let publish_date = date_re.captures(title.as_str()).unwrap().get(1).unwrap().as_str(); // ----------------------------------------------------^^^^^^ here to match. let publish_date = match date_re.captures(title) { Some(captured) => captured.get(1).unwrap().as_str().to_string(), // 这里unwrap()之后只有as_str()方法, 没有to_string() // None => format!("Unable to extract date from {}", title).as_str() // temporary value is freed at the end of this statement // None => "Unable to extract date from {title}" // str is equal to &'static str ? None => format!("Unable to extract date from {}", title) }; publish_date } fn main() { let title = "【Rust Daily】2023-01-21"; let publish_date = get_publish_date(title); println!("Title: {}\nPublish_data: {}\n", title, publish_date); }
Modules
In this section we’ll give you an introduction to Rust’s module system.
Further information
Rustlings
modules1: pub
// modules1.rs // Execute `rustlings hint modules1` or use the `hint` watch subcommand for a hint. // I AM NOT DONE mod sausage_factory { // Don't let anybody outside of this module see this! fn get_secret_recipe() -> String { String::from("Ginger") } fn make_sausage() { get_secret_recipe(); println!("sausage!"); } } fn main() { sausage_factory::make_sausage(); }
Hint
add
pubEverything is private in Rust by default– but there’s a keyword we can use to make something public! The compiler error should point to the thing that needs to be public.
modules2: use self::xxx as bbb
// modules2.rs // You can bring module paths into scopes and provide new names for them with the // 'use' and 'as' keywords. Fix these 'use' statements to make the code compile. // Execute `rustlings hint modules2` or use the `hint` watch subcommand for a hint. // I AM NOT DONE mod delicious_snacks { // TODO: Fix these use statements use self::fruits::PEAR as ??? use self::veggies::CUCUMBER as ??? 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 ); }
Hint
The delicious_snacks module is trying to present an external interface that is
different than its internal structure (the fruits and veggies modules and
associated constants). Complete the use statements to fit the uses in main and
find the one keyword missing for both constants.
#![allow(unused)] fn main() { pub use self::fruits::PEAR as fruit; pub use self::veggies::CUCUMBER as veggie; }
modules3
// modules3.rs // You can use the 'use' keyword to bring module paths from modules from anywhere // and especially from the Rust standard library into your scope. // Bring SystemTime and UNIX_EPOCH // from the std::time module. Bonus style points if you can do it with one line! // Execute `rustlings hint modules3` or use the `hint` watch subcommand for a hint. // I AM NOT DONE // TODO: Complete this use statement use ??? 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!"), } }
Hint
#![allow(unused)] fn main() { use std::time::{SystemTime, UNIX_EPOCH}; }
UNIX_EPOCH and SystemTime are declared in the std::time module. Add a use statement for these two to bring them into scope. You can use nested paths or the glob operator to bring these two in using only one line.
Move Semantics
These exercises are adapted from pnkfelix’s Rust Tutorial – Thank you Felix!!!
Further information
For this section, the book links are especially important.
Rustlings
借用
move_semantics1
// move_semantics1.rs // Execute `rustlings hint move_semantics1` or use the `hint` watch subcommand for a hint. // I AM NOT DONE fn main() { let vec0 = Vec::new(); let vec1 = fill_vec(vec0); // let vec1 = fill_vec(&vec0); // vec0.push(24); // Try accessing `vec0` after having called `fill_vec()`. See what happens! println!("{} has length {} content `{:?}`", "vec1", vec1.len(), vec1); vec1.push(88); println!("{} has length {} content `{:?}`", "vec1", vec1.len(), vec1); } fn fill_vec(vec: Vec<i32>) -> Vec<i32> { let mut vec = vec; vec.push(22); vec.push(44); vec.push(66); vec }
Hint
So you’ve got the “cannot borrow immutable local variable vec1 as mutable” error on line 13,
right? The fix for this is going to be adding one keyword, and the addition is NOT on line 13
where the error is.
Also: Try accessing vec0 after having called fill_vec(). See what happens!
可变借用
move_semantics2
// move_semantics2.rs // Make me compile without changing line 13 or moving line 10! // Execute `rustlings hint move_semantics2` or use the `hint` watch subcommand for a hint. // I AM NOT DONE fn main() { let vec0 = Vec::new(); let mut vec1 = fill_vec(vec0); // Do not change the following line! println!("{} has length {} content `{:?}`", "vec0", vec0.len(), vec0); vec1.push(88); println!("{} has length {} content `{:?}`", "vec1", vec1.len(), vec1); } fn fill_vec(vec: Vec<i32>) -> Vec<i32> { let mut vec = vec; vec.push(22); vec.push(44); vec.push(66); vec }
Hint
So, vec0 is passed into the fill_vec function as an argument. In Rust,
when an argument is passed to a function and it’s not explicitly returned,
you can’t use the original variable anymore. We call this “moving” a variable.
Variables that are moved into a function (or block scope) and aren’t explicitly
returned get “dropped” at the end of that function. This is also what happens here.
There’s a few ways to fix this, try them all if you want:
- Make another, separate version of the data that’s in
vec0and pass that tofill_vecinstead. - Make
fill_vecborrow its argument instead of taking ownership of it, and then copy the data within the function in order to return an ownedVec<i32> - Make
fill_vecmutably borrow a reference to its argument (which will need to be mutable), modify it directly, then not return anything. Then you can get rid ofvec1entirely – note that this will change what gets printed by the firstprintln!
move_semantics3
// move_semantics3.rs // Make me compile without adding new lines-- just changing existing lines! // (no lines with multiple semicolons necessary!) // Execute `rustlings hint move_semantics3` or use the `hint` watch subcommand for a hint. // I AM NOT DONE fn main() { let vec0 = Vec::new(); let mut vec1 = fill_vec(vec0); println!("{} has length {} content `{:?}`", "vec1", vec1.len(), vec1); vec1.push(88); println!("{} has length {} content `{:?}`", "vec1", vec1.len(), vec1); } fn fill_vec(vec: Vec<i32>) -> Vec<i32> { vec.push(22); vec.push(44); vec.push(66); vec }
Hint
The difference between this one and the previous ones is that the first line
of fn fill_vec that had let mut vec = vec; is no longer there. You can,
instead of adding that line back, add mut in one place that will change
an existing binding to be a mutable binding instead of an immutable one :)
move_semantics4
// move_semantics4.rs // Refactor this code so that instead of passing `vec0` into the `fill_vec` function, // the Vector gets created in the function itself and passed back to the main // function. // Execute `rustlings hint move_semantics4` or use the `hint` watch subcommand for a hint. // I AM NOT DONE fn main() { let vec0 = Vec::new(); let mut vec1 = fill_vec(vec0); println!("{} has length {} content `{:?}`", "vec1", vec1.len(), vec1); vec1.push(88); println!("{} has length {} content `{:?}`", "vec1", vec1.len(), vec1); } // `fill_vec()` no longer takes `vec: Vec<i32>` as argument fn fill_vec() -> Vec<i32> { let mut vec = vec; vec.push(22); vec.push(44); vec.push(66); vec }
Hint
Stop reading whenever you feel like you have enough direction :) Or try doing one step and then fixing the compiler errors that result! So the end goal is to:
- get rid of the first line in main that creates the new vector
- so then
vec0doesn’t exist, so we can’t pass it tofill_vec - we don’t want to pass anything to
fill_vec, so its signature should reflect that it does not take any arguments - since we’re not creating a new vec in
mainanymore, we need to create a new vec infill_vec, similarly to the way we did inmain
move_semantics5
// move_semantics5.rs // Make me compile only by reordering the lines in `main()`, but without // adding, changing or removing any of them. // Execute `rustlings hint move_semantics5` or use the `hint` watch subcommand for a hint. // I AM NOT DONE fn main() { let mut x = 100; let y = &mut x; let z = &mut x; *y += 100; *z += 1000; assert_eq!(x, 1200); }
Hint
Carefully reason about the range in which each mutable reference is in scope. Does it help to update the value of referent (x) immediately after the mutable reference is taken? Read more about ‘Mutable References’:
References and Borrowing > Mutable References - The Rust Programming Language
move_semantics6
// move_semantics6.rs // Execute `rustlings hint move_semantics6` or use the `hint` watch subcommand for a hint. // You can't change anything except adding or removing references. // I AM NOT DONE 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); }
Hint
To find the answer, you can consult the book section “References and Borrowing”
-
The first problem is that
get_charis taking ownership of the string. Sodatais moved and can’t be used forstring_uppercasedatais moved toget_charfirst, meaning thatstring_uppercasecannot manipulate the data. Once you’ve fixed that,string_uppercase’s function signature will also need to be adjusted. Can you figure out how? -
Another hint: it has to do with the
&character.
Tests
Going out of order from the book to cover tests – many of the following exercises will ask you to make tests pass!
Further information
Rustlings
tests1
// tests1.rs // Tests are important to ensure that your code does what you think it should do. // Tests can be run on this file with the following command: // rustlings run tests1 // This test has a problem with it -- make the test compile! Make the test // pass! Make the test fail! // Execute `rustlings hint tests1` or use the `hint` watch subcommand for a hint. // I AM NOT DONE #[cfg(test)] mod tests { #[test] fn you_can_assert() { assert!(); } }
tests2
// tests2.rs // This test has a problem with it -- make the test compile! Make the test // pass! Make the test fail! // Execute `rustlings hint tests2` or use the `hint` watch subcommand for a hint. // I AM NOT DONE #[cfg(test)] mod tests { #[test] fn you_can_assert_eq() { assert_eq!(); } }
tests3
// tests3.rs // This test isn't testing our function -- make it do that in such a way that // the test passes. Then write a second test that tests whether we get the result // we expect to get when we call `is_even(5)`. // Execute `rustlings hint tests3` or use the `hint` watch subcommand for a hint. // I AM NOT DONE pub fn is_even(num: i32) -> bool { num % 2 == 0 } #[cfg(test)] mod tests { use super::*; #[test] fn is_true_when_even() { assert!(); } #[test] fn is_false_when_odd() { assert!(); } }
Threads
In most current operating systems, an executed program’s code is run in a process, and the operating system manages multiple processes at once. Within your program, you can also have independent parts that run simultaneously. The features that run these independent parts are called threads.
Further information
Rustlings
threads1
// threads1.rs // Execute `rustlings hint threads1` or use the `hint` watch subcommand for a hint. // 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. // I AM NOT DONE use std::thread; use std::time::{Duration, Instant}; fn main() { let mut handles = vec![]; for i in 0..10 { handles.push(thread::spawn(move || { let start = Instant::now(); thread::sleep(Duration::from_millis(250)); println!("thread {} is complete", i); start.elapsed().as_millis() })); } let mut results: Vec<u128> = vec![]; for handle in handles { // TODO: a struct is returned from thread::spawn, can you use it? } if results.len() != 10 { panic!("Oh no! All the spawned threads did not finish!"); } println!(); for (i, result) in results.into_iter().enumerate() { println!("thread {} took {}ms", i, result); } }
threads2
// threads2.rs // Execute `rustlings hint threads2` or use the `hint` watch subcommand for a hint. // Building on the last exercise, we want all of the threads to complete their work but this time // the spawned threads need to be in charge of updating a shared value: JobStatus.jobs_completed // I AM NOT DONE use std::sync::Arc; use std::thread; use std::time::Duration; struct JobStatus { jobs_completed: u32, } fn main() { let status = Arc::new(JobStatus { jobs_completed: 0 }); let mut handles = vec![]; for _ in 0..10 { let status_shared = Arc::clone(&status); let handle = thread::spawn(move || { thread::sleep(Duration::from_millis(250)); // TODO: You must take an action before you update a shared value status_shared.jobs_completed += 1; }); handles.push(handle); } for handle in handles { handle.join().unwrap(); // TODO: Print the value of the JobStatus.jobs_completed. Did you notice anything // interesting in the output? Do you have to 'join' on all the handles? println!("jobs completed {}", ???); } }
threads3
// threads3.rs // Execute `rustlings hint threads3` or use the `hint` watch subcommand for a hint. // I AM NOT DONE use std::sync::mpsc; use std::sync::Arc; use std::thread; use std::time::Duration; struct Queue { length: u32, first_half: Vec<u32>, second_half: Vec<u32>, } impl Queue { fn new() -> Self { Queue { length: 10, first_half: vec![1, 2, 3, 4, 5], second_half: vec![6, 7, 8, 9, 10], } } } fn send_tx(q: Queue, tx: mpsc::Sender<u32>) -> () { let qc = Arc::new(q); let qc1 = Arc::clone(&qc); let qc2 = Arc::clone(&qc); thread::spawn(move || { for val in &qc1.first_half { println!("sending {:?}", val); tx.send(*val).unwrap(); thread::sleep(Duration::from_secs(1)); } }); thread::spawn(move || { for val in &qc2.second_half { println!("sending {:?}", val); tx.send(*val).unwrap(); thread::sleep(Duration::from_secs(1)); } }); } fn main() { let (tx, rx) = mpsc::channel(); let queue = Queue::new(); let queue_length = queue.length; send_tx(queue, tx); let mut total_received: u32 = 0; for received in rx { println!("Got: {}", received); total_received += 1; } println!("total numbers received: {}", total_received); assert_eq!(total_received, queue_length) }
Variables
In Rust, variables are immutable by default. When a variable is immutable, once a value is bound to a name, you can’t change that value. You can make them mutable by adding mut in front of the variable name.
Further information
Rustlings
variables1
// variables1.rs // Make me compile! // Execute `rustlings hint variables1` or use the `hint` watch subcommand for a hint. // I AM NOT DONE fn main() { x = 5; println!("x has the value {}", x); }
variables2
// variables2.rs // Execute `rustlings hint variables2` or use the `hint` watch subcommand for a hint. // I AM NOT DONE fn main() { let x; if x == 10 { println!("x is ten!"); } else { println!("x is not ten!"); } }
variables3
// variables3.rs // Execute `rustlings hint variables3` or use the `hint` watch subcommand for a hint. // I AM NOT DONE fn main() { let x: i32; println!("Number {}", x); }
variables4
// variables4.rs // Execute `rustlings hint variables4` or use the `hint` watch subcommand for a hint. // I AM NOT DONE fn main() { let x = 3; println!("Number {}", x); x = 5; // don't change this line println!("Number {}", x); }
variables5
// variables5.rs // Execute `rustlings hint variables5` or use the `hint` watch subcommand for a hint. // I AM NOT DONE fn main() { let number = "T-H-R-E-E"; // don't change this line println!("Spell a Number : {}", number); number = 3; // don't rename this variable println!("Number plus two is : {}", number + 2); }
variables6
// variables6.rs // Execute `rustlings hint variables6` or use the `hint` watch subcommand for a hint. // I AM NOT DONE const NUMBER = 3; fn main() { println!("Number {}", NUMBER); }