1
0

Initial commit

This commit is contained in:
2025-12-16 20:38:14 +00:00
commit 66ef613a6d
221 changed files with 8079 additions and 0 deletions

View File

@@ -0,0 +1,5 @@
fn main() {
// Congratulations, you finished the first exercise 🎉
// As an introduction to Rustlings, the first exercise only required
// entering `n` in the terminal to go to the next exercise.
}

View File

@@ -0,0 +1,4 @@
fn main() {
// `println!` instead of `printline!`.
println!("Hello world!");
}

View File

@@ -0,0 +1,6 @@
fn main() {
// Declaring variables requires the `let` keyword.
let x = 5;
println!("x has the value {x}");
}

View File

@@ -0,0 +1,16 @@
fn main() {
// The easiest way to fix the compiler error is to initialize the
// variable `x`. By setting its value to an integer, Rust infers its type
// as `i32` which is the default type for integers.
let x = 42;
// But we can enforce a type different from the default `i32` by adding
// a type annotation:
// let x: u8 = 42;
if x == 10 {
println!("x is ten!");
} else {
println!("x is not ten!");
}
}

View File

@@ -0,0 +1,15 @@
#![allow(clippy::needless_late_init)]
fn main() {
// Reading uninitialized variables isn't allowed in Rust!
// Therefore, we need to assign a value first.
let x: i32 = 42;
println!("Number {x}");
// It is possible to declare a variable and initialize it later.
// But it can't be used before initialization.
let y: i32;
y = 42;
println!("Number {y}");
}

View File

@@ -0,0 +1,9 @@
fn main() {
// In Rust, variables are immutable by default.
// Adding the `mut` keyword after `let` makes the declared variable mutable.
let mut x = 3;
println!("Number {x}");
x = 5;
println!("Number {x}");
}

View File

@@ -0,0 +1,9 @@
fn main() {
let number = "T-H-R-E-E";
println!("Spell a number: {}", number);
// Using variable shadowing
// https://doc.rust-lang.org/book/ch03-01-variables-and-mutability.html#shadowing
let number = 3;
println!("Number plus two is: {}", number + 2);
}

View File

@@ -0,0 +1,6 @@
// The type of constants must always be annotated.
const NUMBER: u64 = 3;
fn main() {
println!("Number: {NUMBER}");
}

View File

@@ -0,0 +1,8 @@
// Some function with the name `call_me` without arguments or a return value.
fn call_me() {
println!("Hello world!");
}
fn main() {
call_me();
}

View File

@@ -0,0 +1,11 @@
// The type of function arguments must be annotated.
// Added the type annotation `u64`.
fn call_me(num: u64) {
for i in 0..num {
println!("Ring! Call number {}", i + 1);
}
}
fn main() {
call_me(3);
}

View File

@@ -0,0 +1,10 @@
fn call_me(num: u8) {
for i in 0..num {
println!("Ring! Call number {}", i + 1);
}
}
fn main() {
// `call_me` expects an argument.
call_me(5);
}

View File

@@ -0,0 +1,17 @@
fn is_even(num: i64) -> bool {
num % 2 == 0
}
// The return type must always be annotated.
fn sale_price(price: i64) -> 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));
}

View File

@@ -0,0 +1,9 @@
fn square(num: i32) -> i32 {
// Removed the semicolon `;` at the end of the line below to implicitly return the result.
num * num
}
fn main() {
let answer = square(3);
println!("The square of 3 is {answer}");
}

32
solutions/03_if/if1.rs Normal file
View File

@@ -0,0 +1,32 @@
fn bigger(a: i32, b: i32) -> i32 {
if a > b {
a
} else {
b
}
}
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));
}
}

35
solutions/03_if/if2.rs Normal file
View File

@@ -0,0 +1,35 @@
fn picky_eater(food: &str) -> &str {
if food == "strawberry" {
"Yummy!"
} else if food == "potato" {
"I guess I can eat that."
} else {
"No thanks!"
}
}
fn main() {
// You can optionally experiment here.
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn yummy_food() {
assert_eq!(picky_eater("strawberry"), "Yummy!");
}
#[test]
fn neutral_food() {
assert_eq!(picky_eater("potato"), "I guess I can eat that.");
}
#[test]
fn default_disliked_food() {
assert_eq!(picky_eater("broccoli"), "No thanks!");
assert_eq!(picky_eater("gummy bears"), "No thanks!");
assert_eq!(picky_eater("literally anything"), "No thanks!");
}
}

53
solutions/03_if/if3.rs Normal file
View File

@@ -0,0 +1,53 @@
fn animal_habitat(animal: &str) -> &str {
let identifier = if animal == "crab" {
1
} else if animal == "gopher" {
2
} else if animal == "snake" {
3
} else {
// Any unused identifier.
4
};
// Instead of such an identifier, you would use an enum in Rust.
// But we didn't get into enums yet.
if identifier == 1 {
"Beach"
} else if identifier == 2 {
"Burrow"
} else if identifier == 3 {
"Desert"
} else {
"Unknown"
}
}
fn main() {
// You can optionally experiment here.
}
#[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")
}
}

View File

@@ -0,0 +1,11 @@
fn main() {
let is_morning = true;
if is_morning {
println!("Good morning!");
}
let is_evening = !is_morning;
if is_evening {
println!("Good evening!");
}
}

View File

@@ -0,0 +1,21 @@
fn main() {
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!");
}
// Example with 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!");
}
}

View File

@@ -0,0 +1,11 @@
fn main() {
// An array with 100 elements of the value 42.
let a = [42; 100];
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");
}
}

View File

@@ -0,0 +1,23 @@
fn main() {
// You can optionally experiment here.
}
#[cfg(test)]
mod tests {
#[test]
fn slice_out_of_array() {
let a = [1, 2, 3, 4, 5];
// 0 1 2 3 4 <- indices
// -------
// |
// +--- slice
// Note that the upper index 4 is excluded.
let nice_slice = &a[1..4];
assert_eq!([2, 3, 4], nice_slice);
// The upper index can be included by using the syntax `..=` (with `=` sign)
let nice_slice = &a[1..=3];
assert_eq!([2, 3, 4], nice_slice);
}
}

View File

@@ -0,0 +1,8 @@
fn main() {
let cat = ("Furry McFurson", 3.5);
// Destructuring the tuple.
let (name, age) = cat;
println!("{name} is {age} years old");
}

View File

@@ -0,0 +1,16 @@
fn main() {
// You can optionally experiment here.
}
#[cfg(test)]
mod tests {
#[test]
fn indexing_tuple() {
let numbers = (1, 2, 3);
// Tuple indexing syntax.
let second = numbers.1;
assert_eq!(second, 2, "This is not the 2nd number in the tuple!");
}
}

View File

@@ -0,0 +1,23 @@
fn array_and_vec() -> ([i32; 4], Vec<i32>) {
let a = [10, 20, 30, 40]; // Array
// Used the `vec!` macro.
let v = vec![10, 20, 30, 40];
(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);
}
}

View File

@@ -0,0 +1,55 @@
fn vec_loop(input: &[i32]) -> Vec<i32> {
let mut output = Vec::new();
for element in input {
output.push(2 * element);
}
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> {
// We will dive deeper into iterators, but for now, this is all what you
// had to do!
// Advanced note: This method is more efficient because it automatically
// preallocates enough capacity. This can be done manually in `vec_loop`
// using `Vec::with_capacity(input.len())` instead of `Vec::new()`.
input.iter().map(|element| 2 * 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]);
}
}

View File

@@ -0,0 +1,25 @@
fn fill_vec(vec: Vec<i32>) -> Vec<i32> {
let mut vec = vec;
// ^^^ added
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);
// `vec0` can't be accessed anymore because it is moved to `fill_vec`.
assert_eq!(vec1, vec![22, 44, 66, 88]);
}
}

View File

@@ -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::*;
#[test]
fn move_semantics2() {
let vec0 = vec![22, 44, 66];
// Cloning `vec0` so that the clone is moved into `fill_vec`, not `vec0`
// itself.
let vec1 = fill_vec(vec0.clone());
assert_eq!(vec0, [22, 44, 66]);
assert_eq!(vec1, [22, 44, 66, 88]);
}
}

View File

@@ -0,0 +1,22 @@
fn fill_vec(mut vec: Vec<i32>) -> Vec<i32> {
// ^^^ added
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]);
}
}

View File

@@ -0,0 +1,21 @@
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;
// `y` used here.
y.push(42);
// The mutable reference `y` is not used anymore,
// therefore a new reference can be created.
let z = &mut x;
z.push(13);
assert_eq!(x, [42, 13]);
}
}

View File

@@ -0,0 +1,23 @@
#![allow(clippy::ptr_arg)]
// Borrows instead of taking ownership.
// It is recommended to use `&str` instead of `&String` here. But this is
// enough for now because we didn't handle strings yet.
fn get_char(data: &String) -> char {
data.chars().last().unwrap()
}
// Takes ownership instead of borrowing.
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);
}

View File

@@ -0,0 +1,49 @@
struct ColorRegularStruct {
red: u8,
green: u8,
blue: u8,
}
struct ColorTupleStruct(u8, u8, u8);
#[derive(Debug)]
struct UnitStruct;
fn main() {
// You can optionally experiment here.
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn regular_structs() {
let green = ColorRegularStruct {
red: 0,
green: 255,
blue: 0,
};
assert_eq!(green.red, 0);
assert_eq!(green.green, 255);
assert_eq!(green.blue, 0);
}
#[test]
fn tuple_structs() {
let green = ColorTupleStruct(0, 255, 0);
assert_eq!(green.0, 0);
assert_eq!(green.1, 255);
assert_eq!(green.2, 0);
}
#[test]
fn unit_structs() {
let unit_struct = UnitStruct;
let message = format!("{unit_struct:?}s are fun!");
assert_eq!(message, "UnitStructs are fun!");
}
}

View File

@@ -0,0 +1,51 @@
#[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();
let your_order = Order {
name: String::from("Hacker in Rust"),
count: 1,
// Struct update syntax
..order_template
};
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);
}
}

View File

@@ -0,0 +1,83 @@
#[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,
}
}
fn is_international(&self) -> bool {
// ^^^^^^^ added
self.sender_country != self.recipient_country
}
fn get_fees(&self, cents_per_gram: u32) -> u32 {
// ^^^^^^ added
self.weight_in_grams * cents_per_gram
}
}
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);
}
}

View File

@@ -0,0 +1,16 @@
#[derive(Debug)]
enum Message {
Resize,
Move,
Echo,
ChangeColor,
Quit,
}
fn main() {
println!("{:?}", Message::Resize);
println!("{:?}", Message::Move);
println!("{:?}", Message::Echo);
println!("{:?}", Message::ChangeColor);
println!("{:?}", Message::Quit);
}

View File

@@ -0,0 +1,37 @@
#[derive(Debug)]
struct Point {
x: u64,
y: u64,
}
#[derive(Debug)]
enum Message {
Resize { width: u64, height: u64 },
Move(Point),
Echo(String),
ChangeColor(u8, u8, u8),
Quit,
}
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();
}
}

View File

@@ -0,0 +1,92 @@
struct Point {
x: u64,
y: u64,
}
enum Message {
Resize { width: u64, height: u64 },
Move(Point),
Echo(String),
ChangeColor(u8, u8, u8),
Quit,
}
struct State {
width: u64,
height: u64,
position: Point,
message: String,
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) {
match message {
Message::Resize { width, height } => self.resize(width, height),
Message::Move(point) => self.move_position(point),
Message::Echo(string) => self.echo(string),
Message::ChangeColor(red, green, blue) => self.change_color(red, green, blue),
Message::Quit => self.quit(),
}
}
}
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);
}
}

View File

@@ -0,0 +1,9 @@
fn current_favorite_color() -> String {
// Equivalent to `String::from("blue")`
"blue".to_string()
}
fn main() {
let answer = current_favorite_color();
println!("My current favorite color is {answer}");
}

View File

@@ -0,0 +1,15 @@
fn is_a_color_word(attempt: &str) -> bool {
attempt == "green" || attempt == "blue" || attempt == "red"
}
fn main() {
let word = String::from("green");
if is_a_color_word(&word) {
// ^ added to have `&String` which is automatically
// coerced to `&str` by the compiler.
println!("That is a color word I know!");
} else {
println!("That is not a color word I know.");
}
}

View File

@@ -0,0 +1,48 @@
fn trim_me(input: &str) -> &str {
input.trim()
}
fn compose_me(input: &str) -> String {
// The macro `format!` has the same syntax as `println!`, but it returns a
// string instead of printing it to the terminal.
// Equivalent to `input.to_string() + " world!"`
format!("{input} world!")
}
fn replace_me(input: &str) -> String {
input.replace("cars", "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",
);
}
}

View File

@@ -0,0 +1,38 @@
fn string_slice(arg: &str) {
println!("{arg}");
}
fn string(arg: String) {
println!("{arg}");
}
fn main() {
string_slice("blue");
string("red".to_string());
string(String::from("hi"));
string("rust is fun!".to_owned());
// Here, both answers work.
// `.into()` converts a type into an expected type.
// If it is called where `String` is expected, it will convert `&str` to `String`.
string("nice weather".into());
// But if it is called where `&str` is expected, then `&str` is kept `&str` since no conversion is needed.
// If you remove the `#[allow(…)]` line, then Clippy will tell you to remove `.into()` below since it is a useless conversion.
#[allow(clippy::useless_conversion)]
string_slice("nice weather".into());
string(format!("Interpolation {}", "Station"));
// WARNING: This is byte indexing, not character indexing.
// Character indexing can be done using `s.chars().nth(INDEX)`.
string_slice(&String::from("abc")[0..1]);
string_slice(" hello there ".trim());
string("Happy Monday!".replace("Mon", "Tues"));
string("mY sHiFt KeY iS sTiCkY".to_lowercase());
}

View File

@@ -0,0 +1,15 @@
mod sausage_factory {
fn get_secret_recipe() -> String {
String::from("Ginger")
}
// Added `pub` before `fn` to make the function accessible outside the module.
pub fn make_sausage() {
get_secret_recipe();
println!("sausage!");
}
}
fn main() {
sausage_factory::make_sausage();
}

View File

@@ -0,0 +1,23 @@
mod delicious_snacks {
// Added `pub` and used the expected alias after `as`.
pub use self::fruits::PEAR as fruit;
pub use self::veggies::CUCUMBER as veggie;
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,
);
}

View File

@@ -0,0 +1,8 @@
use std::time::{SystemTime, UNIX_EPOCH};
fn main() {
match SystemTime::now().duration_since(UNIX_EPOCH) {
Ok(n) => println!("1970-01-01 00:00:00 UTC was {} seconds ago!", n.as_secs()),
Err(_) => panic!("SystemTime before UNIX EPOCH!"),
}
}

View File

@@ -0,0 +1,42 @@
// 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> {
// Declare the hash map.
let mut basket = HashMap::new();
// Two bananas are already given for you :)
basket.insert(String::from("banana"), 2);
// Put more fruits in your basket.
basket.insert(String::from("apple"), 3);
basket.insert(String::from("mango"), 1);
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);
}
}

View File

@@ -0,0 +1,96 @@
// 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 {
// If fruit doesn't exist, insert it with some value.
basket.entry(fruit).or_insert(5);
}
}
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);
}
}
}

View File

@@ -0,0 +1,83 @@
// 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::<&str, TeamScores>::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();
// Insert the default with zeros if a team doesn't exist yet.
let team_1 = scores.entry(team_1_name).or_default();
// Update the values.
team_1.goals_scored += team_1_score;
team_1.goals_conceded += team_2_score;
// Similarly for the second team.
let team_2 = scores.entry(team_2_name).or_default();
team_2.goals_scored += team_2_score;
team_2.goals_conceded += team_1_score;
}
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);
}
}

View File

@@ -0,0 +1,39 @@
// 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> {
match hour_of_day {
0..=21 => Some(5),
22..=23 => Some(0),
_ => None,
}
}
fn main() {
// You can optionally experiment here.
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn raw_value() {
// Using `unwrap` is fine in a test.
let icecreams = maybe_icecream(12).unwrap();
assert_eq!(icecreams, 5);
}
#[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);
}
}

View File

@@ -0,0 +1,37 @@
fn main() {
// You can optionally experiment here.
}
#[cfg(test)]
mod tests {
#[test]
fn simple_option() {
let target = "rustlings";
let optional_target = Some(target);
// if-let
if let Some(word) = optional_target {
assert_eq!(word, target);
}
}
#[test]
fn layered_option() {
let range = 10;
let mut optional_integers: Vec<Option<i8>> = vec![None];
for i in 1..=range {
optional_integers.push(Some(i));
}
let mut cursor = range;
// while-let with nested pattern matching
while let Some(Some(integer)) = optional_integers.pop() {
assert_eq!(integer, cursor);
cursor -= 1;
}
assert_eq!(cursor, 0);
}
}

View File

@@ -0,0 +1,26 @@
#[derive(Debug)]
struct Point {
x: i32,
y: i32,
}
fn main() {
let optional_point = Some(Point { x: 100, y: 200 });
// Solution 1: Matching over the `Option` (not `&Option`) but without moving
// out of the `Some` variant.
match optional_point {
Some(ref p) => println!("Co-ordinates are {},{}", p.x, p.y),
// ^^^ added
_ => panic!("No match!"),
}
// Solution 2: Matching over a reference (`&Option`) by added `&` before
// `optional_point`.
match &optional_point {
Some(p) => println!("Co-ordinates are {},{}", p.x, p.y),
_ => panic!("No match!"),
}
println!("{optional_point:?}");
}

View File

@@ -0,0 +1,37 @@
fn generate_nametag_text(name: String) -> Result<String, String> {
// ^^^^^^ ^^^^^^
if name.is_empty() {
// `Err(String)` instead of `None`.
Err("Empty names aren't allowed".to_string())
} else {
// `Ok` instead of `Some`.
Ok(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"),
);
}
}

View File

@@ -0,0 +1,58 @@
// 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;
#[allow(unused_variables)]
fn total_cost(item_quantity: &str) -> Result<i32, ParseIntError> {
let processing_fee = 1;
let cost_per_item = 5;
// Added `?` to propagate the error.
let qty = item_quantity.parse::<i32>()?;
// ^ added
// Equivalent to this verbose version:
let qty = match item_quantity.parse::<i32>() {
Ok(v) => v,
Err(e) => return Err(e),
};
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,
);
}
}

View File

@@ -0,0 +1,32 @@
// 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)
}
fn main() -> Result<(), ParseIntError> {
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ added
let mut tokens = 100;
let pretend_user_input = "8";
let cost = total_cost(pretend_user_input)?;
if cost > tokens {
println!("You can't afford that many!");
} else {
tokens -= cost;
println!("You now have {tokens} tokens.");
}
// Added this line to return the `Ok` variant of the expected `Result`.
Ok(())
}

View File

@@ -0,0 +1,42 @@
use std::cmp::Ordering;
#[derive(PartialEq, Debug)]
enum CreationError {
Negative,
Zero,
}
#[derive(PartialEq, Debug)]
struct PositiveNonzeroInteger(u64);
impl PositiveNonzeroInteger {
fn new(value: i64) -> Result<Self, CreationError> {
match value.cmp(&0) {
Ordering::Less => Err(CreationError::Negative),
Ordering::Equal => Err(CreationError::Zero),
Ordering::Greater => 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));
}
}

View File

@@ -0,0 +1,54 @@
// 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)),
}
}
}
fn main() -> Result<(), Box<dyn Error>> {
let pretend_user_input = "42";
let x: i64 = pretend_user_input.parse()?;
println!("output={:?}", PositiveNonzeroInteger::new(x)?);
Ok(())
}

View File

@@ -0,0 +1,91 @@
// 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)
}
fn from_parse_int(err: ParseIntError) -> Self {
Self::ParseInt(err)
}
}
#[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> {
// Return an appropriate error instead of panicking when `parse()`
// returns an error.
let x: i64 = s.parse().map_err(ParsePosNonzeroError::from_parse_int)?;
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
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));
}
}

View File

@@ -0,0 +1,17 @@
// `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() {
// `u8` and `i8` can both be converted to `i16`.
let mut numbers: Vec<i16> = Vec::new();
// ^^^^^^^^^^ added
// Don't change the lines below.
let n1: u8 = 42;
numbers.push(n1.into());
let n2: i8 = -1;
numbers.push(n2.into());
println!("{numbers:?}");
}

View File

@@ -0,0 +1,28 @@
struct Wrapper<T> {
value: T,
}
impl<T> Wrapper<T> {
fn new(value: T) -> 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");
}
}

View File

@@ -0,0 +1,32 @@
// 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 {
fn append_bar(self) -> Self {
self + "Bar"
}
}
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");
}
}

View File

@@ -0,0 +1,27 @@
trait AppendBar {
fn append_bar(self) -> Self;
}
impl AppendBar for Vec<String> {
fn append_bar(mut self) -> Self {
// ^^^ this is important
self.push(String::from("Bar"));
self
}
}
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");
}
}

View File

@@ -0,0 +1,36 @@
trait Licensed {
fn licensing_info(&self) -> String {
"Default license".to_string()
}
}
struct SomeSoftware {
version_number: i32,
}
struct OtherSoftware {
version_number: String,
}
impl Licensed for SomeSoftware {}
impl Licensed for OtherSoftware {}
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);
}
}

View File

@@ -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 {}
fn compare_license_types(software1: impl Licensed, software2: impl Licensed) -> 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));
}
}

View File

@@ -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 {}
fn some_func(item: impl SomeTrait + OtherTrait) -> 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));
}
}

View File

@@ -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?
fn longest<'a>(x: &'a str, y: &'a str) -> &'a 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");
}
}

View File

@@ -0,0 +1,33 @@
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
fn main() {
let string1 = String::from("long string is long");
// Solution1: You can move `strings2` out of the inner block so that it is
// not dropped before the print statement.
let string2 = String::from("xyz");
let result;
{
result = longest(&string1, &string2);
}
println!("The longest string is '{result}'");
// `string2` dropped at the end of the function.
// =========================================================================
let string1 = String::from("long string is long");
let result;
{
let string2 = String::from("xyz");
result = longest(&string1, &string2);
// Solution2: You can move the print statement into the inner block so
// that it is executed before `string2` is dropped.
println!("The longest string is '{result}'");
// `string2` dropped here (end of the inner scope).
}
}

View File

@@ -0,0 +1,18 @@
// Lifetimes are also needed when structs hold references.
struct Book<'a> {
// ^^^^ added a lifetime annotation
author: &'a str,
// ^^
title: &'a str,
// ^^
}
fn main() {
let book = Book {
author: "George Orwell",
title: "1984",
};
println!("{} by {}", book.title, book.author);
}

View File

@@ -0,0 +1,24 @@
// 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 {
// When writing unit tests, it is common to import everything from the outer
// module (`super`) using a wildcard.
use super::*;
#[test]
fn you_can_assert() {
assert!(is_even(0));
assert!(!is_even(-1));
// ^ You can assert `false` using the negation operator `!`.
}
}

View File

@@ -0,0 +1,22 @@
// 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() {
assert_eq!(power_of_2(0), 1);
assert_eq!(power_of_2(1), 2);
assert_eq!(power_of_2(2), 4);
assert_eq!(power_of_2(3), 8);
}
}

View File

@@ -0,0 +1,45 @@
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() {
let rect = Rectangle::new(10, 20);
assert_eq!(rect.width, 10); // Check width
assert_eq!(rect.height, 20); // Check height
}
#[test]
#[should_panic] // Added this attribute to check that the test panics.
fn negative_width() {
let _rect = Rectangle::new(-10, 10);
}
#[test]
#[should_panic] // Added this attribute to check that the test panics.
fn negative_height() {
let _rect = Rectangle::new(10, -10);
}
}

View File

@@ -0,0 +1,26 @@
// 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"];
// Create an iterator over the array.
let mut fav_fruits_iterator = my_fav_fruits.iter();
assert_eq!(fav_fruits_iterator.next(), Some(&"banana"));
assert_eq!(fav_fruits_iterator.next(), Some(&"custard apple"));
assert_eq!(fav_fruits_iterator.next(), Some(&"avocado"));
assert_eq!(fav_fruits_iterator.next(), Some(&"peach"));
assert_eq!(fav_fruits_iterator.next(), Some(&"raspberry"));
assert_eq!(fav_fruits_iterator.next(), None);
// ^^^^ reached the end
}
}

View File

@@ -0,0 +1,56 @@
// In this exercise, you'll learn some of the unique advantages that iterators
// can offer.
// "hello" -> "Hello"
fn capitalize_first(input: &str) -> String {
let mut chars = input.chars();
match chars.next() {
None => String::new(),
Some(first) => first.to_uppercase().to_string() + chars.as_str(),
}
}
// 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> {
words.iter().map(|word| capitalize_first(word)).collect()
}
// 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 {
words.iter().map(|word| capitalize_first(word)).collect()
}
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");
}
}

View File

@@ -0,0 +1,86 @@
#[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,
}
fn divide(a: i64, b: i64) -> Result<i64, DivisionError> {
if b == 0 {
return Err(DivisionError::DivideByZero);
}
if a == i64::MIN && b == -1 {
return Err(DivisionError::IntegerOverflow);
}
if a % b != 0 {
return Err(DivisionError::NotDivisible);
}
Ok(a / b)
}
fn result_with_list() -> Result<Vec<i64>, DivisionError> {
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
let numbers = [27, 297, 38502, 81];
let division_results = numbers.into_iter().map(|n| divide(n, 27));
// Collects to the expected return type. Returns the first error in the
// division results (if one exists).
division_results.collect()
}
fn list_of_results() -> Vec<Result<i64, DivisionError>> {
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
let numbers = [27, 297, 38502, 81];
let division_results = numbers.into_iter().map(|n| divide(n, 27));
// Collects to the expected return type.
division_results.collect()
}
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)]);
}
}

View File

@@ -0,0 +1,72 @@
// 3 possible solutions are presented.
// With `for` loop and a mutable variable.
fn factorial_for(num: u64) -> u64 {
let mut result = 1;
for x in 2..=num {
result *= x;
}
result
}
// Equivalent to `factorial_for` but shorter and without a `for` loop and
// mutable variables.
fn factorial_fold(num: u64) -> u64 {
// Case num==0: The iterator 2..=0 is empty
// -> The initial value of `fold` is returned which is 1.
// Case num==1: The iterator 2..=1 is also empty
// -> The initial value 1 is returned.
// Case num==2: The iterator 2..=2 contains one element
// -> The initial value 1 is multiplied by 2 and the result
// is returned.
// Case num==3: The iterator 2..=3 contains 2 elements
// -> 1 * 2 is calculated, then the result 2 is multiplied by
// the second element 3 so the result 6 is returned.
// And so on…
#[allow(clippy::unnecessary_fold)]
(2..=num).fold(1, |acc, x| acc * x)
}
// Equivalent to `factorial_fold` but with a built-in method that is suggested
// by Clippy.
fn factorial_product(num: u64) -> u64 {
(2..=num).product()
}
fn main() {
// You can optionally experiment here.
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn factorial_of_0() {
assert_eq!(factorial_for(0), 1);
assert_eq!(factorial_fold(0), 1);
assert_eq!(factorial_product(0), 1);
}
#[test]
fn factorial_of_1() {
assert_eq!(factorial_for(1), 1);
assert_eq!(factorial_fold(1), 1);
assert_eq!(factorial_product(1), 1);
}
#[test]
fn factorial_of_2() {
assert_eq!(factorial_for(2), 2);
assert_eq!(factorial_fold(2), 2);
assert_eq!(factorial_product(2), 2);
}
#[test]
fn factorial_of_4() {
assert_eq!(factorial_for(4), 24);
assert_eq!(factorial_fold(4), 24);
assert_eq!(factorial_product(4), 24);
}
}

View File

@@ -0,0 +1,168 @@
// 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
}
fn count_iterator(map: &HashMap<String, Progress>, value: Progress) -> usize {
// `map` is a hash map with `String` keys and `Progress` values.
// map = { "variables1": Complete, "from_str": None, … }
map.values().filter(|val| **val == value).count()
}
fn count_collection_for(collection: &[HashMap<String, Progress>], value: Progress) -> usize {
let mut count = 0;
for map in collection {
count += count_for(map, value);
}
count
}
fn count_collection_iterator(collection: &[HashMap<String, Progress>], value: Progress) -> usize {
// `collection` is a slice of hash maps.
// collection = [{ "variables1": Complete, "from_str": None, … },
// { "variables2": Complete, … }, … ]
collection
.iter()
.map(|map| count_iterator(map, value))
.sum()
}
// Equivalent to `count_collection_iterator` and `count_iterator`, iterating as
// if the collection was a single container instead of a container of containers
// (and more accurately, a single iterator instead of an iterator of iterators).
fn count_collection_iterator_flat(
collection: &[HashMap<String, Progress>],
value: Progress,
) -> usize {
// `collection` is a slice of hash maps.
// collection = [{ "variables1": Complete, "from_str": None, … },
// { "variables2": Complete, … }, … ]
collection
.iter()
.flat_map(HashMap::values) // or just `.flatten()` when wanting the default iterator (`HashMap::iter`)
.filter(|val| **val == value)
.count()
}
fn main() {
// You can optionally experiment here.
}
#[cfg(test)]
mod tests {
use super::*;
use Progress::*;
fn get_map() -> HashMap<String, 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>> {
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, Complete), 3);
}
#[test]
fn count_some() {
let map = get_map();
assert_eq!(count_iterator(&map, Some), 1);
}
#[test]
fn count_none() {
let map = get_map();
assert_eq!(count_iterator(&map, None), 2);
}
#[test]
fn count_complete_equals_for() {
let map = get_map();
let progress_states = [Complete, Some, 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, Complete), 6);
assert_eq!(count_collection_iterator_flat(&collection, Complete), 6);
}
#[test]
fn count_collection_some() {
let collection = get_vec_map();
assert_eq!(count_collection_iterator(&collection, Some), 1);
assert_eq!(count_collection_iterator_flat(&collection, Some), 1);
}
#[test]
fn count_collection_none() {
let collection = get_vec_map();
assert_eq!(count_collection_iterator(&collection, None), 4);
assert_eq!(count_collection_iterator_flat(&collection, None), 4);
}
#[test]
fn count_collection_equals_for() {
let collection = get_vec_map();
let progress_states = [Complete, Some, None];
for progress_state in progress_states {
assert_eq!(
count_collection_for(&collection, progress_state),
count_collection_iterator(&collection, progress_state),
);
assert_eq!(
count_collection_for(&collection, progress_state),
count_collection_iterator_flat(&collection, progress_state),
);
}
}
}

View File

@@ -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();
let shared_numbers = Arc::new(numbers);
// ^^^^^^^^^^^^^^^^^
let mut join_handles = Vec::new();
for offset in 0..8 {
let child_numbers = Arc::clone(&shared_numbers);
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^
let handle = thread::spawn(move || {
let sum: u32 = child_numbers.iter().filter(|&&n| n % 8 == offset).sum();
println!("Sum of offset {offset} is {sum}");
});
join_handles.push(handle);
}
for handle in join_handles.into_iter() {
handle.join().unwrap();
}
}

View File

@@ -0,0 +1,47 @@
// 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`.
#[derive(PartialEq, Debug)]
enum List {
Cons(i32, Box<List>),
Nil,
}
fn create_empty_list() -> List {
List::Nil
}
fn create_non_empty_list() -> List {
List::Cons(42, Box::new(List::Nil))
}
fn main() {
println!("This is an empty cons list: {:?}", create_empty_list());
println!(
"This is a non-empty cons list: {:?}",
create_non_empty_list(),
);
}
#[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());
}
}

View File

@@ -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);
assert!(matches!(input, Cow::Borrowed(_)));
// ^^^^^^^^^^^^^^^^
}
#[test]
fn owned_no_mutation() {
// We can also pass `vec` without `&` so `Cow` owns it directly. In this
// case, no mutation occurs (all numbers are already absolute) and thus
// also no clone. But the result is still owned because it was never
// borrowed or mutated.
let vec = vec![0, 1, 2];
let mut input = Cow::from(vec);
abs_all(&mut input);
assert!(matches!(input, Cow::Owned(_)));
// ^^^^^^^^^^^^^
}
#[test]
fn owned_mutation() {
// Of course this is also the case if a mutation does occur (not all
// numbers are absolute). In this case, the call to `to_mut()` in the
// `abs_all` function returns a reference to the same data as before.
let vec = vec![-1, 0, 1];
let mut input = Cow::from(vec);
abs_all(&mut input);
assert!(matches!(input, Cow::Owned(_)));
// ^^^^^^^^^^^^^
}
}

View File

@@ -0,0 +1,104 @@
// 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;
#[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();
let saturn = Planet::Saturn(Rc::clone(&sun));
println!("reference count = {}", Rc::strong_count(&sun)); // 7 references
saturn.details();
// TODO
let uranus = Planet::Uranus(Rc::clone(&sun));
println!("reference count = {}", Rc::strong_count(&sun)); // 8 references
uranus.details();
// TODO
let neptune = Planet::Neptune(Rc::clone(&sun));
println!("reference count = {}", Rc::strong_count(&sun)); // 9 references
neptune.details();
assert_eq!(Rc::strong_count(&sun), 9);
drop(neptune);
println!("reference count = {}", Rc::strong_count(&sun)); // 8 references
drop(uranus);
println!("reference count = {}", Rc::strong_count(&sun)); // 7 references
drop(saturn);
println!("reference count = {}", Rc::strong_count(&sun)); // 6 references
drop(jupiter);
println!("reference count = {}", Rc::strong_count(&sun)); // 5 references
drop(mars);
println!("reference count = {}", Rc::strong_count(&sun)); // 4 references
drop(earth);
println!("reference count = {}", Rc::strong_count(&sun)); // 3 references
drop(venus);
println!("reference count = {}", Rc::strong_count(&sun)); // 2 references
drop(mercury);
println!("reference count = {}", Rc::strong_count(&sun)); // 1 reference
assert_eq!(Rc::strong_count(&sun), 1);
}
}

View File

@@ -0,0 +1,37 @@
// This program spawns multiple threads that each runs for at least 250ms, and
// each thread returns how much time it 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 {
// Collect the results of all threads into the `results` vector.
results.push(handle.join().unwrap());
}
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");
}
}

View File

@@ -0,0 +1,41 @@
// Building on the last exercise, we want all of the threads to complete their
// work. But this time, the spawned threads need to be in charge of updating a
// shared value: `JobStatus.jobs_done`
use std::{
sync::{Arc, Mutex},
thread,
time::Duration,
};
struct JobStatus {
jobs_done: u32,
}
fn main() {
// `Arc` isn't enough if you want a **mutable** shared state.
// We need to wrap the value with a `Mutex`.
let status = Arc::new(Mutex::new(JobStatus { jobs_done: 0 }));
// ^^^^^^^^^^^ ^
let mut handles = Vec::new();
for _ in 0..10 {
let status_shared = Arc::clone(&status);
let handle = thread::spawn(move || {
thread::sleep(Duration::from_millis(250));
// Lock before you update a shared value.
status_shared.lock().unwrap().jobs_done += 1;
// ^^^^^^^^^^^^^^^^
});
handles.push(handle);
}
// Waiting for all jobs to complete.
for handle in handles {
handle.join().unwrap();
}
println!("Jobs done: {}", status.lock().unwrap().jobs_done);
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
}

View File

@@ -0,0 +1,62 @@
use std::{sync::mpsc, thread, time::Duration};
struct Queue {
first_half: Vec<u32>,
second_half: Vec<u32>,
}
impl Queue {
fn new() -> Self {
Self {
first_half: vec![1, 2, 3, 4, 5],
second_half: vec![6, 7, 8, 9, 10],
}
}
}
fn send_tx(q: Queue, tx: mpsc::Sender<u32>) {
// Clone the sender `tx` first.
let tx_clone = tx.clone();
thread::spawn(move || {
for val in q.first_half {
println!("Sending {val:?}");
// Then use the clone in the first thread. This means that
// `tx_clone` is moved to the first thread and `tx` to the second.
tx_clone.send(val).unwrap();
thread::sleep(Duration::from_millis(250));
}
});
thread::spawn(move || {
for val in q.second_half {
println!("Sending {val:?}");
tx.send(val).unwrap();
thread::sleep(Duration::from_millis(250));
}
});
}
fn main() {
// You can optionally experiment here.
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn threads3() {
let (tx, rx) = mpsc::channel();
let queue = Queue::new();
send_tx(queue, tx);
let mut received = Vec::with_capacity(10);
for value in rx {
received.push(value);
}
received.sort();
assert_eq!(received, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
}
}

View File

@@ -0,0 +1,10 @@
macro_rules! my_macro {
() => {
println!("Check out my macro!");
};
}
fn main() {
my_macro!();
// ^
}

View File

@@ -0,0 +1,10 @@
// Moved the macro definition to be before its call.
macro_rules! my_macro {
() => {
println!("Check out my macro!");
};
}
fn main() {
my_macro!();
}

View File

@@ -0,0 +1,13 @@
// Added the attribute `macro_use` attribute.
#[macro_use]
mod macros {
macro_rules! my_macro {
() => {
println!("Check out my macro!");
};
}
}
fn main() {
my_macro!();
}

View File

@@ -0,0 +1,15 @@
// Added semicolons to separate the macro arms.
#[rustfmt::skip]
macro_rules! my_macro {
() => {
println!("Check out my macro!");
};
($val:expr) => {
println!("Look at this other macro: {}", $val);
};
}
fn main() {
my_macro!();
my_macro!(7777);
}

View File

@@ -0,0 +1,17 @@
// The Clippy tool is a collection of lints to analyze your code so you can
// catch common mistakes and improve your Rust code.
//
// For these exercises, the code will fail to compile when there are Clippy
// warnings. Check Clippy's suggestions from the output to solve the exercise.
use std::f32::consts::PI;
fn main() {
// Use the more accurate `PI` constant.
let pi = PI;
let radius: f32 = 5.0;
let area = pi * radius.powi(2);
println!("The area of a circle with radius {radius:.2} is {area:.5}");
}

View File

@@ -0,0 +1,10 @@
fn main() {
let mut res = 42;
let option = Some(12);
// Use `if-let` instead of iteration.
if let Some(x) = option {
res += x;
}
println!("{res}");
}

View File

@@ -0,0 +1,31 @@
use std::mem;
#[rustfmt::skip]
#[allow(unused_variables, unused_assignments)]
fn main() {
let my_option: Option<()> = None;
// `unwrap` of an `Option` after checking if it is `None` will panic.
// Use `if-let` instead.
if let Some(value) = my_option {
println!("{value:?}");
}
// A comma was missing.
let my_arr = &[
-1, -2, -3,
-4, -5, -6,
];
println!("My array! Here it is: {:?}", my_arr);
let mut my_empty_vec = vec![1, 2, 3, 4, 5];
// `resize` mutates a vector instead of returning a new one.
// `resize(0, …)` clears a vector, so it is better to use `clear`.
my_empty_vec.clear();
println!("This Vec is empty, see? {my_empty_vec:?}");
let mut value_a = 45;
let mut value_b = 66;
// Use `mem::swap` to correctly swap two values.
mem::swap(&mut value_a, &mut value_b);
println!("value a: {}; value b: {}", value_a, value_b);
}

View File

@@ -0,0 +1,60 @@
// AsRef and AsMut allow for cheap reference-to-reference conversions. Read more
// about them at https://doc.rust-lang.org/std/convert/trait.AsRef.html and
// https://doc.rust-lang.org/std/convert/trait.AsMut.html, respectively.
// Obtain the number of bytes (not characters) in the given argument
// (`.len()` returns the number of bytes in a string).
fn byte_counter<T: AsRef<str>>(arg: T) -> usize {
arg.as_ref().len()
}
// Obtain the number of characters (not bytes) in the given argument.
fn char_counter<T: AsRef<str>>(arg: T) -> usize {
arg.as_ref().chars().count()
}
// Squares a number using `as_mut()`.
fn num_sq<T: AsMut<u32>>(arg: &mut T) {
let arg = arg.as_mut();
*arg *= *arg;
}
fn main() {
// You can optionally experiment here.
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn different_counts() {
let s = "Café au lait";
assert_ne!(char_counter(s), byte_counter(s));
}
#[test]
fn same_counts() {
let s = "Cafe au lait";
assert_eq!(char_counter(s), byte_counter(s));
}
#[test]
fn different_counts_using_string() {
let s = String::from("Café au lait");
assert_ne!(char_counter(s.clone()), byte_counter(s));
}
#[test]
fn same_counts_using_string() {
let s = String::from("Cafe au lait");
assert_eq!(char_counter(s.clone()), byte_counter(s));
}
#[test]
fn mut_box() {
let mut num: Box<u32> = Box::new(3);
num_sq(&mut num);
assert_eq!(*num, 9);
}
}

View File

@@ -0,0 +1,136 @@
// The `From` trait is used for value-to-value conversions. If `From` is
// implemented, an implementation of `Into` is automatically provided.
// You can read more about it in the documentation:
// https://doc.rust-lang.org/std/convert/trait.From.html
#[derive(Debug)]
struct Person {
name: String,
age: u8,
}
// We implement the Default trait to use it as a fallback when the provided
// string is not convertible into a `Person` object.
impl Default for Person {
fn default() -> Self {
Self {
name: String::from("John"),
age: 30,
}
}
}
impl From<&str> for Person {
fn from(s: &str) -> Self {
let mut split = s.split(',');
let (Some(name), Some(age), None) = (split.next(), split.next(), split.next()) else {
// ^^^^ there should be no third element
return Self::default();
};
if name.is_empty() {
return Self::default();
}
let Ok(age) = age.parse() else {
return Self::default();
};
Self {
name: name.into(),
age,
}
}
}
fn main() {
// Use the `from` function.
let p1 = Person::from("Mark,20");
println!("{p1:?}");
// Since `From` is implemented for Person, we are able to use `Into`.
let p2: Person = "Gerald,70".into();
println!("{p2:?}");
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default() {
let dp = Person::default();
assert_eq!(dp.name, "John");
assert_eq!(dp.age, 30);
}
#[test]
fn test_bad_convert() {
let p = Person::from("");
assert_eq!(p.name, "John");
assert_eq!(p.age, 30);
}
#[test]
fn test_good_convert() {
let p = Person::from("Mark,20");
assert_eq!(p.name, "Mark");
assert_eq!(p.age, 20);
}
#[test]
fn test_bad_age() {
let p = Person::from("Mark,twenty");
assert_eq!(p.name, "John");
assert_eq!(p.age, 30);
}
#[test]
fn test_missing_comma_and_age() {
let p: Person = Person::from("Mark");
assert_eq!(p.name, "John");
assert_eq!(p.age, 30);
}
#[test]
fn test_missing_age() {
let p: Person = Person::from("Mark,");
assert_eq!(p.name, "John");
assert_eq!(p.age, 30);
}
#[test]
fn test_missing_name() {
let p: Person = Person::from(",1");
assert_eq!(p.name, "John");
assert_eq!(p.age, 30);
}
#[test]
fn test_missing_name_and_age() {
let p: Person = Person::from(",");
assert_eq!(p.name, "John");
assert_eq!(p.age, 30);
}
#[test]
fn test_missing_name_and_invalid_age() {
let p: Person = Person::from(",one");
assert_eq!(p.name, "John");
assert_eq!(p.age, 30);
}
#[test]
fn test_trailing_comma() {
let p: Person = Person::from("Mike,32,");
assert_eq!(p.name, "John");
assert_eq!(p.age, 30);
}
#[test]
fn test_trailing_comma_and_some_string() {
let p: Person = Person::from("Mike,32,dog");
assert_eq!(p.name, "John");
assert_eq!(p.age, 30);
}
}

View File

@@ -0,0 +1,117 @@
// This is similar to the previous `from_into` exercise. But this time, we'll
// implement `FromStr` and return errors instead of falling back to a default
// value. Additionally, upon implementing `FromStr`, you can use the `parse`
// method on strings to generate an object of the implementor type. You can read
// more about it in the documentation:
// https://doc.rust-lang.org/std/str/trait.FromStr.html
use std::num::ParseIntError;
use std::str::FromStr;
#[derive(Debug, PartialEq)]
struct Person {
name: String,
age: u8,
}
// We will use this error type for the `FromStr` implementation.
#[derive(Debug, PartialEq)]
enum ParsePersonError {
// Incorrect number of fields
BadLen,
// Empty name field
NoName,
// Wrapped error from parse::<u8>()
ParseInt(ParseIntError),
}
impl FromStr for Person {
type Err = ParsePersonError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut split = s.split(',');
let (Some(name), Some(age), None) = (split.next(), split.next(), split.next()) else {
// ^^^^ there should be no third element
return Err(ParsePersonError::BadLen);
};
if name.is_empty() {
return Err(ParsePersonError::NoName);
}
let age = age.parse().map_err(ParsePersonError::ParseInt)?;
Ok(Self {
name: name.into(),
age,
})
}
}
fn main() {
let p = "Mark,20".parse::<Person>();
println!("{p:?}");
}
#[cfg(test)]
mod tests {
use super::*;
use ParsePersonError::*;
#[test]
fn empty_input() {
assert_eq!("".parse::<Person>(), Err(BadLen));
}
#[test]
fn good_input() {
let p = "John,32".parse::<Person>();
assert!(p.is_ok());
let p = p.unwrap();
assert_eq!(p.name, "John");
assert_eq!(p.age, 32);
}
#[test]
fn missing_age() {
assert!(matches!("John,".parse::<Person>(), Err(ParseInt(_))));
}
#[test]
fn invalid_age() {
assert!(matches!("John,twenty".parse::<Person>(), Err(ParseInt(_))));
}
#[test]
fn missing_comma_and_age() {
assert_eq!("John".parse::<Person>(), Err(BadLen));
}
#[test]
fn missing_name() {
assert_eq!(",1".parse::<Person>(), Err(NoName));
}
#[test]
fn missing_name_and_age() {
assert!(matches!(",".parse::<Person>(), Err(NoName | ParseInt(_))));
}
#[test]
fn missing_name_and_invalid_age() {
assert!(matches!(
",one".parse::<Person>(),
Err(NoName | ParseInt(_)),
));
}
#[test]
fn trailing_comma() {
assert_eq!("John,32,".parse::<Person>(), Err(BadLen));
}
#[test]
fn trailing_comma_and_some_string() {
assert_eq!("John,32,man".parse::<Person>(), Err(BadLen));
}
}

View File

@@ -0,0 +1,193 @@
// `TryFrom` is a simple and safe type conversion that may fail in a controlled
// way under some circumstances. Basically, this is the same as `From`. The main
// difference is that this should return a `Result` type instead of the target
// type itself. You can read more about it in the documentation:
// https://doc.rust-lang.org/std/convert/trait.TryFrom.html
#![allow(clippy::useless_vec)]
use std::convert::{TryFrom, TryInto};
#[derive(Debug, PartialEq)]
struct Color {
red: u8,
green: u8,
blue: u8,
}
// We will use this error type for the `TryFrom` conversions.
#[derive(Debug, PartialEq)]
enum IntoColorError {
// Incorrect length of slice
BadLen,
// Integer conversion error
IntConversion,
}
impl TryFrom<(i16, i16, i16)> for Color {
type Error = IntoColorError;
fn try_from(tuple: (i16, i16, i16)) -> Result<Self, Self::Error> {
let (Ok(red), Ok(green), Ok(blue)) = (
u8::try_from(tuple.0),
u8::try_from(tuple.1),
u8::try_from(tuple.2),
) else {
return Err(IntoColorError::IntConversion);
};
Ok(Self { red, green, blue })
}
}
impl TryFrom<[i16; 3]> for Color {
type Error = IntoColorError;
fn try_from(arr: [i16; 3]) -> Result<Self, Self::Error> {
// Reuse the implementation for a tuple.
Self::try_from((arr[0], arr[1], arr[2]))
}
}
impl TryFrom<&[i16]> for Color {
type Error = IntoColorError;
fn try_from(slice: &[i16]) -> Result<Self, Self::Error> {
// Check the length.
if slice.len() != 3 {
return Err(IntoColorError::BadLen);
}
// Reuse the implementation for a tuple.
Self::try_from((slice[0], slice[1], slice[2]))
}
}
fn main() {
// Using the `try_from` function.
let c1 = Color::try_from((183, 65, 14));
println!("{c1:?}");
// Since `TryFrom` is implemented for `Color`, we can use `TryInto`.
let c2: Result<Color, _> = [183, 65, 14].try_into();
println!("{c2:?}");
let v = vec![183, 65, 14];
// With slice we should use the `try_from` function
let c3 = Color::try_from(&v[..]);
println!("{c3:?}");
// or put the slice within round brackets and use `try_into`.
let c4: Result<Color, _> = (&v[..]).try_into();
println!("{c4:?}");
}
#[cfg(test)]
mod tests {
use super::*;
use IntoColorError::*;
#[test]
fn test_tuple_out_of_range_positive() {
assert_eq!(Color::try_from((256, 1000, 10000)), Err(IntConversion));
}
#[test]
fn test_tuple_out_of_range_negative() {
assert_eq!(Color::try_from((-1, -10, -256)), Err(IntConversion));
}
#[test]
fn test_tuple_sum() {
assert_eq!(Color::try_from((-1, 255, 255)), Err(IntConversion));
}
#[test]
fn test_tuple_correct() {
let c: Result<Color, _> = (183, 65, 14).try_into();
assert!(c.is_ok());
assert_eq!(
c.unwrap(),
Color {
red: 183,
green: 65,
blue: 14,
}
);
}
#[test]
fn test_array_out_of_range_positive() {
let c: Result<Color, _> = [1000, 10000, 256].try_into();
assert_eq!(c, Err(IntConversion));
}
#[test]
fn test_array_out_of_range_negative() {
let c: Result<Color, _> = [-10, -256, -1].try_into();
assert_eq!(c, Err(IntConversion));
}
#[test]
fn test_array_sum() {
let c: Result<Color, _> = [-1, 255, 255].try_into();
assert_eq!(c, Err(IntConversion));
}
#[test]
fn test_array_correct() {
let c: Result<Color, _> = [183, 65, 14].try_into();
assert!(c.is_ok());
assert_eq!(
c.unwrap(),
Color {
red: 183,
green: 65,
blue: 14
}
);
}
#[test]
fn test_slice_out_of_range_positive() {
let arr = [10000, 256, 1000];
assert_eq!(Color::try_from(&arr[..]), Err(IntConversion));
}
#[test]
fn test_slice_out_of_range_negative() {
let arr = [-256, -1, -10];
assert_eq!(Color::try_from(&arr[..]), Err(IntConversion));
}
#[test]
fn test_slice_sum() {
let arr = [-1, 255, 255];
assert_eq!(Color::try_from(&arr[..]), Err(IntConversion));
}
#[test]
fn test_slice_correct() {
let v = vec![183, 65, 14];
let c: Result<Color, _> = Color::try_from(&v[..]);
assert!(c.is_ok());
assert_eq!(
c.unwrap(),
Color {
red: 183,
green: 65,
blue: 14,
}
);
}
#[test]
fn test_slice_excess_length() {
let v = vec![0, 0, 0, 0];
assert_eq!(Color::try_from(&v[..]), Err(BadLen));
}
#[test]
fn test_slice_insufficient_length() {
let v = vec![0, 0];
assert_eq!(Color::try_from(&v[..]), Err(BadLen));
}
}

View File

@@ -0,0 +1,24 @@
// Type casting in Rust is done via the usage of the `as` operator.
// Note that the `as` operator is not only used when type casting. It also helps
// with renaming imports.
fn average(values: &[f64]) -> f64 {
let total = values.iter().sum::<f64>();
total / values.len() as f64
// ^^^^^^
}
fn main() {
let values = [3.5, 0.3, 13.0, 11.7];
println!("{}", average(&values));
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn returns_proper_type_and_value() {
assert_eq!(average(&[3.5, 0.3, 13.0, 11.7]), 7.125);
}
}

6
solutions/README.md Normal file
View File

@@ -0,0 +1,6 @@
# Official Rustlings solutions
Before you finish an exercise, its solution file will only contain an empty `main` function.
The content of this file will be automatically replaced by the actual solution once you finish the exercise.
Note that these solutions are often only _one possibility_ to solve an exercise.

View File

@@ -0,0 +1,30 @@
// Mary is buying apples. The price of an apple is calculated as follows:
// - An apple costs 2 rustbucks.
// - However, if Mary buys more than 40 apples, the price of each apple in the
// entire order is reduced to only 1 rustbuck!
fn calculate_price_of_apples(n_apples: u64) -> u64 {
if n_apples > 40 {
n_apples
} else {
2 * n_apples
}
}
fn main() {
// You can optionally experiment here.
}
// Don't change the tests!
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn verify_test() {
assert_eq!(calculate_price_of_apples(35), 70);
assert_eq!(calculate_price_of_apples(40), 80);
assert_eq!(calculate_price_of_apples(41), 41);
assert_eq!(calculate_price_of_apples(65), 65);
}
}

View File

@@ -0,0 +1,90 @@
// Let's build a little machine in the form of a function. As input, we're going
// to give a list of strings and commands. These commands determine what action
// is going to be applied to the string. It can either be:
// - Uppercase the string
// - Trim the string
// - Append "bar" to the string a specified amount of times
//
// The exact form of this will be:
// - The input is going to be a vector of 2-length tuples,
// the first element is the string, the second one is the command.
// - The output element is going to be a vector of strings.
enum Command {
Uppercase,
Trim,
Append(usize),
}
mod my_module {
use super::Command;
// The solution with a loop. Check out `transformer_iter` for a version
// with iterators.
pub fn transformer(input: Vec<(String, Command)>) -> Vec<String> {
let mut output = Vec::new();
for (string, command) in input {
// Create the new string.
let new_string = match command {
Command::Uppercase => string.to_uppercase(),
Command::Trim => string.trim().to_string(),
Command::Append(n) => string + &"bar".repeat(n),
};
// Push the new string to the output vector.
output.push(new_string);
}
output
}
// Equivalent to `transform` but uses an iterator instead of a loop for
// comparison. Don't worry, we will practice iterators later ;)
pub fn transformer_iter(input: Vec<(String, Command)>) -> Vec<String> {
input
.into_iter()
.map(|(string, command)| match command {
Command::Uppercase => string.to_uppercase(),
Command::Trim => string.trim().to_string(),
Command::Append(n) => string + &"bar".repeat(n),
})
.collect()
}
}
fn main() {
// You can optionally experiment here.
}
#[cfg(test)]
mod tests {
// Import `transformer`.
use super::my_module::transformer;
use super::my_module::transformer_iter;
use super::Command;
#[test]
fn it_works() {
for transformer in [transformer, transformer_iter] {
let input = vec![
("hello".to_string(), Command::Uppercase),
(" all roads lead to rome! ".to_string(), Command::Trim),
("foo".to_string(), Command::Append(1)),
("bar".to_string(), Command::Append(5)),
];
let output = transformer(input);
assert_eq!(
output,
[
"HELLO",
"all roads lead to rome!",
"foobar",
"barbarbarbarbarbar",
]
);
}
}
}

View File

@@ -0,0 +1,65 @@
// An imaginary magical school has a new report card generation system written
// in Rust! Currently, the system only supports creating report cards where the
// student's grade is represented numerically (e.g. 1.0 -> 5.5). However, the
// school also issues alphabetical grades (A+ -> F-) and needs to be able to
// print both types of report card!
//
// Make the necessary code changes in the struct `ReportCard` and the impl
// block to support alphabetical report cards in addition to numerical ones.
use std::fmt::Display;
// Make the struct generic over `T`.
struct ReportCard<T> {
// ^^^
grade: T,
// ^
student_name: String,
student_age: u8,
}
// To be able to print the grade, it has to implement the `Display` trait.
impl<T: Display> ReportCard<T> {
// ^^^^^^^ require that `T` implements `Display`.
fn print(&self) -> String {
format!(
"{} ({}) - achieved a grade of {}",
&self.student_name, &self.student_age, &self.grade,
)
}
}
fn main() {
// You can optionally experiment here.
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn generate_numeric_report_card() {
let report_card = ReportCard {
grade: 2.1,
student_name: "Tom Wriggle".to_string(),
student_age: 12,
};
assert_eq!(
report_card.print(),
"Tom Wriggle (12) - achieved a grade of 2.1",
);
}
#[test]
fn generate_alphabetic_report_card() {
let report_card = ReportCard {
grade: "A+",
student_name: "Gary Plotter".to_string(),
student_age: 11,
};
assert_eq!(
report_card.print(),
"Gary Plotter (11) - achieved a grade of A+",
);
}
}