commit
a3e2bc985c
|
@ -0,0 +1,3 @@
|
|||
Cargo.lock
|
||||
target/
|
||||
.vscode/
|
|
@ -0,0 +1,218 @@
|
|||
bin = [
|
||||
{ name = "intro1", path = "exercises/00_intro/intro1.rs" },
|
||||
{ name = "intro1_sol", path = "solutions/00_intro/intro1.rs" },
|
||||
{ name = "intro2", path = "exercises/00_intro/intro2.rs" },
|
||||
{ name = "intro2_sol", path = "solutions/00_intro/intro2.rs" },
|
||||
{ name = "variables1", path = "exercises/01_variables/variables1.rs" },
|
||||
{ name = "variables1_sol", path = "solutions/01_variables/variables1.rs" },
|
||||
{ name = "variables2", path = "exercises/01_variables/variables2.rs" },
|
||||
{ name = "variables2_sol", path = "solutions/01_variables/variables2.rs" },
|
||||
{ name = "variables3", path = "exercises/01_variables/variables3.rs" },
|
||||
{ name = "variables3_sol", path = "solutions/01_variables/variables3.rs" },
|
||||
{ name = "variables4", path = "exercises/01_variables/variables4.rs" },
|
||||
{ name = "variables4_sol", path = "solutions/01_variables/variables4.rs" },
|
||||
{ name = "variables5", path = "exercises/01_variables/variables5.rs" },
|
||||
{ name = "variables5_sol", path = "solutions/01_variables/variables5.rs" },
|
||||
{ name = "variables6", path = "exercises/01_variables/variables6.rs" },
|
||||
{ name = "variables6_sol", path = "solutions/01_variables/variables6.rs" },
|
||||
{ name = "functions1", path = "exercises/02_functions/functions1.rs" },
|
||||
{ name = "functions1_sol", path = "solutions/02_functions/functions1.rs" },
|
||||
{ name = "functions2", path = "exercises/02_functions/functions2.rs" },
|
||||
{ name = "functions2_sol", path = "solutions/02_functions/functions2.rs" },
|
||||
{ name = "functions3", path = "exercises/02_functions/functions3.rs" },
|
||||
{ name = "functions3_sol", path = "solutions/02_functions/functions3.rs" },
|
||||
{ name = "functions4", path = "exercises/02_functions/functions4.rs" },
|
||||
{ name = "functions4_sol", path = "solutions/02_functions/functions4.rs" },
|
||||
{ name = "functions5", path = "exercises/02_functions/functions5.rs" },
|
||||
{ name = "functions5_sol", path = "solutions/02_functions/functions5.rs" },
|
||||
{ name = "if1", path = "exercises/03_if/if1.rs" },
|
||||
{ name = "if1_sol", path = "solutions/03_if/if1.rs" },
|
||||
{ name = "if2", path = "exercises/03_if/if2.rs" },
|
||||
{ name = "if2_sol", path = "solutions/03_if/if2.rs" },
|
||||
{ name = "if3", path = "exercises/03_if/if3.rs" },
|
||||
{ name = "if3_sol", path = "solutions/03_if/if3.rs" },
|
||||
{ name = "quiz1", path = "exercises/quizzes/quiz1.rs" },
|
||||
{ name = "quiz1_sol", path = "solutions/quizzes/quiz1.rs" },
|
||||
{ name = "primitive_types1", path = "exercises/04_primitive_types/primitive_types1.rs" },
|
||||
{ name = "primitive_types1_sol", path = "solutions/04_primitive_types/primitive_types1.rs" },
|
||||
{ name = "primitive_types2", path = "exercises/04_primitive_types/primitive_types2.rs" },
|
||||
{ name = "primitive_types2_sol", path = "solutions/04_primitive_types/primitive_types2.rs" },
|
||||
{ name = "primitive_types3", path = "exercises/04_primitive_types/primitive_types3.rs" },
|
||||
{ name = "primitive_types3_sol", path = "solutions/04_primitive_types/primitive_types3.rs" },
|
||||
{ name = "primitive_types4", path = "exercises/04_primitive_types/primitive_types4.rs" },
|
||||
{ name = "primitive_types4_sol", path = "solutions/04_primitive_types/primitive_types4.rs" },
|
||||
{ name = "primitive_types5", path = "exercises/04_primitive_types/primitive_types5.rs" },
|
||||
{ name = "primitive_types5_sol", path = "solutions/04_primitive_types/primitive_types5.rs" },
|
||||
{ name = "primitive_types6", path = "exercises/04_primitive_types/primitive_types6.rs" },
|
||||
{ name = "primitive_types6_sol", path = "solutions/04_primitive_types/primitive_types6.rs" },
|
||||
{ name = "vecs1", path = "exercises/05_vecs/vecs1.rs" },
|
||||
{ name = "vecs1_sol", path = "solutions/05_vecs/vecs1.rs" },
|
||||
{ name = "vecs2", path = "exercises/05_vecs/vecs2.rs" },
|
||||
{ name = "vecs2_sol", path = "solutions/05_vecs/vecs2.rs" },
|
||||
{ name = "move_semantics1", path = "exercises/06_move_semantics/move_semantics1.rs" },
|
||||
{ name = "move_semantics1_sol", path = "solutions/06_move_semantics/move_semantics1.rs" },
|
||||
{ name = "move_semantics2", path = "exercises/06_move_semantics/move_semantics2.rs" },
|
||||
{ name = "move_semantics2_sol", path = "solutions/06_move_semantics/move_semantics2.rs" },
|
||||
{ name = "move_semantics3", path = "exercises/06_move_semantics/move_semantics3.rs" },
|
||||
{ name = "move_semantics3_sol", path = "solutions/06_move_semantics/move_semantics3.rs" },
|
||||
{ name = "move_semantics4", path = "exercises/06_move_semantics/move_semantics4.rs" },
|
||||
{ name = "move_semantics4_sol", path = "solutions/06_move_semantics/move_semantics4.rs" },
|
||||
{ name = "move_semantics5", path = "exercises/06_move_semantics/move_semantics5.rs" },
|
||||
{ name = "move_semantics5_sol", path = "solutions/06_move_semantics/move_semantics5.rs" },
|
||||
{ name = "structs1", path = "exercises/07_structs/structs1.rs" },
|
||||
{ name = "structs1_sol", path = "solutions/07_structs/structs1.rs" },
|
||||
{ name = "structs2", path = "exercises/07_structs/structs2.rs" },
|
||||
{ name = "structs2_sol", path = "solutions/07_structs/structs2.rs" },
|
||||
{ name = "structs3", path = "exercises/07_structs/structs3.rs" },
|
||||
{ name = "structs3_sol", path = "solutions/07_structs/structs3.rs" },
|
||||
{ name = "enums1", path = "exercises/08_enums/enums1.rs" },
|
||||
{ name = "enums1_sol", path = "solutions/08_enums/enums1.rs" },
|
||||
{ name = "enums2", path = "exercises/08_enums/enums2.rs" },
|
||||
{ name = "enums2_sol", path = "solutions/08_enums/enums2.rs" },
|
||||
{ name = "enums3", path = "exercises/08_enums/enums3.rs" },
|
||||
{ name = "enums3_sol", path = "solutions/08_enums/enums3.rs" },
|
||||
{ name = "strings1", path = "exercises/09_strings/strings1.rs" },
|
||||
{ name = "strings1_sol", path = "solutions/09_strings/strings1.rs" },
|
||||
{ name = "strings2", path = "exercises/09_strings/strings2.rs" },
|
||||
{ name = "strings2_sol", path = "solutions/09_strings/strings2.rs" },
|
||||
{ name = "strings3", path = "exercises/09_strings/strings3.rs" },
|
||||
{ name = "strings3_sol", path = "solutions/09_strings/strings3.rs" },
|
||||
{ name = "strings4", path = "exercises/09_strings/strings4.rs" },
|
||||
{ name = "strings4_sol", path = "solutions/09_strings/strings4.rs" },
|
||||
{ name = "modules1", path = "exercises/10_modules/modules1.rs" },
|
||||
{ name = "modules1_sol", path = "solutions/10_modules/modules1.rs" },
|
||||
{ name = "modules2", path = "exercises/10_modules/modules2.rs" },
|
||||
{ name = "modules2_sol", path = "solutions/10_modules/modules2.rs" },
|
||||
{ name = "modules3", path = "exercises/10_modules/modules3.rs" },
|
||||
{ name = "modules3_sol", path = "solutions/10_modules/modules3.rs" },
|
||||
{ name = "hashmaps1", path = "exercises/11_hashmaps/hashmaps1.rs" },
|
||||
{ name = "hashmaps1_sol", path = "solutions/11_hashmaps/hashmaps1.rs" },
|
||||
{ name = "hashmaps2", path = "exercises/11_hashmaps/hashmaps2.rs" },
|
||||
{ name = "hashmaps2_sol", path = "solutions/11_hashmaps/hashmaps2.rs" },
|
||||
{ name = "hashmaps3", path = "exercises/11_hashmaps/hashmaps3.rs" },
|
||||
{ name = "hashmaps3_sol", path = "solutions/11_hashmaps/hashmaps3.rs" },
|
||||
{ name = "quiz2", path = "exercises/quizzes/quiz2.rs" },
|
||||
{ name = "quiz2_sol", path = "solutions/quizzes/quiz2.rs" },
|
||||
{ name = "options1", path = "exercises/12_options/options1.rs" },
|
||||
{ name = "options1_sol", path = "solutions/12_options/options1.rs" },
|
||||
{ name = "options2", path = "exercises/12_options/options2.rs" },
|
||||
{ name = "options2_sol", path = "solutions/12_options/options2.rs" },
|
||||
{ name = "options3", path = "exercises/12_options/options3.rs" },
|
||||
{ name = "options3_sol", path = "solutions/12_options/options3.rs" },
|
||||
{ name = "errors1", path = "exercises/13_error_handling/errors1.rs" },
|
||||
{ name = "errors1_sol", path = "solutions/13_error_handling/errors1.rs" },
|
||||
{ name = "errors2", path = "exercises/13_error_handling/errors2.rs" },
|
||||
{ name = "errors2_sol", path = "solutions/13_error_handling/errors2.rs" },
|
||||
{ name = "errors3", path = "exercises/13_error_handling/errors3.rs" },
|
||||
{ name = "errors3_sol", path = "solutions/13_error_handling/errors3.rs" },
|
||||
{ name = "errors4", path = "exercises/13_error_handling/errors4.rs" },
|
||||
{ name = "errors4_sol", path = "solutions/13_error_handling/errors4.rs" },
|
||||
{ name = "errors5", path = "exercises/13_error_handling/errors5.rs" },
|
||||
{ name = "errors5_sol", path = "solutions/13_error_handling/errors5.rs" },
|
||||
{ name = "errors6", path = "exercises/13_error_handling/errors6.rs" },
|
||||
{ name = "errors6_sol", path = "solutions/13_error_handling/errors6.rs" },
|
||||
{ name = "generics1", path = "exercises/14_generics/generics1.rs" },
|
||||
{ name = "generics1_sol", path = "solutions/14_generics/generics1.rs" },
|
||||
{ name = "generics2", path = "exercises/14_generics/generics2.rs" },
|
||||
{ name = "generics2_sol", path = "solutions/14_generics/generics2.rs" },
|
||||
{ name = "traits1", path = "exercises/15_traits/traits1.rs" },
|
||||
{ name = "traits1_sol", path = "solutions/15_traits/traits1.rs" },
|
||||
{ name = "traits2", path = "exercises/15_traits/traits2.rs" },
|
||||
{ name = "traits2_sol", path = "solutions/15_traits/traits2.rs" },
|
||||
{ name = "traits3", path = "exercises/15_traits/traits3.rs" },
|
||||
{ name = "traits3_sol", path = "solutions/15_traits/traits3.rs" },
|
||||
{ name = "traits4", path = "exercises/15_traits/traits4.rs" },
|
||||
{ name = "traits4_sol", path = "solutions/15_traits/traits4.rs" },
|
||||
{ name = "traits5", path = "exercises/15_traits/traits5.rs" },
|
||||
{ name = "traits5_sol", path = "solutions/15_traits/traits5.rs" },
|
||||
{ name = "quiz3", path = "exercises/quizzes/quiz3.rs" },
|
||||
{ name = "quiz3_sol", path = "solutions/quizzes/quiz3.rs" },
|
||||
{ name = "lifetimes1", path = "exercises/16_lifetimes/lifetimes1.rs" },
|
||||
{ name = "lifetimes1_sol", path = "solutions/16_lifetimes/lifetimes1.rs" },
|
||||
{ name = "lifetimes2", path = "exercises/16_lifetimes/lifetimes2.rs" },
|
||||
{ name = "lifetimes2_sol", path = "solutions/16_lifetimes/lifetimes2.rs" },
|
||||
{ name = "lifetimes3", path = "exercises/16_lifetimes/lifetimes3.rs" },
|
||||
{ name = "lifetimes3_sol", path = "solutions/16_lifetimes/lifetimes3.rs" },
|
||||
{ name = "tests1", path = "exercises/17_tests/tests1.rs" },
|
||||
{ name = "tests1_sol", path = "solutions/17_tests/tests1.rs" },
|
||||
{ name = "tests2", path = "exercises/17_tests/tests2.rs" },
|
||||
{ name = "tests2_sol", path = "solutions/17_tests/tests2.rs" },
|
||||
{ name = "tests3", path = "exercises/17_tests/tests3.rs" },
|
||||
{ name = "tests3_sol", path = "solutions/17_tests/tests3.rs" },
|
||||
{ name = "iterators1", path = "exercises/18_iterators/iterators1.rs" },
|
||||
{ name = "iterators1_sol", path = "solutions/18_iterators/iterators1.rs" },
|
||||
{ name = "iterators2", path = "exercises/18_iterators/iterators2.rs" },
|
||||
{ name = "iterators2_sol", path = "solutions/18_iterators/iterators2.rs" },
|
||||
{ name = "iterators3", path = "exercises/18_iterators/iterators3.rs" },
|
||||
{ name = "iterators3_sol", path = "solutions/18_iterators/iterators3.rs" },
|
||||
{ name = "iterators4", path = "exercises/18_iterators/iterators4.rs" },
|
||||
{ name = "iterators4_sol", path = "solutions/18_iterators/iterators4.rs" },
|
||||
{ name = "iterators5", path = "exercises/18_iterators/iterators5.rs" },
|
||||
{ name = "iterators5_sol", path = "solutions/18_iterators/iterators5.rs" },
|
||||
{ name = "box1", path = "exercises/19_smart_pointers/box1.rs" },
|
||||
{ name = "box1_sol", path = "solutions/19_smart_pointers/box1.rs" },
|
||||
{ name = "rc1", path = "exercises/19_smart_pointers/rc1.rs" },
|
||||
{ name = "rc1_sol", path = "solutions/19_smart_pointers/rc1.rs" },
|
||||
{ name = "arc1", path = "exercises/19_smart_pointers/arc1.rs" },
|
||||
{ name = "arc1_sol", path = "solutions/19_smart_pointers/arc1.rs" },
|
||||
{ name = "cow1", path = "exercises/19_smart_pointers/cow1.rs" },
|
||||
{ name = "cow1_sol", path = "solutions/19_smart_pointers/cow1.rs" },
|
||||
{ name = "threads1", path = "exercises/20_threads/threads1.rs" },
|
||||
{ name = "threads1_sol", path = "solutions/20_threads/threads1.rs" },
|
||||
{ name = "threads2", path = "exercises/20_threads/threads2.rs" },
|
||||
{ name = "threads2_sol", path = "solutions/20_threads/threads2.rs" },
|
||||
{ name = "threads3", path = "exercises/20_threads/threads3.rs" },
|
||||
{ name = "threads3_sol", path = "solutions/20_threads/threads3.rs" },
|
||||
{ name = "macros1", path = "exercises/21_macros/macros1.rs" },
|
||||
{ name = "macros1_sol", path = "solutions/21_macros/macros1.rs" },
|
||||
{ name = "macros2", path = "exercises/21_macros/macros2.rs" },
|
||||
{ name = "macros2_sol", path = "solutions/21_macros/macros2.rs" },
|
||||
{ name = "macros3", path = "exercises/21_macros/macros3.rs" },
|
||||
{ name = "macros3_sol", path = "solutions/21_macros/macros3.rs" },
|
||||
{ name = "macros4", path = "exercises/21_macros/macros4.rs" },
|
||||
{ name = "macros4_sol", path = "solutions/21_macros/macros4.rs" },
|
||||
{ name = "clippy1", path = "exercises/22_clippy/clippy1.rs" },
|
||||
{ name = "clippy1_sol", path = "solutions/22_clippy/clippy1.rs" },
|
||||
{ name = "clippy2", path = "exercises/22_clippy/clippy2.rs" },
|
||||
{ name = "clippy2_sol", path = "solutions/22_clippy/clippy2.rs" },
|
||||
{ name = "clippy3", path = "exercises/22_clippy/clippy3.rs" },
|
||||
{ name = "clippy3_sol", path = "solutions/22_clippy/clippy3.rs" },
|
||||
{ name = "using_as", path = "exercises/23_conversions/using_as.rs" },
|
||||
{ name = "using_as_sol", path = "solutions/23_conversions/using_as.rs" },
|
||||
{ name = "from_into", path = "exercises/23_conversions/from_into.rs" },
|
||||
{ name = "from_into_sol", path = "solutions/23_conversions/from_into.rs" },
|
||||
{ name = "from_str", path = "exercises/23_conversions/from_str.rs" },
|
||||
{ name = "from_str_sol", path = "solutions/23_conversions/from_str.rs" },
|
||||
{ name = "try_from_into", path = "exercises/23_conversions/try_from_into.rs" },
|
||||
{ name = "try_from_into_sol", path = "solutions/23_conversions/try_from_into.rs" },
|
||||
{ name = "as_ref_mut", path = "exercises/23_conversions/as_ref_mut.rs" },
|
||||
{ name = "as_ref_mut_sol", path = "solutions/23_conversions/as_ref_mut.rs" },
|
||||
]
|
||||
|
||||
[package]
|
||||
name = "exercises"
|
||||
edition = "2021"
|
||||
# Don't publish the exercises on crates.io!
|
||||
publish = false
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
|
||||
[profile.dev]
|
||||
panic = "abort"
|
||||
|
||||
[lints.rust]
|
||||
# You shouldn't write unsafe code in Rustlings
|
||||
unsafe_code = "forbid"
|
||||
# You don't need unstable features in Rustlings and shouldn't rely on them while learning Rust
|
||||
unstable_features = "forbid"
|
||||
|
||||
[lints.clippy]
|
||||
# You forgot a `todo!()`
|
||||
todo = "forbid"
|
||||
# This can only happen by mistake in Rustlings
|
||||
empty_loop = "forbid"
|
||||
# No infinite loops are needed in Rustlings
|
||||
infinite_loop = "deny"
|
||||
# You shouldn't leak memory while still learning Rust
|
||||
mem_forget = "deny"
|
|
@ -0,0 +1,8 @@
|
|||
# Intro
|
||||
|
||||
Rust uses the `print!` and `println!` macros to print text to the console.
|
||||
|
||||
## Further information
|
||||
|
||||
- [Hello World](https://doc.rust-lang.org/rust-by-example/hello.html)
|
||||
- [Formatted print](https://doc.rust-lang.org/rust-by-example/hello/print.html)
|
|
@ -0,0 +1,24 @@
|
|||
// TODO: We sometimes encourage you to keep trying things on a given exercise
|
||||
// even after you already figured it out. If you got everything working and feel
|
||||
// ready for the next exercise, enter `n` in the terminal.
|
||||
//
|
||||
// The exercise file will be reloaded when you change one of the lines below!
|
||||
// Try adding a new `println!` and check the updated output in the terminal.
|
||||
|
||||
fn main() {
|
||||
println!(r#" Welcome to... "#);
|
||||
println!(r#" _ _ _ "#);
|
||||
println!(r#" _ __ _ _ ___| |_| (_)_ __ __ _ ___ "#);
|
||||
println!(r#" | '__| | | / __| __| | | '_ \ / _` / __| "#);
|
||||
println!(r#" | | | |_| \__ \ |_| | | | | | (_| \__ \ "#);
|
||||
println!(r#" |_| \__,_|___/\__|_|_|_| |_|\__, |___/ "#);
|
||||
println!(r#" |___/ "#);
|
||||
println!();
|
||||
println!("This exercise compiles successfully. The remaining exercises contain a compiler");
|
||||
println!("or logic error. The central concept behind Rustlings is to fix these errors and");
|
||||
println!("solve the exercises. Good luck!");
|
||||
println!();
|
||||
println!("The file of this exercise is `exercises/00_intro/intro1.rs`. Have a look!");
|
||||
println!("The current exercise path will be always shown under the progress bar.");
|
||||
println!("You can click on the path to open the exercise file in your editor.");
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
fn main() {
|
||||
// TODO: Fix the code to print "Hello world!".
|
||||
printline!("Hello world!");
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
# 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
|
||||
|
||||
- [Variables and Mutability](https://doc.rust-lang.org/book/ch03-01-variables-and-mutability.html)
|
|
@ -0,0 +1,6 @@
|
|||
fn main() {
|
||||
// TODO: Add the missing keyword.
|
||||
x = 5;
|
||||
|
||||
println!("x has the value {x}");
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
fn main() {
|
||||
// TODO: Change the line below to fix the compiler error.
|
||||
let x;
|
||||
|
||||
if x == 10 {
|
||||
println!("x is ten!");
|
||||
} else {
|
||||
println!("x is not ten!");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
fn main() {
|
||||
// TODO: Change the line below to fix the compiler error.
|
||||
let x: i32;
|
||||
|
||||
println!("Number {x}");
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
// TODO: Fix the compiler error.
|
||||
fn main() {
|
||||
let x = 3;
|
||||
println!("Number {x}");
|
||||
|
||||
x = 5; // Don't change this line
|
||||
println!("Number {x}");
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
fn main() {
|
||||
let number = "T-H-R-E-E"; // Don't change this line
|
||||
println!("Spell a number: {}", number);
|
||||
|
||||
// TODO: Fix the compiler error by changing the line below without renaming the variable.
|
||||
number = 3;
|
||||
println!("Number plus two is: {}", number + 2);
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
// TODO: Change the line below to fix the compiler error.
|
||||
const NUMBER = 3;
|
||||
|
||||
fn main() {
|
||||
println!("Number: {NUMBER}");
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
# 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
|
||||
|
||||
- [How Functions Work](https://doc.rust-lang.org/book/ch03-03-how-functions-work.html)
|
|
@ -0,0 +1,5 @@
|
|||
// TODO: Add some function with the name `call_me` without arguments or a return value.
|
||||
|
||||
fn main() {
|
||||
call_me(); // Don't change this line
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
// TODO: Add the missing type of the argument `num` after the colon `:`.
|
||||
fn call_me(num:) {
|
||||
for i in 0..num {
|
||||
println!("Ring! Call number {}", i + 1);
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
call_me(3);
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
fn call_me(num: u8) {
|
||||
for i in 0..num {
|
||||
println!("Ring! Call number {}", i + 1);
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// TODO: Fix the function call.
|
||||
call_me();
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
// 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 are only interested in
|
||||
// the signatures for now.
|
||||
|
||||
fn is_even(num: i64) -> bool {
|
||||
num % 2 == 0
|
||||
}
|
||||
|
||||
// TODO: Fix the function signature.
|
||||
fn sale_price(price: i64) -> {
|
||||
if is_even(price) {
|
||||
price - 10
|
||||
} else {
|
||||
price - 3
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let original_price = 51;
|
||||
println!("Your sale price is {}", sale_price(original_price));
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
// TODO: Fix the function body without changing the signature.
|
||||
fn square(num: i32) -> i32 {
|
||||
num * num;
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let answer = square(3);
|
||||
println!("The square of 3 is {answer}");
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
# If
|
||||
|
||||
`if`, the most basic (but still surprisingly versatile!) type of control flow, is what you'll learn here.
|
||||
|
||||
## Further information
|
||||
|
||||
- [Control Flow - if expressions](https://doc.rust-lang.org/book/ch03-05-control-flow.html#if-expressions)
|
|
@ -0,0 +1,32 @@
|
|||
fn bigger(a: i32, b: i32) -> i32 {
|
||||
// TODO: Complete this function to return the bigger number!
|
||||
// If both numbers are equal, any of them can be returned.
|
||||
// Do not use:
|
||||
// - another function call
|
||||
// - additional variables
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// You can optionally experiment here.
|
||||
}
|
||||
|
||||
// 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));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn equal_numbers() {
|
||||
assert_eq!(42, bigger(42, 42));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
// TODO: Fix the compiler error on this function.
|
||||
fn foo_if_fizz(fizzish: &str) -> &str {
|
||||
if fizzish == "fizz" {
|
||||
"foo"
|
||||
} else {
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// You can optionally experiment here.
|
||||
}
|
||||
|
||||
// TODO: Read the tests to understand the desired behavior.
|
||||
// Make all tests pass without changing them.
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn foo_for_fizz() {
|
||||
// This means that calling `foo_if_fizz` with the argument "fizz" should return "foo".
|
||||
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");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
fn animal_habitat(animal: &str) -> &str {
|
||||
// TODO: Fix the compiler error in the statement below.
|
||||
let identifier = if animal == "crab" {
|
||||
1
|
||||
} else if animal == "gopher" {
|
||||
2.0
|
||||
} else if animal == "snake" {
|
||||
3
|
||||
} else {
|
||||
"Unknown"
|
||||
};
|
||||
|
||||
// Don't change the expression below!
|
||||
if identifier == 1 {
|
||||
"Beach"
|
||||
} else if identifier == 2 {
|
||||
"Burrow"
|
||||
} else if identifier == 3 {
|
||||
"Desert"
|
||||
} else {
|
||||
"Unknown"
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// You can optionally experiment here.
|
||||
}
|
||||
|
||||
// Don't change the tests!
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn gopher_lives_in_burrow() {
|
||||
assert_eq!(animal_habitat("gopher"), "Burrow")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn snake_lives_in_desert() {
|
||||
assert_eq!(animal_habitat("snake"), "Desert")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn crab_lives_on_beach() {
|
||||
assert_eq!(animal_habitat("crab"), "Beach")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unknown_animal() {
|
||||
assert_eq!(animal_habitat("dinosaur"), "Unknown")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
# 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
|
||||
|
||||
- [Data Types](https://doc.rust-lang.org/book/ch03-02-data-types.html)
|
||||
- [The Slice Type](https://doc.rust-lang.org/book/ch04-03-slices.html)
|
|
@ -0,0 +1,15 @@
|
|||
// Booleans (`bool`)
|
||||
|
||||
fn main() {
|
||||
let is_morning = true;
|
||||
if is_morning {
|
||||
println!("Good morning!");
|
||||
}
|
||||
|
||||
// TODO: Define a boolean variable with the name `is_evening` before the `if` statement below.
|
||||
// The value of the variable should be the negation (opposite) of `is_morning`.
|
||||
// let …
|
||||
if is_evening {
|
||||
println!("Good evening!");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
// Characters (`char`)
|
||||
|
||||
fn main() {
|
||||
// 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!");
|
||||
}
|
||||
|
||||
// TODO: Analogous to the example before, declare a variable called `your_character`
|
||||
// below with your favorite character.
|
||||
// Try a letter, try a digit (in single quotes), try a special character, try a character
|
||||
// from a different language than your own, try an emoji 😉
|
||||
// let your_character = '';
|
||||
|
||||
if your_character.is_alphabetic() {
|
||||
println!("Alphabetical!");
|
||||
} else if your_character.is_numeric() {
|
||||
println!("Numerical!");
|
||||
} else {
|
||||
println!("Neither alphabetic nor numeric!");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fn main() {
|
||||
// TODO: Create an array called `a` with at least 100 elements in it.
|
||||
// let a = ???
|
||||
|
||||
if a.len() >= 100 {
|
||||
println!("Wow, that's a big array!");
|
||||
} else {
|
||||
println!("Meh, I eat arrays like that for breakfast.");
|
||||
panic!("Array not big enough, more elements needed");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
fn main() {
|
||||
// You can optionally experiment here.
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn slice_out_of_array() {
|
||||
let a = [1, 2, 3, 4, 5];
|
||||
|
||||
// TODO: Get a slice called `nice_slice` out of the array `a` so that the test passes.
|
||||
// let nice_slice = ???
|
||||
|
||||
assert_eq!([2, 3, 4], nice_slice);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
fn main() {
|
||||
let cat = ("Furry McFurson", 3.5);
|
||||
|
||||
// TODO: Destructure the `cat` tuple in one statement so that the println works.
|
||||
// let /* your pattern here */ = cat;
|
||||
|
||||
println!("{name} is {age} years old");
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
fn main() {
|
||||
// You can optionally experiment here.
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn indexing_tuple() {
|
||||
let numbers = (1, 2, 3);
|
||||
|
||||
// TODO: Use a tuple index to access the second element of `numbers`
|
||||
// and assign it to a variable called `second`.
|
||||
// let second = ???;
|
||||
|
||||
assert_eq!(second, 2, "This is not the 2nd number in the tuple!");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
# 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
|
||||
|
||||
- [Storing Lists of Values with Vectors](https://doc.rust-lang.org/book/ch08-01-vectors.html)
|
||||
- [`iter_mut`](https://doc.rust-lang.org/std/primitive.slice.html#method.iter_mut)
|
||||
- [`map`](https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.map)
|
|
@ -0,0 +1,24 @@
|
|||
fn array_and_vec() -> ([i32; 4], Vec<i32>) {
|
||||
let a = [10, 20, 30, 40]; // Array
|
||||
|
||||
// TODO: Create a vector called `v` which contains the exact same elements as in the array `a`.
|
||||
// Use the vector macro.
|
||||
// let v = ???;
|
||||
|
||||
(a, v)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// You can optionally experiment here.
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_array_and_vec_similarity() {
|
||||
let (a, v) = array_and_vec();
|
||||
assert_eq!(a, *v);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
fn vec_loop(input: &[i32]) -> Vec<i32> {
|
||||
let mut output = Vec::new();
|
||||
|
||||
for element in input {
|
||||
// TODO: Multiply each element in the `input` slice by 2 and push it to
|
||||
// the `output` vector.
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
fn vec_map_example(input: &[i32]) -> Vec<i32> {
|
||||
// An example of collecting a vector after mapping.
|
||||
// We map each element of the `input` slice to its value plus 1.
|
||||
// If the input is `[1, 2, 3]`, the output is `[2, 3, 4]`.
|
||||
input.iter().map(|element| element + 1).collect()
|
||||
}
|
||||
|
||||
fn vec_map(input: &[i32]) -> Vec<i32> {
|
||||
// TODO: Here, we also want to multiply each element in the `input` slice
|
||||
// by 2, but with iterator mapping instead of manually pushing into an empty
|
||||
// vector.
|
||||
// See the example in the function `vec_map_example` above.
|
||||
input
|
||||
.iter()
|
||||
.map(|element| {
|
||||
// ???
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// You can optionally experiment here.
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_vec_loop() {
|
||||
let input = [2, 4, 6, 8, 10];
|
||||
let ans = vec_loop(&input);
|
||||
assert_eq!(ans, [4, 8, 12, 16, 20]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_vec_map_example() {
|
||||
let input = [1, 2, 3];
|
||||
let ans = vec_map_example(&input);
|
||||
assert_eq!(ans, [2, 3, 4]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_vec_map() {
|
||||
let input = [2, 4, 6, 8, 10];
|
||||
let ans = vec_map(&input);
|
||||
assert_eq!(ans, [4, 8, 12, 16, 20]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
# Move Semantics
|
||||
|
||||
These exercises are adapted from [pnkfelix](https://github.com/pnkfelix)'s [Rust Tutorial](https://pnkfelix.github.io/rust-examples-icfp2014/) -- Thank you Felix!!!
|
||||
|
||||
## Further information
|
||||
|
||||
For this section, the book links are especially important.
|
||||
|
||||
- [Ownership](https://doc.rust-lang.org/book/ch04-01-what-is-ownership.html)
|
||||
- [Reference and borrowing](https://doc.rust-lang.org/book/ch04-02-references-and-borrowing.html)
|
|
@ -0,0 +1,24 @@
|
|||
// TODO: Fix the compiler error in this function.
|
||||
fn fill_vec(vec: Vec<i32>) -> Vec<i32> {
|
||||
let vec = vec;
|
||||
|
||||
vec.push(88);
|
||||
|
||||
vec
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// You can optionally experiment here.
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn move_semantics1() {
|
||||
let vec0 = vec![22, 44, 66];
|
||||
let vec1 = fill_vec(vec0);
|
||||
assert_eq!(vec1, vec![22, 44, 66, 88]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
fn fill_vec(vec: Vec<i32>) -> Vec<i32> {
|
||||
let mut vec = vec;
|
||||
|
||||
vec.push(88);
|
||||
|
||||
vec
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// You can optionally experiment here.
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
// TODO: Make both vectors `vec0` and `vec1` accessible at the same time to
|
||||
// fix the compiler error in the test.
|
||||
#[test]
|
||||
fn move_semantics2() {
|
||||
let vec0 = vec![22, 44, 66];
|
||||
|
||||
let vec1 = fill_vec(vec0);
|
||||
|
||||
assert_eq!(vec0, [22, 44, 66]);
|
||||
assert_eq!(vec1, [22, 44, 66, 88]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
// TODO: Fix the compiler error in the function without adding any new line.
|
||||
fn fill_vec(vec: Vec<i32>) -> Vec<i32> {
|
||||
vec.push(88);
|
||||
|
||||
vec
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// You can optionally experiment here.
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn move_semantics3() {
|
||||
let vec0 = vec![22, 44, 66];
|
||||
let vec1 = fill_vec(vec0);
|
||||
assert_eq!(vec1, [22, 44, 66, 88]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
fn main() {
|
||||
// You can optionally experiment here.
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
// TODO: Fix the compiler errors only by reordering the lines in the test.
|
||||
// Don't add, change or remove any line.
|
||||
#[test]
|
||||
fn move_semantics4() {
|
||||
let mut x = Vec::new();
|
||||
let y = &mut x;
|
||||
let z = &mut x;
|
||||
y.push(42);
|
||||
z.push(13);
|
||||
assert_eq!(x, [42, 13]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
#![allow(clippy::ptr_arg)]
|
||||
|
||||
// TODO: Fix the compiler errors without changing anything except adding or
|
||||
// removing references (the character `&`).
|
||||
|
||||
// Shouldn't 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}");
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let data = "Rust is great!".to_string();
|
||||
|
||||
get_char(data);
|
||||
|
||||
string_uppercase(&data);
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
# Structs
|
||||
|
||||
Rust has three struct types: a classic C struct, a tuple struct, and a unit struct.
|
||||
|
||||
## Further information
|
||||
|
||||
- [Structures](https://doc.rust-lang.org/book/ch05-01-defining-structs.html)
|
||||
- [Method Syntax](https://doc.rust-lang.org/book/ch05-03-method-syntax.html)
|
|
@ -0,0 +1,47 @@
|
|||
struct ColorRegularStruct {
|
||||
// TODO: Add the fields that the test `regular_structs` expects.
|
||||
// What types should the fields have? What are the minimum and maximum values for RGB colors?
|
||||
}
|
||||
|
||||
struct ColorTupleStruct(/* TODO: Add the fields that the test `tuple_structs` expects */);
|
||||
|
||||
#[derive(Debug)]
|
||||
struct UnitStruct;
|
||||
|
||||
fn main() {
|
||||
// You can optionally experiment here.
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn regular_structs() {
|
||||
// TODO: Instantiate a regular 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 struct.
|
||||
// let unit_struct =
|
||||
let message = format!("{unit_struct:?}s are fun!");
|
||||
|
||||
assert_eq!(message, "UnitStructs are fun!");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
#[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,
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// You can optionally experiment here.
|
||||
}
|
||||
|
||||
#[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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
// 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.
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Package {
|
||||
sender_country: String,
|
||||
recipient_country: String,
|
||||
weight_in_grams: u32,
|
||||
}
|
||||
|
||||
impl Package {
|
||||
fn new(sender_country: String, recipient_country: String, weight_in_grams: u32) -> Self {
|
||||
if weight_in_grams < 10 {
|
||||
// This isn't how you should handle errors in Rust, but we will
|
||||
// learn about error handling later.
|
||||
panic!("Can't ship a package with weight below 10 grams");
|
||||
}
|
||||
|
||||
Self {
|
||||
sender_country,
|
||||
recipient_country,
|
||||
weight_in_grams,
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Add the correct return type to the function signature.
|
||||
fn is_international(&self) {
|
||||
// TODO: Read the tests that use this method to find out when a package
|
||||
// is considered international.
|
||||
}
|
||||
|
||||
// TODO: Add the correct return type to the function signature.
|
||||
fn get_fees(&self, cents_per_gram: u32) {
|
||||
// TODO: Calculate the package's fees.
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// You can optionally experiment 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, 5);
|
||||
}
|
||||
|
||||
#[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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
# 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
|
||||
|
||||
- [Enums](https://doc.rust-lang.org/book/ch06-00-enums.html)
|
||||
- [Pattern syntax](https://doc.rust-lang.org/book/ch18-03-pattern-syntax.html)
|
|
@ -0,0 +1,12 @@
|
|||
#[derive(Debug)]
|
||||
enum Message {
|
||||
// TODO: Define a few types of messages as used below.
|
||||
}
|
||||
|
||||
fn main() {
|
||||
println!("{:?}", Message::Resize);
|
||||
println!("{:?}", Message::Move);
|
||||
println!("{:?}", Message::Echo);
|
||||
println!("{:?}", Message::ChangeColor);
|
||||
println!("{:?}", Message::Quit);
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
#![allow(dead_code)]
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Point {
|
||||
x: u64,
|
||||
y: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Message {
|
||||
// TODO: Define the different variants used below.
|
||||
}
|
||||
|
||||
impl Message {
|
||||
fn call(&self) {
|
||||
println!("{self:?}");
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let messages = [
|
||||
Message::Resize {
|
||||
width: 10,
|
||||
height: 30,
|
||||
},
|
||||
Message::Move(Point { x: 10, y: 15 }),
|
||||
Message::Echo(String::from("hello world")),
|
||||
Message::ChangeColor(200, 255, 255),
|
||||
Message::Quit,
|
||||
];
|
||||
|
||||
for message in &messages {
|
||||
message.call();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
struct Point {
|
||||
x: u64,
|
||||
y: u64,
|
||||
}
|
||||
|
||||
enum Message {
|
||||
// TODO: Implement the message variant types based on their usage below.
|
||||
}
|
||||
|
||||
struct State {
|
||||
width: u64,
|
||||
height: u64,
|
||||
position: Point,
|
||||
message: String,
|
||||
// RGB color composed of red, green and blue.
|
||||
color: (u8, u8, u8),
|
||||
quit: bool,
|
||||
}
|
||||
|
||||
impl State {
|
||||
fn resize(&mut self, width: u64, height: u64) {
|
||||
self.width = width;
|
||||
self.height = height;
|
||||
}
|
||||
|
||||
fn move_position(&mut self, point: Point) {
|
||||
self.position = point;
|
||||
}
|
||||
|
||||
fn echo(&mut self, s: String) {
|
||||
self.message = s;
|
||||
}
|
||||
|
||||
fn change_color(&mut self, red: u8, green: u8, blue: u8) {
|
||||
self.color = (red, green, blue);
|
||||
}
|
||||
|
||||
fn quit(&mut self) {
|
||||
self.quit = true;
|
||||
}
|
||||
|
||||
fn process(&mut self, message: Message) {
|
||||
// TODO: Create a match expression to process the different message
|
||||
// variants using the methods defined above.
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// You can optionally experiment here.
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_match_message_call() {
|
||||
let mut state = State {
|
||||
width: 0,
|
||||
height: 0,
|
||||
position: Point { x: 0, y: 0 },
|
||||
message: String::from("hello world"),
|
||||
color: (0, 0, 0),
|
||||
quit: false,
|
||||
};
|
||||
|
||||
state.process(Message::Resize {
|
||||
width: 10,
|
||||
height: 30,
|
||||
});
|
||||
state.process(Message::Move(Point { x: 10, y: 15 }));
|
||||
state.process(Message::Echo(String::from("Hello world!")));
|
||||
state.process(Message::ChangeColor(255, 0, 255));
|
||||
state.process(Message::Quit);
|
||||
|
||||
assert_eq!(state.width, 10);
|
||||
assert_eq!(state.height, 30);
|
||||
assert_eq!(state.position.x, 10);
|
||||
assert_eq!(state.position.y, 15);
|
||||
assert_eq!(state.message, "Hello world!");
|
||||
assert_eq!(state.color, (255, 0, 255));
|
||||
assert!(state.quit);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
# Strings
|
||||
|
||||
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](https://doc.rust-lang.org/book/ch08-02-strings.html)
|
|
@ -0,0 +1,9 @@
|
|||
// TODO: Fix the compiler error without changing the function signature.
|
||||
fn current_favorite_color() -> String {
|
||||
"blue"
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let answer = current_favorite_color();
|
||||
println!("My current favorite color is {answer}");
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
// TODO: Fix the compiler error in the `main` function without changing this function.
|
||||
fn is_a_color_word(attempt: &str) -> bool {
|
||||
attempt == "green" || attempt == "blue" || attempt == "red"
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let word = String::from("green"); // Don't change 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.");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
fn trim_me(input: &str) -> &str {
|
||||
// TODO: Remove whitespace from both ends of a string.
|
||||
}
|
||||
|
||||
fn compose_me(input: &str) -> String {
|
||||
// TODO: Add " world!" to the string! There are multiple ways to do this.
|
||||
}
|
||||
|
||||
fn replace_me(input: &str) -> String {
|
||||
// TODO: Replace "cars" in the string with "balloons".
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// You can optionally experiment here.
|
||||
}
|
||||
|
||||
#[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",
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
// Calls of this function should be replaced with calls of `string_slice` or `string`.
|
||||
fn placeholder() {}
|
||||
|
||||
fn string_slice(arg: &str) {
|
||||
println!("{arg}");
|
||||
}
|
||||
|
||||
fn string(arg: String) {
|
||||
println!("{arg}");
|
||||
}
|
||||
|
||||
// TODO: Here are a bunch of values - some are `String`, some are `&str`.
|
||||
// Your task is to replace `placeholder(…)` with either `string_slice(…)`
|
||||
// or `string(…)` depending on what you think each value is.
|
||||
fn main() {
|
||||
placeholder("blue");
|
||||
|
||||
placeholder("red".to_string());
|
||||
|
||||
placeholder(String::from("hi"));
|
||||
|
||||
placeholder("rust is fun!".to_owned());
|
||||
|
||||
placeholder("nice weather".into());
|
||||
|
||||
placeholder(format!("Interpolation {}", "Station"));
|
||||
|
||||
// WARNING: This is byte indexing, not character indexing.
|
||||
// Character indexing can be done using `s.chars().nth(INDEX)`.
|
||||
placeholder(&String::from("abc")[0..1]);
|
||||
|
||||
placeholder(" hello there ".trim());
|
||||
|
||||
placeholder("Happy Monday!".replace("Mon", "Tues"));
|
||||
|
||||
placeholder("mY sHiFt KeY iS sTiCkY".to_lowercase());
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
# Modules
|
||||
|
||||
In this section we'll give you an introduction to Rust's module system.
|
||||
|
||||
## Further information
|
||||
|
||||
- [The Module System](https://doc.rust-lang.org/book/ch07-00-managing-growing-projects-with-packages-crates-and-modules.html)
|
|
@ -0,0 +1,16 @@
|
|||
// TODO: Fix the compiler error about calling a private function.
|
||||
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();
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
// You can bring module paths into scopes and provide new names for them with
|
||||
// the `use` and `as` keywords.
|
||||
|
||||
#[allow(dead_code)]
|
||||
mod delicious_snacks {
|
||||
// TODO: Add the following two `use` statements after fixing them.
|
||||
// use self::fruits::PEAR as ???;
|
||||
// use self::veggies::CUCUMBER as ???;
|
||||
|
||||
mod fruits {
|
||||
pub const PEAR: &str = "Pear";
|
||||
pub const APPLE: &str = "Apple";
|
||||
}
|
||||
|
||||
mod veggies {
|
||||
pub const CUCUMBER: &str = "Cucumber";
|
||||
pub const CARROT: &str = "Carrot";
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
println!(
|
||||
"favorite snacks: {} and {}",
|
||||
delicious_snacks::fruit,
|
||||
delicious_snacks::veggie,
|
||||
);
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
// You can use the `use` keyword to bring module paths from modules from
|
||||
// anywhere and especially from the standard library into your scope.
|
||||
|
||||
// TODO: Bring `SystemTime` and `UNIX_EPOCH` from the `std::time` module into
|
||||
// your scope. Bonus style points if you can do it with one line!
|
||||
// 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!"),
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
# 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++](https://en.cppreference.com/w/cpp/container/unordered_map),
|
||||
[*dictionary* in Python](https://docs.python.org/3/tutorial/datastructures.html#dictionaries) 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
|
||||
|
||||
- [Storing Keys with Associated Values in Hash Maps](https://doc.rust-lang.org/book/ch08-03-hash-maps.html)
|
|
@ -0,0 +1,40 @@
|
|||
// 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 3 different
|
||||
// types of fruits (e.g. apple, banana, mango) in the basket and the total count
|
||||
// of all the fruits should be at least 5.
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
fn fruit_basket() -> HashMap<String, u32> {
|
||||
// TODO: Declare the hash map.
|
||||
// let mut basket =
|
||||
|
||||
// Two bananas are already given for you :)
|
||||
basket.insert(String::from("banana"), 2);
|
||||
|
||||
// TODO: Put more fruits in your basket.
|
||||
|
||||
basket
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// You can optionally experiment here.
|
||||
}
|
||||
|
||||
#[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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
// We're collecting different fruits to bake a delicious fruit cake. For this,
|
||||
// we have a basket, which we'll represent in the form of a hash map. The key
|
||||
// represents the name of each fruit we collect and the value represents how
|
||||
// many of that particular fruit we have collected. Three types of fruits -
|
||||
// Apple (4), Mango (2) and Lychee (5) are already in the basket hash map. You
|
||||
// must add fruit to the basket so that there is at least one of each kind and
|
||||
// more than 11 in total - we have a lot of mouths to feed. You are not allowed
|
||||
// to insert any more of the fruits that are already in the basket (Apple,
|
||||
// Mango, and Lychee).
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Hash, PartialEq, Eq, Debug)]
|
||||
enum Fruit {
|
||||
Apple,
|
||||
Banana,
|
||||
Mango,
|
||||
Lychee,
|
||||
Pineapple,
|
||||
}
|
||||
|
||||
fn fruit_basket(basket: &mut HashMap<Fruit, u32>) {
|
||||
let fruit_kinds = [
|
||||
Fruit::Apple,
|
||||
Fruit::Banana,
|
||||
Fruit::Mango,
|
||||
Fruit::Lychee,
|
||||
Fruit::Pineapple,
|
||||
];
|
||||
|
||||
for fruit in fruit_kinds {
|
||||
// TODO: Insert new fruits if they are not already present in the
|
||||
// basket. Note that you are not allowed to put any type of fruit that's
|
||||
// already present!
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// You can optionally experiment here.
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
// Don't modify this function!
|
||||
fn get_fruit_basket() -> HashMap<Fruit, u32> {
|
||||
let content = [(Fruit::Apple, 4), (Fruit::Mango, 2), (Fruit::Lychee, 5)];
|
||||
HashMap::from_iter(content)
|
||||
}
|
||||
|
||||
#[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);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn all_fruit_types_in_basket() {
|
||||
let fruit_kinds = [
|
||||
Fruit::Apple,
|
||||
Fruit::Banana,
|
||||
Fruit::Mango,
|
||||
Fruit::Lychee,
|
||||
Fruit::Pineapple,
|
||||
];
|
||||
|
||||
let mut basket = get_fruit_basket();
|
||||
fruit_basket(&mut basket);
|
||||
|
||||
for fruit_kind in fruit_kinds {
|
||||
let Some(amount) = basket.get(&fruit_kind) else {
|
||||
panic!("Fruit kind {fruit_kind:?} was not found in basket");
|
||||
};
|
||||
assert!(*amount > 0);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
// 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, the total
|
||||
// number of goals the team scored, and the total number of goals the team
|
||||
// conceded.
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
// A structure to store the goal details of a team.
|
||||
#[derive(Default)]
|
||||
struct TeamScores {
|
||||
goals_scored: u8,
|
||||
goals_conceded: u8,
|
||||
}
|
||||
|
||||
fn build_scores_table(results: &str) -> HashMap<&str, TeamScores> {
|
||||
// The name of the team is the key and its associated struct is the value.
|
||||
let mut scores = HashMap::new();
|
||||
|
||||
for line in results.lines() {
|
||||
let mut split_iterator = line.split(',');
|
||||
// NOTE: We use `unwrap` because we didn't deal with error handling yet.
|
||||
let team_1_name = split_iterator.next().unwrap();
|
||||
let team_2_name = split_iterator.next().unwrap();
|
||||
let team_1_score: u8 = split_iterator.next().unwrap().parse().unwrap();
|
||||
let team_2_score: u8 = split_iterator.next().unwrap().parse().unwrap();
|
||||
|
||||
// TODO: Populate the scores table with the extracted details.
|
||||
// Keep in mind that goals scored by team 1 will be the number of goals
|
||||
// conceded by team 2. Similarly, goals scored by team 2 will be the
|
||||
// number of goals conceded by team 1.
|
||||
}
|
||||
|
||||
scores
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// You can optionally experiment here.
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
const RESULTS: &str = "England,France,4,2
|
||||
France,Italy,3,1
|
||||
Poland,Spain,2,0
|
||||
Germany,England,2,1
|
||||
England,Spain,1,0";
|
||||
|
||||
#[test]
|
||||
fn build_scores() {
|
||||
let scores = build_scores_table(RESULTS);
|
||||
|
||||
assert!(["England", "France", "Germany", "Italy", "Poland", "Spain"]
|
||||
.into_iter()
|
||||
.all(|team_name| scores.contains_key(team_name)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_team_score_1() {
|
||||
let scores = build_scores_table(RESULTS);
|
||||
let team = scores.get("England").unwrap();
|
||||
assert_eq!(team.goals_scored, 6);
|
||||
assert_eq!(team.goals_conceded, 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_team_score_2() {
|
||||
let scores = build_scores_table(RESULTS);
|
||||
let team = scores.get("Spain").unwrap();
|
||||
assert_eq!(team.goals_scored, 0);
|
||||
assert_eq!(team.goals_conceded, 3);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
# 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
|
||||
|
||||
- [Option Enum Format](https://doc.rust-lang.org/book/ch10-01-syntax.html#in-enum-definitions)
|
||||
- [Option Module Documentation](https://doc.rust-lang.org/std/option/)
|
||||
- [Option Enum Documentation](https://doc.rust-lang.org/std/option/enum.Option.html)
|
||||
- [if let](https://doc.rust-lang.org/rust-by-example/flow_control/if_let.html)
|
||||
- [while let](https://doc.rust-lang.org/rust-by-example/flow_control/while_let.html)
|
|
@ -0,0 +1,36 @@
|
|||
// This function returns how much icecream there is left in the fridge.
|
||||
// If it's before 22:00 (24-hour system), then 5 scoops are left. At 22:00,
|
||||
// someone eats it all, so no icecream is left (value 0). Return `None` if
|
||||
// `hour_of_day` is higher than 23.
|
||||
fn maybe_icecream(hour_of_day: u16) -> Option<u16> {
|
||||
// TODO: Complete the function body.
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// You can optionally experiment here.
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn raw_value() {
|
||||
// TODO: Fix this test. How do you get the value contained in the
|
||||
// Option?
|
||||
let icecreams = maybe_icecream(12);
|
||||
|
||||
assert_eq!(icecreams, 5); // Don't change this line.
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_icecream() {
|
||||
assert_eq!(maybe_icecream(0), Some(5));
|
||||
assert_eq!(maybe_icecream(9), Some(5));
|
||||
assert_eq!(maybe_icecream(18), Some(5));
|
||||
assert_eq!(maybe_icecream(22), Some(0));
|
||||
assert_eq!(maybe_icecream(23), Some(0));
|
||||
assert_eq!(maybe_icecream(24), None);
|
||||
assert_eq!(maybe_icecream(25), None);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
fn main() {
|
||||
// You can optionally experiment here.
|
||||
}
|
||||
|
||||
#[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`.
|
||||
word = optional_target {
|
||||
assert_eq!(word, target);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn layered_option() {
|
||||
let range = 10;
|
||||
let mut optional_integers: Vec<Option<i8>> = vec![None];
|
||||
|
||||
for i in 1..=range {
|
||||
optional_integers.push(Some(i));
|
||||
}
|
||||
|
||||
let mut cursor = range;
|
||||
|
||||
// TODO: Make this a while-let statement. Remember that `Vec::pop()`
|
||||
// adds another layer of `Option`. You can do nested pattern matching
|
||||
// in if-let and while-let statements.
|
||||
integer = optional_integers.pop() {
|
||||
assert_eq!(integer, cursor);
|
||||
cursor -= 1;
|
||||
}
|
||||
|
||||
assert_eq!(cursor, 0);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
#[derive(Debug)]
|
||||
struct Point {
|
||||
x: i32,
|
||||
y: i32,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let optional_point = Some(Point { x: 100, y: 200 });
|
||||
|
||||
// TODO: Fix the compiler error by adding something to this match statement.
|
||||
match optional_point {
|
||||
Some(p) => println!("Co-ordinates are {},{}", p.x, p.y),
|
||||
_ => panic!("No match!"),
|
||||
}
|
||||
|
||||
println!("{optional_point:?}"); // Don't change this line.
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
# 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.
|
||||
|
||||
## Further information
|
||||
|
||||
- [Error Handling](https://doc.rust-lang.org/book/ch09-02-recoverable-errors-with-result.html)
|
||||
- [Generics](https://doc.rust-lang.org/book/ch10-01-syntax.html)
|
||||
- [Result](https://doc.rust-lang.org/rust-by-example/error/result.html)
|
||||
- [Boxing errors](https://doc.rust-lang.org/rust-by-example/error/multiple_error_types/boxing_errors.html)
|
|
@ -0,0 +1,41 @@
|
|||
// TODO: 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 returning `None`. Thankfully, Rust has a similar
|
||||
// construct to `Option` that can be used to express error conditions. Change
|
||||
// the function signature and body to return `Result<String, String>` instead
|
||||
// of `Option<String>`.
|
||||
fn generate_nametag_text(name: String) -> Option<String> {
|
||||
if name.is_empty() {
|
||||
// Empty names aren't allowed
|
||||
None
|
||||
} else {
|
||||
Some(format!("Hi! My name is {name}"))
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// You can optionally experiment here.
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn generates_nametag_text_for_a_nonempty_name() {
|
||||
assert_eq!(
|
||||
generate_nametag_text("Beyoncé".to_string()).as_deref(),
|
||||
Ok("Hi! My name is Beyoncé"),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn explains_why_generating_nametag_text_fails() {
|
||||
assert_eq!(
|
||||
generate_nametag_text(String::new())
|
||||
.as_ref()
|
||||
.map_err(|e| e.as_str()),
|
||||
Err("Empty names aren't allowed"),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
// 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 cost of the items. Since
|
||||
// the player typed in the quantity, we get it as a string. They might have
|
||||
// typed anything, not just numbers!
|
||||
//
|
||||
// Right now, this function isn't handling the error case at all. What we want
|
||||
// to do is: If we call the `total_cost` function on a string that is not a
|
||||
// number, that function will return a `ParseIntError`. 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!
|
||||
|
||||
use std::num::ParseIntError;
|
||||
|
||||
fn total_cost(item_quantity: &str) -> Result<i32, ParseIntError> {
|
||||
let processing_fee = 1;
|
||||
let cost_per_item = 5;
|
||||
|
||||
// TODO: Handle the error case as described above.
|
||||
let qty = item_quantity.parse::<i32>();
|
||||
|
||||
Ok(qty * cost_per_item + processing_fee)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// You can optionally experiment here.
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::num::IntErrorKind;
|
||||
|
||||
#[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().kind(),
|
||||
&IntErrorKind::InvalidDigit,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
// 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?
|
||||
|
||||
use std::num::ParseIntError;
|
||||
|
||||
// Don't change this function.
|
||||
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)
|
||||
}
|
||||
|
||||
// TODO: Fix the compiler error by changing the signature and body of the
|
||||
// `main` function.
|
||||
fn main() {
|
||||
let mut tokens = 100;
|
||||
let pretend_user_input = "8";
|
||||
|
||||
// Don't change this line.
|
||||
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.");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
#[derive(PartialEq, Debug)]
|
||||
enum CreationError {
|
||||
Negative,
|
||||
Zero,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
struct PositiveNonzeroInteger(u64);
|
||||
|
||||
impl PositiveNonzeroInteger {
|
||||
fn new(value: i64) -> Result<Self, CreationError> {
|
||||
// TODO: This function shouldn't always return an `Ok`.
|
||||
Ok(Self(value as u64))
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// You can optionally experiment here.
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_creation() {
|
||||
assert_eq!(
|
||||
PositiveNonzeroInteger::new(10),
|
||||
Ok(PositiveNonzeroInteger(10)),
|
||||
);
|
||||
assert_eq!(
|
||||
PositiveNonzeroInteger::new(-10),
|
||||
Err(CreationError::Negative),
|
||||
);
|
||||
assert_eq!(PositiveNonzeroInteger::new(0), Err(CreationError::Zero));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
// This exercise is an altered version of the `errors4` exercise. It 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.
|
||||
//
|
||||
// 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`.
|
||||
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
enum CreationError {
|
||||
Negative,
|
||||
Zero,
|
||||
}
|
||||
|
||||
// This is required so that `CreationError` can implement `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 for CreationError {}
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
struct PositiveNonzeroInteger(u64);
|
||||
|
||||
impl PositiveNonzeroInteger {
|
||||
fn new(value: i64) -> Result<PositiveNonzeroInteger, CreationError> {
|
||||
match value {
|
||||
x if x < 0 => Err(CreationError::Negative),
|
||||
0 => Err(CreationError::Zero),
|
||||
x => Ok(PositiveNonzeroInteger(x as u64)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Add the correct return type `Result<(), Box<dyn ???>>`. What can we
|
||||
// use to describe both errors? Is there a trait which both errors implement?
|
||||
fn main() {
|
||||
let pretend_user_input = "42";
|
||||
let x: i64 = pretend_user_input.parse()?;
|
||||
println!("output={:?}", PositiveNonzeroInteger::new(x)?);
|
||||
Ok(())
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
// Using catch-all error types like `Box<dyn 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.
|
||||
|
||||
use std::num::ParseIntError;
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
enum CreationError {
|
||||
Negative,
|
||||
Zero,
|
||||
}
|
||||
|
||||
// A custom error type that we will be using in `PositiveNonzeroInteger::parse`.
|
||||
#[derive(PartialEq, Debug)]
|
||||
enum ParsePosNonzeroError {
|
||||
Creation(CreationError),
|
||||
ParseInt(ParseIntError),
|
||||
}
|
||||
|
||||
impl ParsePosNonzeroError {
|
||||
fn from_creation(err: CreationError) -> Self {
|
||||
Self::Creation(err)
|
||||
}
|
||||
|
||||
// TODO: Add another error conversion function here.
|
||||
// fn from_parse_int(???) -> Self { ??? }
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
struct PositiveNonzeroInteger(u64);
|
||||
|
||||
impl PositiveNonzeroInteger {
|
||||
fn new(value: i64) -> Result<Self, CreationError> {
|
||||
match value {
|
||||
x if x < 0 => Err(CreationError::Negative),
|
||||
0 => Err(CreationError::Zero),
|
||||
x => Ok(Self(x as u64)),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse(s: &str) -> Result<Self, ParsePosNonzeroError> {
|
||||
// TODO: change this to return an appropriate error instead of panicking
|
||||
// when `parse()` returns an error.
|
||||
let x: i64 = s.parse().unwrap();
|
||||
Self::new(x).map_err(ParsePosNonzeroError::from_creation)
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// You can optionally experiment here.
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_parse_error() {
|
||||
assert!(matches!(
|
||||
PositiveNonzeroInteger::parse("not a number"),
|
||||
Err(ParsePosNonzeroError::ParseInt(_)),
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_negative() {
|
||||
assert_eq!(
|
||||
PositiveNonzeroInteger::parse("-555"),
|
||||
Err(ParsePosNonzeroError::Creation(CreationError::Negative)),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_zero() {
|
||||
assert_eq!(
|
||||
PositiveNonzeroInteger::parse("0"),
|
||||
Err(ParsePosNonzeroError::Creation(CreationError::Zero)),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_positive() {
|
||||
let x = PositiveNonzeroInteger::new(42).unwrap();
|
||||
assert_eq!(x.0, 42);
|
||||
assert_eq!(PositiveNonzeroInteger::parse("42"), Ok(x));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
# 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 some rather involved 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
|
||||
|
||||
- [Generic Data Types](https://doc.rust-lang.org/book/ch10-01-syntax.html)
|
||||
- [Bounds](https://doc.rust-lang.org/rust-by-example/generics/bounds.html)
|
|
@ -0,0 +1,18 @@
|
|||
// `Vec<T>` is generic over the type `T`. In most cases, the compiler is able to
|
||||
// infer `T`, for example after pushing a value with a concrete type to the vector.
|
||||
// But in this exercise, the compiler needs some help through a type annotation.
|
||||
|
||||
fn main() {
|
||||
// TODO: Fix the compiler error by annotating the type of the vector
|
||||
// `Vec<T>`. Choose `T` as some integer type that can be created from
|
||||
// `u8` and `i8`.
|
||||
let mut numbers = Vec::new();
|
||||
|
||||
// Don't change the lines below.
|
||||
let n1: u8 = 42;
|
||||
numbers.push(n1.into());
|
||||
let n2: i8 = -1;
|
||||
numbers.push(n2.into());
|
||||
|
||||
println!("{numbers:?}");
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
// This powerful wrapper provides the ability to store a positive integer value.
|
||||
// TODO: Rewrite it using a generic so that it supports wrapping ANY type.
|
||||
struct Wrapper {
|
||||
value: u32,
|
||||
}
|
||||
|
||||
// TODO: Adapt the struct's implementation to be generic over the wrapped value.
|
||||
impl Wrapper {
|
||||
fn new(value: u32) -> Self {
|
||||
Wrapper { value }
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// You can optionally experiment here.
|
||||
}
|
||||
|
||||
#[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");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
# 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` (the `clone` method)
|
||||
- `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
|
||||
|
||||
- [Traits](https://doc.rust-lang.org/book/ch10-02-traits.html)
|
|
@ -0,0 +1,30 @@
|
|||
// The trait `AppendBar` has only one function which appends "Bar" to any object
|
||||
// implementing this trait.
|
||||
trait AppendBar {
|
||||
fn append_bar(self) -> Self;
|
||||
}
|
||||
|
||||
impl AppendBar for String {
|
||||
// TODO: Implement `AppendBar` for the type `String`.
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let s = String::from("Foo");
|
||||
let s = s.append_bar();
|
||||
println!("s: {s}");
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn is_foo_bar() {
|
||||
assert_eq!(String::from("Foo").append_bar(), "FooBar");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_bar_bar() {
|
||||
assert_eq!(String::from("").append_bar().append_bar(), "BarBar");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
trait AppendBar {
|
||||
fn append_bar(self) -> Self;
|
||||
}
|
||||
|
||||
// TODO: Implement the trait `AppendBar` for a vector of strings.
|
||||
// `append_bar` should push the string "Bar" into the vector.
|
||||
|
||||
fn main() {
|
||||
// You can optionally experiment here.
|
||||
}
|
||||
|
||||
#[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(), "Bar");
|
||||
assert_eq!(foo.pop().unwrap(), "Foo");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
#![allow(dead_code)]
|
||||
|
||||
trait Licensed {
|
||||
// TODO: Add a default implementation for `licensing_info` so that
|
||||
// implementors like the two structs below can share that default behavior
|
||||
// without repeating the function.
|
||||
// The default license information should be the string "Default license".
|
||||
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.
|
||||
|
||||
fn main() {
|
||||
// You can optionally experiment here.
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn is_licensing_info_the_same() {
|
||||
let licensing_info = "Default license";
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
trait Licensed {
|
||||
fn licensing_info(&self) -> String {
|
||||
"Default license".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
struct SomeSoftware;
|
||||
struct OtherSoftware;
|
||||
|
||||
impl Licensed for SomeSoftware {}
|
||||
impl Licensed for OtherSoftware {}
|
||||
|
||||
// TODO: Fix the compiler error by only changing the signature of this function.
|
||||
fn compare_license_types(software1: ???, software2: ???) -> bool {
|
||||
software1.licensing_info() == software2.licensing_info()
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// You can optionally experiment here.
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn compare_license_information() {
|
||||
assert!(compare_license_types(SomeSoftware, OtherSoftware));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compare_license_information_backwards() {
|
||||
assert!(compare_license_types(OtherSoftware, SomeSoftware));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
trait SomeTrait {
|
||||
fn some_function(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
trait OtherTrait {
|
||||
fn other_function(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
struct SomeStruct;
|
||||
impl SomeTrait for SomeStruct {}
|
||||
impl OtherTrait for SomeStruct {}
|
||||
|
||||
struct OtherStruct;
|
||||
impl SomeTrait for OtherStruct {}
|
||||
impl OtherTrait for OtherStruct {}
|
||||
|
||||
// TODO: Fix the compiler error by only changing the signature of this function.
|
||||
fn some_func(item: ???) -> bool {
|
||||
item.some_function() && item.other_function()
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// You can optionally experiment here.
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_some_func() {
|
||||
assert!(some_func(SomeStruct));
|
||||
assert!(some_func(OtherStruct));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
# Lifetimes
|
||||
|
||||
Lifetimes tell the compiler how to check whether references live 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.
|
||||
|
||||
If you'd like to learn more about lifetime annotations, the
|
||||
[lifetimekata](https://tfpk.github.io/lifetimekata/) project
|
||||
has a similar style of exercises to Rustlings, but is all about
|
||||
learning to write lifetime annotations.
|
||||
|
||||
## Further information
|
||||
|
||||
- [Lifetimes (in Rust By Example)](https://doc.rust-lang.org/stable/rust-by-example/scope/lifetime.html)
|
||||
- [Validating References with Lifetimes](https://doc.rust-lang.org/book/ch10-03-lifetime-syntax.html)
|
|
@ -0,0 +1,28 @@
|
|||
// 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?
|
||||
|
||||
// TODO: Fix the compiler error by updating the function signature.
|
||||
fn longest(x: &str, y: &str) -> &str {
|
||||
if x.len() > y.len() {
|
||||
x
|
||||
} else {
|
||||
y
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// You can optionally experiment here.
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_longest() {
|
||||
assert_eq!(longest("abcd", "123"), "abcd");
|
||||
assert_eq!(longest("abc", "1234"), "1234");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
// Don't change this function.
|
||||
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
|
||||
if x.len() > y.len() {
|
||||
x
|
||||
} else {
|
||||
y
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// TODO: Fix the compiler error by moving one line.
|
||||
|
||||
let string1 = String::from("long string is long");
|
||||
let result;
|
||||
{
|
||||
let string2 = String::from("xyz");
|
||||
result = longest(&string1, &string2);
|
||||
}
|
||||
println!("The longest string is '{result}'");
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
// Lifetimes are also needed when structs hold references.
|
||||
|
||||
// TODO: Fix the compiler errors about the struct.
|
||||
struct Book {
|
||||
author: &str,
|
||||
title: &str,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let book = Book {
|
||||
author: "George Orwell",
|
||||
title: "1984",
|
||||
};
|
||||
|
||||
println!("{} by {}", book.title, book.author);
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
# 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
|
||||
|
||||
- [Writing Tests](https://doc.rust-lang.org/book/ch11-01-writing-tests.html)
|
|
@ -0,0 +1,23 @@
|
|||
// Tests are important to ensure that your code does what you think it should
|
||||
// do.
|
||||
|
||||
fn is_even(n: i64) -> bool {
|
||||
n % 2 == 0
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// You can optionally experiment here.
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
// TODO: Import `is_even`. You can use a wildcard to import everything in
|
||||
// the outer module.
|
||||
|
||||
#[test]
|
||||
fn you_can_assert() {
|
||||
// TODO: Test the function `is_even` with some values.
|
||||
assert!();
|
||||
assert!();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
// Calculates the power of 2 using a bit shift.
|
||||
// `1 << n` is equivalent to "2 to the power of n".
|
||||
fn power_of_2(n: u8) -> u64 {
|
||||
1 << n
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// You can optionally experiment here.
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn you_can_assert_eq() {
|
||||
// TODO: Test the function `power_of_2` with some values.
|
||||
assert_eq!();
|
||||
assert_eq!();
|
||||
assert_eq!();
|
||||
assert_eq!();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
struct Rectangle {
|
||||
width: i32,
|
||||
height: i32,
|
||||
}
|
||||
|
||||
impl Rectangle {
|
||||
// Don't change this function.
|
||||
fn new(width: i32, height: i32) -> Self {
|
||||
if width <= 0 || height <= 0 {
|
||||
// Returning a `Result` would be better here. But we want to learn
|
||||
// how to test functions that can panic.
|
||||
panic!("Rectangle width and height must be positive");
|
||||
}
|
||||
|
||||
Rectangle { width, height }
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// You can optionally experiment here.
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn correct_width_and_height() {
|
||||
// TODO: This test should check if the rectangle has the size that we
|
||||
// pass to its constructor.
|
||||
let rect = Rectangle::new(10, 20);
|
||||
assert_eq!(todo!(), 10); // Check width
|
||||
assert_eq!(todo!(), 20); // Check height
|
||||
}
|
||||
|
||||
// TODO: This test should check if the program panics when we try to create
|
||||
// a rectangle with negative width.
|
||||
#[test]
|
||||
fn negative_width() {
|
||||
let _rect = Rectangle::new(-10, 10);
|
||||
}
|
||||
|
||||
// TODO: This test should check if the program panics when we try to create
|
||||
// a rectangle with negative height.
|
||||
#[test]
|
||||
fn negative_height() {
|
||||
let _rect = Rectangle::new(10, -10);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
# Iterators
|
||||
|
||||
This section will teach you about Iterators.
|
||||
|
||||
## Further information
|
||||
|
||||
- [Iterator](https://doc.rust-lang.org/book/ch13-02-iterators.html)
|
||||
- [Iterator documentation](https://doc.rust-lang.org/stable/std/iter/)
|
|
@ -0,0 +1,25 @@
|
|||
// 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.
|
||||
|
||||
fn main() {
|
||||
// You can optionally experiment here.
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn iterators() {
|
||||
let my_fav_fruits = ["banana", "custard apple", "avocado", "peach", "raspberry"];
|
||||
|
||||
// TODO: Create an iterator over the array.
|
||||
let mut fav_fruits_iterator = todo!();
|
||||
|
||||
assert_eq!(fav_fruits_iterator.next(), Some(&"banana"));
|
||||
assert_eq!(fav_fruits_iterator.next(), todo!()); // TODO: Replace `todo!()`
|
||||
assert_eq!(fav_fruits_iterator.next(), Some(&"avocado"));
|
||||
assert_eq!(fav_fruits_iterator.next(), todo!()); // TODO: Replace `todo!()`
|
||||
assert_eq!(fav_fruits_iterator.next(), Some(&"raspberry"));
|
||||
assert_eq!(fav_fruits_iterator.next(), todo!()); // TODO: Replace `todo!()`
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
// In this exercise, you'll learn some of the unique advantages that iterators
|
||||
// can offer.
|
||||
|
||||
// TODO: Complete the `capitalize_first` function.
|
||||
// "hello" -> "Hello"
|
||||
fn capitalize_first(input: &str) -> String {
|
||||
let mut chars = input.chars();
|
||||
match chars.next() {
|
||||
None => String::new(),
|
||||
Some(first) => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Apply the `capitalize_first` function to a slice of string slices.
|
||||
// Return a vector of strings.
|
||||
// ["hello", "world"] -> ["Hello", "World"]
|
||||
fn capitalize_words_vector(words: &[&str]) -> Vec<String> {
|
||||
// ???
|
||||
}
|
||||
|
||||
// TODO: Apply the `capitalize_first` function again to a slice of string
|
||||
// slices. Return a single string.
|
||||
// ["hello", " ", "world"] -> "Hello World"
|
||||
fn capitalize_words_string(words: &[&str]) -> String {
|
||||
// ???
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// You can optionally experiment here.
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_success() {
|
||||
assert_eq!(capitalize_first("hello"), "Hello");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_empty() {
|
||||
assert_eq!(capitalize_first(""), "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_iterate_string_vec() {
|
||||
let words = vec!["hello", "world"];
|
||||
assert_eq!(capitalize_words_vector(&words), ["Hello", "World"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_iterate_into_string() {
|
||||
let words = vec!["hello", " ", "world"];
|
||||
assert_eq!(capitalize_words_string(&words), "Hello World");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
#[derive(Debug, PartialEq, Eq)]
|
||||
enum DivisionError {
|
||||
// Example: 42 / 0
|
||||
DivideByZero,
|
||||
// Only case for `i64`: `i64::MIN / -1` because the result is `i64::MAX + 1`
|
||||
IntegerOverflow,
|
||||
// Example: 5 / 2 = 2.5
|
||||
NotDivisible,
|
||||
}
|
||||
|
||||
// TODO: Calculate `a` divided by `b` if `a` is evenly divisible by `b`.
|
||||
// Otherwise, return a suitable error.
|
||||
fn divide(a: i64, b: i64) -> Result<i64, DivisionError> {
|
||||
todo!();
|
||||
}
|
||||
|
||||
// TODO: Add the correct return type and complete the function body.
|
||||
// Desired output: `Ok([1, 11, 1426, 3])`
|
||||
fn result_with_list() {
|
||||
let numbers = [27, 297, 38502, 81];
|
||||
let division_results = numbers.into_iter().map(|n| divide(n, 27));
|
||||
}
|
||||
|
||||
// TODO: Add the correct return type and complete the function body.
|
||||
// Desired output: `[Ok(1), Ok(11), Ok(1426), Ok(3)]`
|
||||
fn list_of_results() {
|
||||
let numbers = [27, 297, 38502, 81];
|
||||
let division_results = numbers.into_iter().map(|n| divide(n, 27));
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// You can optionally experiment here.
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_success() {
|
||||
assert_eq!(divide(81, 9), Ok(9));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_divide_by_0() {
|
||||
assert_eq!(divide(81, 0), Err(DivisionError::DivideByZero));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_integer_overflow() {
|
||||
assert_eq!(divide(i64::MIN, -1), Err(DivisionError::IntegerOverflow));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_not_divisible() {
|
||||
assert_eq!(divide(81, 6), Err(DivisionError::NotDivisible));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_divide_0_by_something() {
|
||||
assert_eq!(divide(0, 81), Ok(0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_result_with_list() {
|
||||
assert_eq!(result_with_list().unwrap(), [1, 11, 1426, 3]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_list_of_results() {
|
||||
assert_eq!(list_of_results(), [Ok(1), Ok(11), Ok(1426), Ok(3)]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
fn factorial(num: u64) -> u64 {
|
||||
// TODO: Complete this function to return the factorial of `num` which is
|
||||
// defined as `1 * 2 * 3 * … * num`.
|
||||
// https://en.wikipedia.org/wiki/Factorial
|
||||
//
|
||||
// Do not use:
|
||||
// - early returns (using the `return` keyword explicitly)
|
||||
// Try not to use:
|
||||
// - imperative style loops (for/while)
|
||||
// - additional variables
|
||||
// For an extra challenge, don't use:
|
||||
// - recursion
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// You can optionally experiment here.
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn factorial_of_0() {
|
||||
assert_eq!(factorial(0), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn factorial_of_1() {
|
||||
assert_eq!(factorial(1), 1);
|
||||
}
|
||||
#[test]
|
||||
fn factorial_of_2() {
|
||||
assert_eq!(factorial(2), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn factorial_of_4() {
|
||||
assert_eq!(factorial(4), 24);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,153 @@
|
|||
// 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. Recreate this counting
|
||||
// functionality using iterators. Try to not use imperative loops (for/while).
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// TODO: Implement the functionality of `count_for` but with an iterator instead
|
||||
// of a `for` loop.
|
||||
fn count_iterator(map: &HashMap<String, Progress>, value: Progress) -> usize {
|
||||
// `map` is a hash map with `String` keys and `Progress` values.
|
||||
// map = { "variables1": Complete, "from_str": None, … }
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// TODO: Implement the functionality of `count_collection_for` but with an
|
||||
// iterator instead of a `for` loop.
|
||||
fn count_collection_iterator(collection: &[HashMap<String, Progress>], value: Progress) -> usize {
|
||||
// `collection` is a slice of hash maps.
|
||||
// collection = [{ "variables1": Complete, "from_str": None, … },
|
||||
// { "variables2": Complete, … }, … ]
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// You can optionally experiment here.
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
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]
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn count_complete() {
|
||||
let map = get_map();
|
||||
assert_eq!(count_iterator(&map, Progress::Complete), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn count_some() {
|
||||
let map = get_map();
|
||||
assert_eq!(count_iterator(&map, Progress::Some), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn count_none() {
|
||||
let map = get_map();
|
||||
assert_eq!(count_iterator(&map, Progress::None), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn count_complete_equals_for() {
|
||||
let map = get_map();
|
||||
let progress_states = [Progress::Complete, Progress::Some, Progress::None];
|
||||
for progress_state in progress_states {
|
||||
assert_eq!(
|
||||
count_for(&map, progress_state),
|
||||
count_iterator(&map, progress_state),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn count_collection_complete() {
|
||||
let collection = get_vec_map();
|
||||
assert_eq!(
|
||||
count_collection_iterator(&collection, Progress::Complete),
|
||||
6,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn count_collection_some() {
|
||||
let collection = get_vec_map();
|
||||
assert_eq!(count_collection_iterator(&collection, Progress::Some), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn count_collection_none() {
|
||||
let collection = get_vec_map();
|
||||
assert_eq!(count_collection_iterator(&collection, Progress::None), 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn count_collection_equals_for() {
|
||||
let collection = get_vec_map();
|
||||
let progress_states = [Progress::Complete, Progress::Some, Progress::None];
|
||||
|
||||
for progress_state in progress_states {
|
||||
assert_eq!(
|
||||
count_collection_for(&collection, progress_state),
|
||||
count_collection_iterator(&collection, progress_state),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
# 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
|
||||
|
||||
- [Smart Pointers](https://doc.rust-lang.org/book/ch15-00-smart-pointers.html)
|
||||
- [Using Box to Point to Data on the Heap](https://doc.rust-lang.org/book/ch15-01-box.html)
|
||||
- [Rc\<T\>, the Reference Counted Smart Pointer](https://doc.rust-lang.org/book/ch15-04-rc.html)
|
||||
- [Shared-State Concurrency](https://doc.rust-lang.org/book/ch16-03-shared-state.html)
|
||||
- [Cow Documentation](https://doc.rust-lang.org/std/borrow/enum.Cow.html)
|
|
@ -0,0 +1,45 @@
|
|||
// In this exercise, we are given a `Vec` of `u32` called `numbers` with values
|
||||
// ranging from 0 to 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, …
|
||||
//
|
||||
// Each thread should own a reference-counting pointer to the vector of
|
||||
// numbers. But `Rc` isn't thread-safe. Therefore, we need to use `Arc`.
|
||||
//
|
||||
// Don't get distracted by how threads are spawned and joined. We will practice
|
||||
// that later in the exercises about threads.
|
||||
|
||||
// Don't change the lines below.
|
||||
#![forbid(unused_imports)]
|
||||
use std::{sync::Arc, thread};
|
||||
|
||||
fn main() {
|
||||
let numbers: Vec<_> = (0..100u32).collect();
|
||||
|
||||
// TODO: Define `shared_numbers` by using `Arc`.
|
||||
// let shared_numbers = ???;
|
||||
|
||||
let mut join_handles = Vec::new();
|
||||
|
||||
for offset in 0..8 {
|
||||
// TODO: Define `child_numbers` using `shared_numbers`.
|
||||
// let child_numbers = ???;
|
||||
|
||||
let handle = thread::spawn(move || {
|
||||
let sum: u32 = child_numbers.iter().filter(|&&n| n % 8 == offset).sum();
|
||||
println!("Sum of offset {offset} is {sum}");
|
||||
});
|
||||
|
||||
join_handles.push(handle);
|
||||
}
|
||||
|
||||
for handle in join_handles.into_iter() {
|
||||
handle.join().unwrap();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
// 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`.
|
||||
|
||||
// TODO: Use a `Box` in the enum definition to make the code compile.
|
||||
#[derive(PartialEq, Debug)]
|
||||
enum List {
|
||||
Cons(i32, List),
|
||||
Nil,
|
||||
}
|
||||
|
||||
// TODO: Create an empty cons list.
|
||||
fn create_empty_list() -> List {
|
||||
todo!()
|
||||
}
|
||||
|
||||
// TODO: Create a non-empty cons list.
|
||||
fn create_non_empty_list() -> List {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn main() {
|
||||
println!("This is an empty cons list: {:?}", create_empty_list());
|
||||
println!(
|
||||
"This is a non-empty cons list: {:?}",
|
||||
create_non_empty_list(),
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_create_empty_list() {
|
||||
assert_eq!(create_empty_list(), List::Nil);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_non_empty_list() {
|
||||
assert_ne!(create_empty_list(), create_non_empty_list());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
// This exercise explores the `Cow` (Clone-On-Write) smart pointer. It can
|
||||
// enclose and provide immutable access to borrowed data and clone the data
|
||||
// lazily when mutation or ownership is required. The type is designed to work
|
||||
// with general borrowed data via the `Borrow` trait.
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
fn abs_all(input: &mut Cow<[i32]>) {
|
||||
for ind in 0..input.len() {
|
||||
let value = input[ind];
|
||||
if value < 0 {
|
||||
// Clones into a vector if not already owned.
|
||||
input.to_mut()[ind] = -value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// You can optionally experiment here.
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn reference_mutation() {
|
||||
// Clone occurs because `input` needs to be mutated.
|
||||
let vec = vec![-1, 0, 1];
|
||||
let mut input = Cow::from(&vec);
|
||||
abs_all(&mut input);
|
||||
assert!(matches!(input, Cow::Owned(_)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reference_no_mutation() {
|
||||
// No clone occurs because `input` doesn't need to be mutated.
|
||||
let vec = vec![0, 1, 2];
|
||||
let mut input = Cow::from(&vec);
|
||||
abs_all(&mut input);
|
||||
// TODO: Replace `todo!()` with `Cow::Owned(_)` or `Cow::Borrowed(_)`.
|
||||
assert!(matches!(input, todo!()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn owned_no_mutation() {
|
||||
// We can also pass `vec` without `&` so `Cow` owns it directly. In this
|
||||
// case, no mutation occurs (all numbers are already absolute) and thus
|
||||
// also no clone. But the result is still owned because it was never
|
||||
// borrowed or mutated.
|
||||
let vec = vec![0, 1, 2];
|
||||
let mut input = Cow::from(vec);
|
||||
abs_all(&mut input);
|
||||
// TODO: Replace `todo!()` with `Cow::Owned(_)` or `Cow::Borrowed(_)`.
|
||||
assert!(matches!(input, todo!()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn owned_mutation() {
|
||||
// Of course this is also the case if a mutation does occur (not all
|
||||
// numbers are absolute). In this case, the call to `to_mut()` in the
|
||||
// `abs_all` function returns a reference to the same data as before.
|
||||
let vec = vec![-1, 0, 1];
|
||||
let mut input = Cow::from(vec);
|
||||
abs_all(&mut input);
|
||||
// TODO: Replace `todo!()` with `Cow::Owned(_)` or `Cow::Borrowed(_)`.
|
||||
assert!(matches!(input, todo!()));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
// 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 `Planet`s. The planets take ownership of the sun, indicating that
|
||||
// they revolve around the sun.
|
||||
|
||||
use std::rc::Rc;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Sun;
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[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() {
|
||||
// You can optionally experiment here.
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn rc1() {
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
# 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
|
||||
|
||||
- [Dining Philosophers example](https://doc.rust-lang.org/1.4.0/book/dining-philosophers.html)
|
||||
- [Using Threads to Run Code Simultaneously](https://doc.rust-lang.org/book/ch16-01-threads.html)
|
||||
- [Using Message Passing to Transfer Data Between Threads](https://doc.rust-lang.org/book/ch16-02-message-passing.html)
|
|
@ -0,0 +1,37 @@
|
|||
// This program spawns multiple threads that each run for at least 250ms, and
|
||||
// each thread returns how much time they took to complete. The program should
|
||||
// wait until all the spawned threads have finished and should collect their
|
||||
// return values into a vector.
|
||||
|
||||
use std::{
|
||||
thread,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
fn main() {
|
||||
let mut handles = Vec::new();
|
||||
for i in 0..10 {
|
||||
let handle = thread::spawn(move || {
|
||||
let start = Instant::now();
|
||||
thread::sleep(Duration::from_millis(250));
|
||||
println!("Thread {i} done");
|
||||
start.elapsed().as_millis()
|
||||
});
|
||||
handles.push(handle);
|
||||
}
|
||||
|
||||
let mut results = Vec::new();
|
||||
for handle in handles {
|
||||
// TODO: Collect the results of all threads into the `results` vector.
|
||||
// Use the `JoinHandle` struct which is returned by `thread::spawn`.
|
||||
}
|
||||
|
||||
if results.len() != 10 {
|
||||
panic!("Oh no! Some thread isn't done yet!");
|
||||
}
|
||||
|
||||
println!();
|
||||
for (i, result) in results.into_iter().enumerate() {
|
||||
println!("Thread {i} took {result}ms");
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue