ye
This commit is contained in:
parent
c971a855c8
commit
ba17002994
135 changed files with 4261 additions and 0 deletions
7
clippy/Cargo.lock
generated
Normal file
7
clippy/Cargo.lock
generated
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clippy3"
|
||||||
|
version = "0.0.1"
|
7
clippy/Cargo.toml
Normal file
7
clippy/Cargo.toml
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
[package]
|
||||||
|
name = "clippy3"
|
||||||
|
version = "0.0.1"
|
||||||
|
edition = "2021"
|
||||||
|
[[bin]]
|
||||||
|
name = "clippy3"
|
||||||
|
path = "clippy3.rs"
|
10
clippy/README.md
Normal file
10
clippy/README.md
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
# Clippy
|
||||||
|
|
||||||
|
The Clippy tool is a collection of lints to analyze your code so you can catch common mistakes and improve your Rust code.
|
||||||
|
|
||||||
|
If you used the installation script for Rustlings, Clippy should be already installed.
|
||||||
|
If not you can install it manually via `rustup component add clippy`.
|
||||||
|
|
||||||
|
## Further information
|
||||||
|
|
||||||
|
- [GitHub Repository](https://github.com/rust-lang/rust-clippy).
|
24
clippy/clippy1.rs
Normal file
24
clippy/clippy1.rs
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
// clippy1.rs
|
||||||
|
//
|
||||||
|
// The Clippy tool is a collection of lints to analyze your code so you can
|
||||||
|
// catch common mistakes and improve your Rust code.
|
||||||
|
//
|
||||||
|
// For these exercises the code will fail to compile when there are clippy
|
||||||
|
// warnings check clippy's suggestions from the output to solve the exercise.
|
||||||
|
//
|
||||||
|
// Execute `rustlings hint clippy1` or use the `hint` watch subcommand for a
|
||||||
|
// hint.
|
||||||
|
|
||||||
|
use std::f32;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let pi = std::f32::consts::PI;
|
||||||
|
let radius = 5.00f32;
|
||||||
|
|
||||||
|
let area = pi * f32::powi(radius, 2);
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"The area of a circle with radius {:.2} is {:.5}!",
|
||||||
|
radius, area
|
||||||
|
)
|
||||||
|
}
|
13
clippy/clippy2.rs
Normal file
13
clippy/clippy2.rs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
// clippy2.rs
|
||||||
|
//
|
||||||
|
// Execute `rustlings hint clippy2` or use the `hint` watch subcommand for a
|
||||||
|
// hint.
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut res = 42;
|
||||||
|
let option = Some(12);
|
||||||
|
if let Some(x) = option {
|
||||||
|
res += x;
|
||||||
|
}
|
||||||
|
println!("{}", res);
|
||||||
|
}
|
24
clippy/clippy3.rs
Normal file
24
clippy/clippy3.rs
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
// clippy3.rs
|
||||||
|
//
|
||||||
|
// Here's a couple more easy Clippy fixes, so you can see its utility.
|
||||||
|
// No hints.
|
||||||
|
|
||||||
|
#[allow(unused_variables, unused_assignments)]
|
||||||
|
fn main() {
|
||||||
|
let my_option: Option<()> = None;
|
||||||
|
|
||||||
|
let my_arr = &[
|
||||||
|
-1, -2, -3,
|
||||||
|
-4, -5, -6,
|
||||||
|
];
|
||||||
|
println!("My array! Here it is: {:?}", my_arr);
|
||||||
|
|
||||||
|
//let my_empty_vec = vec![1, 2, 3, 4, 5].resize(0, 5);
|
||||||
|
//println!("This Vec is empty, see? {:?}", my_empty_vec);
|
||||||
|
|
||||||
|
let mut value_a = 45;
|
||||||
|
let mut value_b = 66;
|
||||||
|
// Let's swap these two!
|
||||||
|
std::mem::swap(&mut value_a, &mut value_b);
|
||||||
|
println!("value a: {}; value b: {}", value_a, value_b);
|
||||||
|
}
|
1
clippy/target/.rustc_info.json
Normal file
1
clippy/target/.rustc_info.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{"rustc_fingerprint":12824587143487430529,"outputs":{"18303909016113816085":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.so\nlib___.so\nlib___.a\nlib___.so\n/home/adam/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu\noff\npacked\nunpacked\n___\ndebug_assertions\nfeature=\"cargo-clippy\"\noverflow_checks\npanic=\"unwind\"\nproc_macro\nrelocation_model=\"pic\"\ntarget_abi=\"\"\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"gnu\"\ntarget_family=\"unix\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_has_atomic\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_has_atomic_equal_alignment=\"16\"\ntarget_has_atomic_equal_alignment=\"32\"\ntarget_has_atomic_equal_alignment=\"64\"\ntarget_has_atomic_equal_alignment=\"8\"\ntarget_has_atomic_equal_alignment=\"ptr\"\ntarget_has_atomic_load_store\ntarget_has_atomic_load_store=\"16\"\ntarget_has_atomic_load_store=\"32\"\ntarget_has_atomic_load_store=\"64\"\ntarget_has_atomic_load_store=\"8\"\ntarget_has_atomic_load_store=\"ptr\"\ntarget_os=\"linux\"\ntarget_pointer_width=\"64\"\ntarget_thread_local\ntarget_vendor=\"unknown\"\nunix\n","stderr":""},"4614504638168534921":{"success":true,"status":"","code":0,"stdout":"rustc 1.76.0-nightly (f704f3b93 2023-12-19)\nbinary: rustc\ncommit-hash: f704f3b93b1543cf504ecca0052f9f8531b1f61f\ncommit-date: 2023-12-19\nhost: x86_64-unknown-linux-gnu\nrelease: 1.76.0-nightly\nLLVM version: 17.0.6\n","stderr":""}},"successes":{}}
|
3
clippy/target/CACHEDIR.TAG
Normal file
3
clippy/target/CACHEDIR.TAG
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
Signature: 8a477f597d28d172789f06886806bc55
|
||||||
|
# This file is a cache directory tag created by cargo.
|
||||||
|
# For information about cache directory tags see https://bford.info/cachedir/
|
0
clippy/target/debug/.cargo-lock
Normal file
0
clippy/target/debug/.cargo-lock
Normal file
|
@ -0,0 +1 @@
|
||||||
|
a65d8094a4f80f39
|
|
@ -0,0 +1 @@
|
||||||
|
{"rustc":4208557171189394848,"features":"[]","declared_features":"","target":14064950887575378383,"profile":5601947868832436996,"path":2386747541556052359,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/clippy3-ffc27d43dbc4dd6f/dep-bin-clippy3"}}],"rustflags":[],"metadata":7797948686568424061,"config":2202906307356721367,"compile_kind":0}
|
Binary file not shown.
|
@ -0,0 +1 @@
|
||||||
|
This file has an mtime of when this was started.
|
9
clippy/target/debug/deps/clippy3-ffc27d43dbc4dd6f.d
Normal file
9
clippy/target/debug/deps/clippy3-ffc27d43dbc4dd6f.d
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
/home/adam/projects/rust/rustlings/rustlings/exercises/clippy/target/debug/deps/libclippy3-ffc27d43dbc4dd6f.rmeta: clippy3.rs Cargo.toml
|
||||||
|
|
||||||
|
/home/adam/projects/rust/rustlings/rustlings/exercises/clippy/target/debug/deps/clippy3-ffc27d43dbc4dd6f.d: clippy3.rs Cargo.toml
|
||||||
|
|
||||||
|
clippy3.rs:
|
||||||
|
Cargo.toml:
|
||||||
|
|
||||||
|
# env-dep:CLIPPY_ARGS=-D__CLIPPY_HACKERY__warnings__CLIPPY_HACKERY__-D__CLIPPY_HACKERY__clippy::float_cmp__CLIPPY_HACKERY__
|
||||||
|
# env-dep:CLIPPY_CONF_DIR
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
23
conversions/README.md
Normal file
23
conversions/README.md
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
# Type conversions
|
||||||
|
|
||||||
|
Rust offers a multitude of ways to convert a value of a given type into another type.
|
||||||
|
|
||||||
|
The simplest form of type conversion is a type cast expression. It is denoted with the binary operator `as`. For instance, `println!("{}", 1 + 1.0);` would not compile, since `1` is an integer while `1.0` is a float. However, `println!("{}", 1 as f32 + 1.0)` should compile. The exercise [`using_as`](using_as.rs) tries to cover this.
|
||||||
|
|
||||||
|
Rust also offers traits that facilitate type conversions upon implementation. These traits can be found under the [`convert`](https://doc.rust-lang.org/std/convert/index.html) module.
|
||||||
|
The traits are the following:
|
||||||
|
|
||||||
|
- `From` and `Into` covered in [`from_into`](from_into.rs)
|
||||||
|
- `TryFrom` and `TryInto` covered in [`try_from_into`](try_from_into.rs)
|
||||||
|
- `AsRef` and `AsMut` covered in [`as_ref_mut`](as_ref_mut.rs)
|
||||||
|
|
||||||
|
Furthermore, the `std::str` module offers a trait called [`FromStr`](https://doc.rust-lang.org/std/str/trait.FromStr.html) which helps with converting strings into target types via the `parse` method on strings. If properly implemented for a given type `Person`, then `let p: Person = "Mark,20".parse().unwrap()` should both compile and run without panicking.
|
||||||
|
|
||||||
|
These should be the main ways ***within the standard library*** to convert data into your desired types.
|
||||||
|
|
||||||
|
## Further information
|
||||||
|
|
||||||
|
These are not directly covered in the book, but the standard library has a great documentation for it.
|
||||||
|
|
||||||
|
- [conversions](https://doc.rust-lang.org/std/convert/index.html)
|
||||||
|
- [`FromStr` trait](https://doc.rust-lang.org/std/str/trait.FromStr.html)
|
63
conversions/as_ref_mut.rs
Normal file
63
conversions/as_ref_mut.rs
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
// as_ref_mut.rs
|
||||||
|
//
|
||||||
|
// AsRef and AsMut allow for cheap reference-to-reference conversions. Read more
|
||||||
|
// about them at https://doc.rust-lang.org/std/convert/trait.AsRef.html and
|
||||||
|
// https://doc.rust-lang.org/std/convert/trait.AsMut.html, respectively.
|
||||||
|
//
|
||||||
|
// Execute `rustlings hint as_ref_mut` or use the `hint` watch subcommand for a
|
||||||
|
// hint.
|
||||||
|
|
||||||
|
// Obtain the number of bytes (not characters) in the given argument.
|
||||||
|
// TODO: Add the AsRef trait appropriately as a trait bound.
|
||||||
|
fn byte_counter<T: AsRef<str>>(arg: T) -> usize {
|
||||||
|
arg.as_ref().as_bytes().len()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Obtain the number of characters (not bytes) in the given argument.
|
||||||
|
// TODO: Add the AsRef trait appropriately as a trait bound.
|
||||||
|
fn char_counter<T: AsRef<str>>(arg: T) -> usize {
|
||||||
|
arg.as_ref().chars().count()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Squares a number using as_mut().
|
||||||
|
// TODO: Add the appropriate trait bound.
|
||||||
|
fn num_sq<T: AsMut<u32>>(arg: &mut T) {
|
||||||
|
// TODO: Implement the function body.
|
||||||
|
*arg.as_mut() *= *arg.as_mut()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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);
|
||||||
|
}
|
||||||
|
}
|
153
conversions/from_into.rs
Normal file
153
conversions/from_into.rs
Normal file
|
@ -0,0 +1,153 @@
|
||||||
|
// from_into.rs
|
||||||
|
//
|
||||||
|
// The From trait is used for value-to-value conversions. If From is implemented
|
||||||
|
// correctly for a type, the Into trait should work conversely. You can read
|
||||||
|
// more about it at https://doc.rust-lang.org/std/convert/trait.From.html
|
||||||
|
//
|
||||||
|
// Execute `rustlings hint from_into` or use the `hint` watch subcommand for a
|
||||||
|
// hint.
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Person {
|
||||||
|
name: String,
|
||||||
|
age: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
// We implement the Default trait to use it as a fallback
|
||||||
|
// when the provided string is not convertible into a Person object
|
||||||
|
impl Default for Person {
|
||||||
|
fn default() -> Person {
|
||||||
|
Person {
|
||||||
|
name: String::from("John"),
|
||||||
|
age: 30,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Your task is to complete this implementation in order for the line `let p =
|
||||||
|
// Person::from("Mark,20")` to compile Please note that you'll need to parse the
|
||||||
|
// age component into a `usize` with something like `"4".parse::<usize>()`. The
|
||||||
|
// outcome of this needs to be handled appropriately.
|
||||||
|
//
|
||||||
|
// Steps:
|
||||||
|
// 1. If the length of the provided string is 0, then return the default of
|
||||||
|
// Person.
|
||||||
|
// 2. Split the given string on the commas present in it.
|
||||||
|
// 3. Extract the first element from the split operation and use it as the name.
|
||||||
|
// 4. If the name is empty, then return the default of Person.
|
||||||
|
// 5. Extract the other element from the split operation and parse it into a
|
||||||
|
// `usize` as the age.
|
||||||
|
// If while parsing the age, something goes wrong, then return the default of
|
||||||
|
// Person Otherwise, then return an instantiated Person object with the results
|
||||||
|
|
||||||
|
impl From<&str> for Person {
|
||||||
|
fn from(s: &str) -> Self {
|
||||||
|
let parts = s.split(',').collect::<Vec<&str>>();
|
||||||
|
if parts.len() < 2 {
|
||||||
|
Person::default()
|
||||||
|
} else {
|
||||||
|
match parts[..2] {
|
||||||
|
[name, age] if !name.is_empty() => age
|
||||||
|
.parse()
|
||||||
|
.map(|age| Self {
|
||||||
|
name: name.to_string(),
|
||||||
|
age,
|
||||||
|
})
|
||||||
|
.unwrap_or_default(),
|
||||||
|
_ => Self::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// Use the `from` function
|
||||||
|
let p1 = Person::from("Mark,20");
|
||||||
|
// Since From is implemented for Person, we should be able to use Into
|
||||||
|
let p2: Person = "Gerald,70".into();
|
||||||
|
println!("{:?}", p1);
|
||||||
|
println!("{:?}", p2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
#[test]
|
||||||
|
fn test_default() {
|
||||||
|
// Test that the default person is 30 year old John
|
||||||
|
let dp = Person::default();
|
||||||
|
assert_eq!(dp.name, "John");
|
||||||
|
assert_eq!(dp.age, 30);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_bad_convert() {
|
||||||
|
// Test that John is returned when bad string is provided
|
||||||
|
let p = Person::from("");
|
||||||
|
assert_eq!(p.name, "John");
|
||||||
|
assert_eq!(p.age, 30);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_good_convert() {
|
||||||
|
// Test that "Mark,20" works
|
||||||
|
let p = Person::from("Mark,20");
|
||||||
|
assert_eq!(p.name, "Mark");
|
||||||
|
assert_eq!(p.age, 20);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_bad_age() {
|
||||||
|
// Test that "Mark,twenty" will return the default person due to an
|
||||||
|
// error in parsing age
|
||||||
|
let p = Person::from("Mark,twenty");
|
||||||
|
assert_eq!(p.name, "John");
|
||||||
|
assert_eq!(p.age, 30);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_missing_comma_and_age() {
|
||||||
|
let p: Person = Person::from("Mark");
|
||||||
|
assert_eq!(p.name, "John");
|
||||||
|
assert_eq!(p.age, 30);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_missing_age() {
|
||||||
|
let p: Person = Person::from("Mark,");
|
||||||
|
assert_eq!(p.name, "John");
|
||||||
|
assert_eq!(p.age, 30);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_missing_name() {
|
||||||
|
let p: Person = Person::from(",1");
|
||||||
|
assert_eq!(p.name, "John");
|
||||||
|
assert_eq!(p.age, 30);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_missing_name_and_age() {
|
||||||
|
let p: Person = Person::from(",");
|
||||||
|
assert_eq!(p.name, "John");
|
||||||
|
assert_eq!(p.age, 30);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_missing_name_and_invalid_age() {
|
||||||
|
let p: Person = Person::from(",one");
|
||||||
|
assert_eq!(p.name, "John");
|
||||||
|
assert_eq!(p.age, 30);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_trailing_comma() {
|
||||||
|
let p: Person = Person::from("Mike,32,");
|
||||||
|
assert_eq!(p.name, "Mike");
|
||||||
|
assert_eq!(p.age, 32);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_trailing_comma_and_some_string() {
|
||||||
|
let p: Person = Person::from("Mike,32,man");
|
||||||
|
assert_eq!(p.name, "Mike");
|
||||||
|
assert_eq!(p.age, 32);
|
||||||
|
}
|
||||||
|
}
|
147
conversions/from_str.rs
Normal file
147
conversions/from_str.rs
Normal file
|
@ -0,0 +1,147 @@
|
||||||
|
// from_str.rs
|
||||||
|
//
|
||||||
|
// This is similar to from_into.rs, but this time we'll implement `FromStr` and
|
||||||
|
// return errors instead of falling back to a default value. Additionally, upon
|
||||||
|
// implementing FromStr, you can use the `parse` method on strings to generate
|
||||||
|
// an object of the implementor type. You can read more about it at
|
||||||
|
// https://doc.rust-lang.org/std/str/trait.FromStr.html
|
||||||
|
//
|
||||||
|
// Execute `rustlings hint from_str` or use the `hint` watch subcommand for a
|
||||||
|
// hint.
|
||||||
|
|
||||||
|
use std::num::ParseIntError;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
struct Person {
|
||||||
|
name: String,
|
||||||
|
age: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
// We will use this error type for the `FromStr` implementation.
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
enum ParsePersonError {
|
||||||
|
// Empty input string
|
||||||
|
Empty,
|
||||||
|
// Incorrect number of fields
|
||||||
|
BadLen,
|
||||||
|
// Empty name field
|
||||||
|
NoName,
|
||||||
|
// Wrapped error from parse::<usize>()
|
||||||
|
ParseInt(ParseIntError),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Steps:
|
||||||
|
// 1. If the length of the provided string is 0, an error should be returned
|
||||||
|
// 2. Split the given string on the commas present in it
|
||||||
|
// 3. Only 2 elements should be returned from the split, otherwise return an
|
||||||
|
// error
|
||||||
|
// 4. Extract the first element from the split operation and use it as the name
|
||||||
|
// 5. Extract the other element from the split operation and parse it into a
|
||||||
|
// `usize` as the age with something like `"4".parse::<usize>()`
|
||||||
|
// 6. If while extracting the name and the age something goes wrong, an error
|
||||||
|
// should be returned
|
||||||
|
// If everything goes well, then return a Result of a Person object
|
||||||
|
//
|
||||||
|
// As an aside: `Box<dyn Error>` implements `From<&'_ str>`. This means that if
|
||||||
|
// you want to return a string error message, you can do so via just using
|
||||||
|
// return `Err("my error message".into())`.
|
||||||
|
|
||||||
|
impl FromStr for Person {
|
||||||
|
type Err = ParsePersonError;
|
||||||
|
fn from_str(s: &str) -> Result<Person, Self::Err> {
|
||||||
|
if s.len() == 0 {
|
||||||
|
Err(ParsePersonError::Empty)
|
||||||
|
} else {
|
||||||
|
let parts = s.split(',').collect::<Vec<&str>>();
|
||||||
|
if parts.len() != 2 {
|
||||||
|
Err(ParsePersonError::BadLen)
|
||||||
|
} else if parts[0].len() == 0 {
|
||||||
|
Err(ParsePersonError::NoName)
|
||||||
|
} else {
|
||||||
|
let name = parts[0].to_string();
|
||||||
|
let age = parts[1]
|
||||||
|
.parse::<usize>()
|
||||||
|
.map_err(ParsePersonError::ParseInt)?;
|
||||||
|
Ok(Person { name, age })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let p = "Mark,20".parse::<Person>().unwrap();
|
||||||
|
println!("{:?}", p);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn empty_input() {
|
||||||
|
assert_eq!("".parse::<Person>(), Err(ParsePersonError::Empty));
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn good_input() {
|
||||||
|
let p = "John,32".parse::<Person>();
|
||||||
|
assert!(p.is_ok());
|
||||||
|
let p = p.unwrap();
|
||||||
|
assert_eq!(p.name, "John");
|
||||||
|
assert_eq!(p.age, 32);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn missing_age() {
|
||||||
|
assert!(matches!(
|
||||||
|
"John,".parse::<Person>(),
|
||||||
|
Err(ParsePersonError::ParseInt(_))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn invalid_age() {
|
||||||
|
assert!(matches!(
|
||||||
|
"John,twenty".parse::<Person>(),
|
||||||
|
Err(ParsePersonError::ParseInt(_))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn missing_comma_and_age() {
|
||||||
|
assert_eq!("John".parse::<Person>(), Err(ParsePersonError::BadLen));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn missing_name() {
|
||||||
|
assert_eq!(",1".parse::<Person>(), Err(ParsePersonError::NoName));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn missing_name_and_age() {
|
||||||
|
assert!(matches!(
|
||||||
|
",".parse::<Person>(),
|
||||||
|
Err(ParsePersonError::NoName | ParsePersonError::ParseInt(_))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn missing_name_and_invalid_age() {
|
||||||
|
assert!(matches!(
|
||||||
|
",one".parse::<Person>(),
|
||||||
|
Err(ParsePersonError::NoName | ParsePersonError::ParseInt(_))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn trailing_comma() {
|
||||||
|
assert_eq!("John,32,".parse::<Person>(), Err(ParsePersonError::BadLen));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn trailing_comma_and_some_string() {
|
||||||
|
assert_eq!(
|
||||||
|
"John,32,man".parse::<Person>(),
|
||||||
|
Err(ParsePersonError::BadLen)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
226
conversions/try_from_into.rs
Normal file
226
conversions/try_from_into.rs
Normal file
|
@ -0,0 +1,226 @@
|
||||||
|
// try_from_into.rs
|
||||||
|
//
|
||||||
|
// TryFrom is a simple and safe type conversion that may fail in a controlled
|
||||||
|
// way under some circumstances. Basically, this is the same as From. The main
|
||||||
|
// difference is that this should return a Result type instead of the target
|
||||||
|
// type itself. You can read more about it at
|
||||||
|
// https://doc.rust-lang.org/std/convert/trait.TryFrom.html
|
||||||
|
//
|
||||||
|
// Execute `rustlings hint try_from_into` or use the `hint` watch subcommand for
|
||||||
|
// a hint.
|
||||||
|
|
||||||
|
use std::convert::{TryFrom, TryInto};
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
struct Color {
|
||||||
|
red: u8,
|
||||||
|
green: u8,
|
||||||
|
blue: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
// We will use this error type for these `TryFrom` conversions.
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
enum IntoColorError {
|
||||||
|
// Incorrect length of slice
|
||||||
|
BadLen,
|
||||||
|
// Integer conversion error
|
||||||
|
IntConversion,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Your task is to complete this implementation and return an Ok result of inner
|
||||||
|
// type Color. You need to create an implementation for a tuple of three
|
||||||
|
// integers, an array of three integers, and a slice of integers.
|
||||||
|
//
|
||||||
|
// Note that the implementation for tuple and array will be checked at compile
|
||||||
|
// time, but the slice implementation needs to check the slice length! Also note
|
||||||
|
// that correct RGB color values must be integers in the 0..=255 range.
|
||||||
|
|
||||||
|
// Tuple implementation
|
||||||
|
impl TryFrom<(i16, i16, i16)> for Color {
|
||||||
|
type Error = IntoColorError;
|
||||||
|
fn try_from(tuple: (i16, i16, i16)) -> Result<Self, Self::Error> {
|
||||||
|
let (red, green, blue) = tuple;
|
||||||
|
|
||||||
|
for color in [red, green, blue] {
|
||||||
|
if !(0..=255).contains(&color) {
|
||||||
|
return Err(IntoColorError::IntConversion);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(Self {
|
||||||
|
red: tuple.0 as u8,
|
||||||
|
green: tuple.1 as u8,
|
||||||
|
blue: tuple.2 as u8,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Array implementation
|
||||||
|
impl TryFrom<[i16; 3]> for Color {
|
||||||
|
type Error = IntoColorError;
|
||||||
|
fn try_from(arr: [i16; 3]) -> Result<Self, Self::Error> {
|
||||||
|
for color in arr {
|
||||||
|
if !(0..=255).contains(&color) {
|
||||||
|
return Err(IntoColorError::IntConversion);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(Self {
|
||||||
|
red: arr[0] as u8,
|
||||||
|
green: arr[1] as u8,
|
||||||
|
blue: arr[2] as u8,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Slice implementation
|
||||||
|
impl TryFrom<&[i16]> for Color {
|
||||||
|
type Error = IntoColorError;
|
||||||
|
fn try_from(slice: &[i16]) -> Result<Self, Self::Error> {
|
||||||
|
if slice.len() != 3 {
|
||||||
|
return Err(IntoColorError::BadLen);
|
||||||
|
}
|
||||||
|
for color in slice {
|
||||||
|
if !(0..=255).contains(color) {
|
||||||
|
return Err(IntoColorError::IntConversion);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(Self {
|
||||||
|
red: slice[0] as u8,
|
||||||
|
green: slice[1] as u8,
|
||||||
|
blue: slice[2] as u8,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// Use the `try_from` function
|
||||||
|
let c1 = Color::try_from((183, 65, 14));
|
||||||
|
println!("{:?}", c1);
|
||||||
|
|
||||||
|
// Since TryFrom is implemented for Color, we should be able to use TryInto
|
||||||
|
let c2: Result<Color, _> = [183, 65, 14].try_into();
|
||||||
|
println!("{:?}", c2);
|
||||||
|
|
||||||
|
let v = vec![183, 65, 14];
|
||||||
|
// With slice we should use `try_from` function
|
||||||
|
let c3 = Color::try_from(&v[..]);
|
||||||
|
println!("{:?}", c3);
|
||||||
|
// or take slice within round brackets and use TryInto
|
||||||
|
let c4: Result<Color, _> = (&v[..]).try_into();
|
||||||
|
println!("{:?}", c4);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_tuple_out_of_range_positive() {
|
||||||
|
assert_eq!(
|
||||||
|
Color::try_from((256, 1000, 10000)),
|
||||||
|
Err(IntoColorError::IntConversion)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_tuple_out_of_range_negative() {
|
||||||
|
assert_eq!(
|
||||||
|
Color::try_from((-1, -10, -256)),
|
||||||
|
Err(IntoColorError::IntConversion)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_tuple_sum() {
|
||||||
|
assert_eq!(
|
||||||
|
Color::try_from((-1, 255, 255)),
|
||||||
|
Err(IntoColorError::IntConversion)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_tuple_correct() {
|
||||||
|
let c: Result<Color, _> = (183, 65, 14).try_into();
|
||||||
|
assert!(c.is_ok());
|
||||||
|
assert_eq!(
|
||||||
|
c.unwrap(),
|
||||||
|
Color {
|
||||||
|
red: 183,
|
||||||
|
green: 65,
|
||||||
|
blue: 14
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_array_out_of_range_positive() {
|
||||||
|
let c: Result<Color, _> = [1000, 10000, 256].try_into();
|
||||||
|
assert_eq!(c, Err(IntoColorError::IntConversion));
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_array_out_of_range_negative() {
|
||||||
|
let c: Result<Color, _> = [-10, -256, -1].try_into();
|
||||||
|
assert_eq!(c, Err(IntoColorError::IntConversion));
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_array_sum() {
|
||||||
|
let c: Result<Color, _> = [-1, 255, 255].try_into();
|
||||||
|
assert_eq!(c, Err(IntoColorError::IntConversion));
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_array_correct() {
|
||||||
|
let c: Result<Color, _> = [183, 65, 14].try_into();
|
||||||
|
assert!(c.is_ok());
|
||||||
|
assert_eq!(
|
||||||
|
c.unwrap(),
|
||||||
|
Color {
|
||||||
|
red: 183,
|
||||||
|
green: 65,
|
||||||
|
blue: 14
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_slice_out_of_range_positive() {
|
||||||
|
let arr = [10000, 256, 1000];
|
||||||
|
assert_eq!(
|
||||||
|
Color::try_from(&arr[..]),
|
||||||
|
Err(IntoColorError::IntConversion)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_slice_out_of_range_negative() {
|
||||||
|
let arr = [-256, -1, -10];
|
||||||
|
assert_eq!(
|
||||||
|
Color::try_from(&arr[..]),
|
||||||
|
Err(IntoColorError::IntConversion)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_slice_sum() {
|
||||||
|
let arr = [-1, 255, 255];
|
||||||
|
assert_eq!(
|
||||||
|
Color::try_from(&arr[..]),
|
||||||
|
Err(IntoColorError::IntConversion)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_slice_correct() {
|
||||||
|
let v = vec![183, 65, 14];
|
||||||
|
let c: Result<Color, _> = Color::try_from(&v[..]);
|
||||||
|
assert!(c.is_ok());
|
||||||
|
assert_eq!(
|
||||||
|
c.unwrap(),
|
||||||
|
Color {
|
||||||
|
red: 183,
|
||||||
|
green: 65,
|
||||||
|
blue: 14
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_slice_excess_length() {
|
||||||
|
let v = vec![0, 0, 0, 0];
|
||||||
|
assert_eq!(Color::try_from(&v[..]), Err(IntoColorError::BadLen));
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_slice_insufficient_length() {
|
||||||
|
let v = vec![0, 0];
|
||||||
|
assert_eq!(Color::try_from(&v[..]), Err(IntoColorError::BadLen));
|
||||||
|
}
|
||||||
|
}
|
31
conversions/using_as.rs
Normal file
31
conversions/using_as.rs
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
// using_as.rs
|
||||||
|
//
|
||||||
|
// Type casting in Rust is done via the usage of the `as` operator. Please note
|
||||||
|
// that the `as` operator is not only used when type casting. It also helps with
|
||||||
|
// renaming imports.
|
||||||
|
//
|
||||||
|
// The goal is to make sure that the division does not fail to compile and
|
||||||
|
// returns the proper type.
|
||||||
|
//
|
||||||
|
// Execute `rustlings hint using_as` or use the `hint` watch subcommand for a
|
||||||
|
// hint.
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
10
enums/README.md
Normal file
10
enums/README.md
Normal file
|
@ -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)
|
18
enums/enums1.rs
Normal file
18
enums/enums1.rs
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
// enums1.rs
|
||||||
|
//
|
||||||
|
// No hints this time! ;)
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum Message {
|
||||||
|
Quit,
|
||||||
|
Echo,
|
||||||
|
Move,
|
||||||
|
ChangeColor
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
println!("{:?}", Message::Quit);
|
||||||
|
println!("{:?}", Message::Echo);
|
||||||
|
println!("{:?}", Message::Move);
|
||||||
|
println!("{:?}", Message::ChangeColor);
|
||||||
|
}
|
31
enums/enums2.rs
Normal file
31
enums/enums2.rs
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
// enums2.rs
|
||||||
|
//
|
||||||
|
// Execute `rustlings hint enums2` or use the `hint` watch subcommand for a
|
||||||
|
// hint.
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum Message {
|
||||||
|
Move{x: u8, y: u8},
|
||||||
|
Echo(String),
|
||||||
|
ChangeColor(u8, u8, u8),
|
||||||
|
Quit,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Message {
|
||||||
|
fn call(&self) {
|
||||||
|
println!("{:?}", self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let messages = [
|
||||||
|
Message::Move { x: 10, y: 30 },
|
||||||
|
Message::Echo(String::from("hello world")),
|
||||||
|
Message::ChangeColor(200, 255, 255),
|
||||||
|
Message::Quit,
|
||||||
|
];
|
||||||
|
|
||||||
|
for message in &messages {
|
||||||
|
message.call();
|
||||||
|
}
|
||||||
|
}
|
81
enums/enums3.rs
Normal file
81
enums/enums3.rs
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
// enums3.rs
|
||||||
|
//
|
||||||
|
// Address all the TODOs to make the tests pass!
|
||||||
|
//
|
||||||
|
// Execute `rustlings hint enums3` or use the `hint` watch subcommand for a
|
||||||
|
// hint.
|
||||||
|
|
||||||
|
enum Message {
|
||||||
|
ChangeColor(u8, u8, u8),
|
||||||
|
Echo(String),
|
||||||
|
Move(Point),
|
||||||
|
Quit,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Point {
|
||||||
|
x: u8,
|
||||||
|
y: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct State {
|
||||||
|
color: (u8, u8, u8),
|
||||||
|
position: Point,
|
||||||
|
quit: bool,
|
||||||
|
message: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl State {
|
||||||
|
fn change_color(&mut self, color: (u8, u8, u8)) {
|
||||||
|
self.color = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn quit(&mut self) {
|
||||||
|
self.quit = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn echo(&mut self, s: String) {
|
||||||
|
self.message = s
|
||||||
|
}
|
||||||
|
|
||||||
|
fn move_position(&mut self, p: Point) {
|
||||||
|
self.position = p;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process(&mut self, message: Message) {
|
||||||
|
// TODO: create a match expression to process the different message
|
||||||
|
// variants
|
||||||
|
// Remember: When passing a tuple as a function argument, you'll need
|
||||||
|
// extra parentheses: fn function((t, u, p, l, e))
|
||||||
|
match message {
|
||||||
|
Message::ChangeColor(r, g, b) => self.change_color((r, g, b)),
|
||||||
|
Message::Echo(echo) => self.echo(echo),
|
||||||
|
Message::Move(point) => self.move_position(point),
|
||||||
|
Message::Quit => self.quit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_match_message_call() {
|
||||||
|
let mut state = State {
|
||||||
|
quit: false,
|
||||||
|
position: Point { x: 0, y: 0 },
|
||||||
|
color: (0, 0, 0),
|
||||||
|
message: "hello world".to_string(),
|
||||||
|
};
|
||||||
|
state.process(Message::ChangeColor(255, 0, 255));
|
||||||
|
state.process(Message::Echo(String::from("Hello world!")));
|
||||||
|
state.process(Message::Move(Point { x: 10, y: 15 }));
|
||||||
|
state.process(Message::Quit);
|
||||||
|
|
||||||
|
assert_eq!(state.color, (255, 0, 255));
|
||||||
|
assert_eq!(state.position.x, 10);
|
||||||
|
assert_eq!(state.position.y, 15);
|
||||||
|
assert_eq!(state.quit, true);
|
||||||
|
assert_eq!(state.message, "Hello world!");
|
||||||
|
}
|
||||||
|
}
|
12
error_handling/README.md
Normal file
12
error_handling/README.md
Normal file
|
@ -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)
|
40
error_handling/errors1.rs
Normal file
40
error_handling/errors1.rs
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
// errors1.rs
|
||||||
|
//
|
||||||
|
// This function refuses to generate text to be printed on a nametag if you pass
|
||||||
|
// it an empty string. It'd be nicer if it explained what the problem was,
|
||||||
|
// instead of just sometimes returning `None`. Thankfully, Rust has a similar
|
||||||
|
// construct to `Option` that can be used to express error conditions. Let's use
|
||||||
|
// it!
|
||||||
|
//
|
||||||
|
// Execute `rustlings hint errors1` or use the `hint` watch subcommand for a
|
||||||
|
// hint.
|
||||||
|
|
||||||
|
pub fn generate_nametag_text(name: String) -> Result<String, String> {
|
||||||
|
if name.is_empty() {
|
||||||
|
Err("`name` was empty; it must be nonempty.".to_string())
|
||||||
|
} else {
|
||||||
|
Ok(format!("Hi! My name is {}", name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn generates_nametag_text_for_a_nonempty_name() {
|
||||||
|
assert_eq!(
|
||||||
|
generate_nametag_text("Beyoncé".into()),
|
||||||
|
Ok("Hi! My name is Beyoncé".into())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn explains_why_generating_nametag_text_fails() {
|
||||||
|
assert_eq!(
|
||||||
|
generate_nametag_text("".into()),
|
||||||
|
// Don't change this line
|
||||||
|
Err("`name` was empty; it must be nonempty.".into())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
48
error_handling/errors2.rs
Normal file
48
error_handling/errors2.rs
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
// errors2.rs
|
||||||
|
//
|
||||||
|
// Say we're writing a game where you can buy items with tokens. All items cost
|
||||||
|
// 5 tokens, and whenever you purchase items there is a processing fee of 1
|
||||||
|
// token. A player of the game will type in how many items they want to buy, and
|
||||||
|
// the `total_cost` function will calculate the total cost of the tokens. Since
|
||||||
|
// the player typed in the quantity, though, we get it as a string-- and they
|
||||||
|
// might have typed anything, not just numbers!
|
||||||
|
//
|
||||||
|
// Right now, this function isn't handling the error case at all (and isn't
|
||||||
|
// handling the success case properly either). What we want to do is: if we call
|
||||||
|
// the `total_cost` function on a string that is not a number, that function
|
||||||
|
// will return a `ParseIntError`, and in that case, we want to immediately
|
||||||
|
// return that error from our function and not try to multiply and add.
|
||||||
|
//
|
||||||
|
// There are at least two ways to implement this that are both correct-- but one
|
||||||
|
// is a lot shorter!
|
||||||
|
//
|
||||||
|
// Execute `rustlings hint errors2` or use the `hint` watch subcommand for a
|
||||||
|
// hint.
|
||||||
|
|
||||||
|
use std::num::ParseIntError;
|
||||||
|
|
||||||
|
pub fn total_cost(item_quantity: &str) -> Result<i32, ParseIntError> {
|
||||||
|
let processing_fee = 1;
|
||||||
|
let cost_per_item = 5;
|
||||||
|
let qty = item_quantity.parse::<i32>()?;
|
||||||
|
|
||||||
|
Ok(qty * cost_per_item + processing_fee)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn item_quantity_is_a_valid_number() {
|
||||||
|
assert_eq!(total_cost("34"), Ok(171));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn item_quantity_is_an_invalid_number() {
|
||||||
|
assert_eq!(
|
||||||
|
total_cost("beep boop").unwrap_err().to_string(),
|
||||||
|
"invalid digit found in string"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
33
error_handling/errors3.rs
Normal file
33
error_handling/errors3.rs
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
// errors3.rs
|
||||||
|
//
|
||||||
|
// This is a program that is trying to use a completed version of the
|
||||||
|
// `total_cost` function from the previous exercise. It's not working though!
|
||||||
|
// Why not? What should we do to fix it?
|
||||||
|
//
|
||||||
|
// Execute `rustlings hint errors3` or use the `hint` watch subcommand for a
|
||||||
|
// hint.
|
||||||
|
|
||||||
|
use std::num::ParseIntError;
|
||||||
|
|
||||||
|
fn main() -> Result<(), ParseIntError> {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn total_cost(item_quantity: &str) -> Result<i32, ParseIntError> {
|
||||||
|
let processing_fee = 1;
|
||||||
|
let cost_per_item = 5;
|
||||||
|
let qty = item_quantity.parse::<i32>()?;
|
||||||
|
|
||||||
|
Ok(qty * cost_per_item + processing_fee)
|
||||||
|
}
|
35
error_handling/errors4.rs
Normal file
35
error_handling/errors4.rs
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
// errors4.rs
|
||||||
|
//
|
||||||
|
// Execute `rustlings hint errors4` or use the `hint` watch subcommand for a
|
||||||
|
// hint.
|
||||||
|
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
|
#[derive(PartialEq, Debug)]
|
||||||
|
struct PositiveNonzeroInteger(u64);
|
||||||
|
|
||||||
|
#[derive(PartialEq, Debug)]
|
||||||
|
enum CreationError {
|
||||||
|
Negative,
|
||||||
|
Zero,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PositiveNonzeroInteger {
|
||||||
|
fn new(value: i64) -> Result<PositiveNonzeroInteger, CreationError> {
|
||||||
|
match value.cmp(&0) {
|
||||||
|
Ordering::Greater => Ok(PositiveNonzeroInteger(value as u64)),
|
||||||
|
Ordering::Equal => Err(CreationError::Zero),
|
||||||
|
Ordering::Less => Err(CreationError::Negative),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_creation() {
|
||||||
|
assert!(PositiveNonzeroInteger::new(10).is_ok());
|
||||||
|
assert_eq!(
|
||||||
|
Err(CreationError::Negative),
|
||||||
|
PositiveNonzeroInteger::new(-10)
|
||||||
|
);
|
||||||
|
assert_eq!(Err(CreationError::Zero), PositiveNonzeroInteger::new(0));
|
||||||
|
}
|
69
error_handling/errors5.rs
Normal file
69
error_handling/errors5.rs
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
// errors5.rs
|
||||||
|
//
|
||||||
|
// This program uses an altered version of the code from errors4.
|
||||||
|
//
|
||||||
|
// This exercise uses some concepts that we won't get to until later in the
|
||||||
|
// course, like `Box` and the `From` trait. It's not important to understand
|
||||||
|
// them in detail right now, but you can read ahead if you like. For now, think
|
||||||
|
// of the `Box<dyn ???>` type as an "I want anything that does ???" type, which,
|
||||||
|
// given Rust's usual standards for runtime safety, should strike you as
|
||||||
|
// somewhat lenient!
|
||||||
|
//
|
||||||
|
// In short, this particular use case for boxes is for when you want to own a
|
||||||
|
// value and you care only that it is a type which implements a particular
|
||||||
|
// trait. To do so, The Box is declared as of type Box<dyn Trait> where Trait is
|
||||||
|
// the trait the compiler looks for on any value used in that context. For this
|
||||||
|
// exercise, that context is the potential errors which can be returned in a
|
||||||
|
// Result.
|
||||||
|
//
|
||||||
|
// What can we use to describe both errors? In other words, is there a trait
|
||||||
|
// which both errors implement?
|
||||||
|
//
|
||||||
|
// Execute `rustlings hint errors5` or use the `hint` watch subcommand for a
|
||||||
|
// hint.
|
||||||
|
|
||||||
|
use std::error;
|
||||||
|
use std::fmt;
|
||||||
|
use std::num::ParseIntError;
|
||||||
|
|
||||||
|
// TODO: update the return type of `main()` to make this compile.
|
||||||
|
fn main() -> Result<(), Box<dyn error::Error>> {
|
||||||
|
let pretend_user_input = "42";
|
||||||
|
let x: i64 = pretend_user_input.parse()?;
|
||||||
|
println!("output={:?}", PositiveNonzeroInteger::new(x)?);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't change anything below this line.
|
||||||
|
|
||||||
|
#[derive(PartialEq, Debug)]
|
||||||
|
struct PositiveNonzeroInteger(u64);
|
||||||
|
|
||||||
|
#[derive(PartialEq, Debug)]
|
||||||
|
enum CreationError {
|
||||||
|
Negative,
|
||||||
|
Zero,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PositiveNonzeroInteger {
|
||||||
|
fn new(value: i64) -> Result<PositiveNonzeroInteger, CreationError> {
|
||||||
|
match value {
|
||||||
|
x if x < 0 => Err(CreationError::Negative),
|
||||||
|
x if x == 0 => Err(CreationError::Zero),
|
||||||
|
x => Ok(PositiveNonzeroInteger(x as u64)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is required so that `CreationError` can implement `error::Error`.
|
||||||
|
impl fmt::Display for CreationError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
let description = match *self {
|
||||||
|
CreationError::Negative => "number is negative",
|
||||||
|
CreationError::Zero => "number is zero",
|
||||||
|
};
|
||||||
|
f.write_str(description)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl error::Error for CreationError {}
|
93
error_handling/errors6.rs
Normal file
93
error_handling/errors6.rs
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
// errors6.rs
|
||||||
|
//
|
||||||
|
// Using catch-all error types like `Box<dyn error::Error>` isn't recommended
|
||||||
|
// for library code, where callers might want to make decisions based on the
|
||||||
|
// error content, instead of printing it out or propagating it further. Here, we
|
||||||
|
// define a custom error type to make it possible for callers to decide what to
|
||||||
|
// do next when our function returns an error.
|
||||||
|
//
|
||||||
|
// Execute `rustlings hint errors6` or use the `hint` watch subcommand for a
|
||||||
|
// hint.
|
||||||
|
|
||||||
|
use std::num::ParseIntError;
|
||||||
|
|
||||||
|
// This is a custom error type that we will be using in `parse_pos_nonzero()`.
|
||||||
|
#[derive(PartialEq, Debug)]
|
||||||
|
enum ParsePosNonzeroError {
|
||||||
|
Creation(CreationError),
|
||||||
|
ParseInt(ParseIntError),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ParsePosNonzeroError {
|
||||||
|
fn from_creation(err: CreationError) -> ParsePosNonzeroError {
|
||||||
|
ParsePosNonzeroError::Creation(err)
|
||||||
|
}
|
||||||
|
fn from_parse_int(err: ParseIntError) -> ParsePosNonzeroError {
|
||||||
|
ParsePosNonzeroError::ParseInt(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_pos_nonzero(s: &str) -> Result<PositiveNonzeroInteger, ParsePosNonzeroError> {
|
||||||
|
// TODO: change this to return an appropriate error instead of panicking
|
||||||
|
// when `parse()` returns an error.
|
||||||
|
let x: i64 = s.parse().map_err(ParsePosNonzeroError::from_parse_int)?;
|
||||||
|
PositiveNonzeroInteger::new(x).map_err(ParsePosNonzeroError::from_creation)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't change anything below this line.
|
||||||
|
|
||||||
|
#[derive(PartialEq, Debug)]
|
||||||
|
struct PositiveNonzeroInteger(u64);
|
||||||
|
|
||||||
|
#[derive(PartialEq, Debug)]
|
||||||
|
enum CreationError {
|
||||||
|
Negative,
|
||||||
|
Zero,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PositiveNonzeroInteger {
|
||||||
|
fn new(value: i64) -> Result<PositiveNonzeroInteger, CreationError> {
|
||||||
|
match value {
|
||||||
|
x if x < 0 => Err(CreationError::Negative),
|
||||||
|
x if x == 0 => Err(CreationError::Zero),
|
||||||
|
x => Ok(PositiveNonzeroInteger(x as u64)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_error() {
|
||||||
|
// We can't construct a ParseIntError, so we have to pattern match.
|
||||||
|
assert!(matches!(
|
||||||
|
parse_pos_nonzero("not a number"),
|
||||||
|
Err(ParsePosNonzeroError::ParseInt(_))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_negative() {
|
||||||
|
assert_eq!(
|
||||||
|
parse_pos_nonzero("-555"),
|
||||||
|
Err(ParsePosNonzeroError::Creation(CreationError::Negative))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_zero() {
|
||||||
|
assert_eq!(
|
||||||
|
parse_pos_nonzero("0"),
|
||||||
|
Err(ParsePosNonzeroError::Creation(CreationError::Zero))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_positive() {
|
||||||
|
let x = PositiveNonzeroInteger::new(42);
|
||||||
|
assert!(x.is_ok());
|
||||||
|
assert_eq!(parse_pos_nonzero("42"), Ok(x.unwrap()));
|
||||||
|
}
|
||||||
|
}
|
8
functions/README.md
Normal file
8
functions/README.md
Normal file
|
@ -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)
|
10
functions/functions1.rs
Normal file
10
functions/functions1.rs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
// functions1.rs
|
||||||
|
//
|
||||||
|
// Execute `rustlings hint functions1` or use the `hint` watch subcommand for a
|
||||||
|
// hint.
|
||||||
|
|
||||||
|
fn call_me() {
|
||||||
|
}
|
||||||
|
fn main() {
|
||||||
|
call_me();
|
||||||
|
}
|
14
functions/functions2.rs
Normal file
14
functions/functions2.rs
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
// functions2.rs
|
||||||
|
//
|
||||||
|
// Execute `rustlings hint functions2` or use the `hint` watch subcommand for a
|
||||||
|
// hint.
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
call_me(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call_me(num: u8) {
|
||||||
|
for i in 0..num {
|
||||||
|
println!("Ring! Call number {}", i + 1);
|
||||||
|
}
|
||||||
|
}
|
14
functions/functions3.rs
Normal file
14
functions/functions3.rs
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
// functions3.rs
|
||||||
|
//
|
||||||
|
// Execute `rustlings hint functions3` or use the `hint` watch subcommand for a
|
||||||
|
// hint.
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
call_me(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call_me(num: u32) {
|
||||||
|
for i in 0..num {
|
||||||
|
println!("Ring! Call number {}", i + 1);
|
||||||
|
}
|
||||||
|
}
|
26
functions/functions4.rs
Normal file
26
functions/functions4.rs
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
// functions4.rs
|
||||||
|
//
|
||||||
|
// This store is having a sale where if the price is an even number, you get 10
|
||||||
|
// Rustbucks off, but if it's an odd number, it's 3 Rustbucks off. (Don't worry
|
||||||
|
// about the function bodies themselves, we're only interested in the signatures
|
||||||
|
// for now. If anything, this is a good way to peek ahead to future exercises!)
|
||||||
|
//
|
||||||
|
// Execute `rustlings hint functions4` or use the `hint` watch subcommand for a
|
||||||
|
// hint.
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let original_price = 51;
|
||||||
|
println!("Your sale price is {}", sale_price(original_price));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sale_price(price: i32) -> i32 {
|
||||||
|
if is_even(price) {
|
||||||
|
price - 10
|
||||||
|
} else {
|
||||||
|
price - 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_even(num: i32) -> bool {
|
||||||
|
num % 2 == 0
|
||||||
|
}
|
13
functions/functions5.rs
Normal file
13
functions/functions5.rs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
// functions5.rs
|
||||||
|
//
|
||||||
|
// Execute `rustlings hint functions5` or use the `hint` watch subcommand for a
|
||||||
|
// hint.
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let answer = square(3);
|
||||||
|
println!("The square of 3 is {}", answer);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn square(num: i32) -> i32 {
|
||||||
|
num * num
|
||||||
|
}
|
11
generics/README.md
Normal file
11
generics/README.md
Normal file
|
@ -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 rather involving syntax.
|
||||||
|
Namely, being generic requires taking great care to specify over which types a generic type is actually considered valid.
|
||||||
|
The simplest and most common use of generics is for type parameters.
|
||||||
|
|
||||||
|
## Further information
|
||||||
|
|
||||||
|
- [Generic Data Types](https://doc.rust-lang.org/stable/book/ch10-01-syntax.html)
|
||||||
|
- [Bounds](https://doc.rust-lang.org/rust-by-example/generics/bounds.html)
|
12
generics/generics1.rs
Normal file
12
generics/generics1.rs
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
// generics1.rs
|
||||||
|
//
|
||||||
|
// This shopping list program isn't compiling! Use your knowledge of generics to
|
||||||
|
// fix it.
|
||||||
|
//
|
||||||
|
// Execute `rustlings hint generics1` or use the `hint` watch subcommand for a
|
||||||
|
// hint.
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut shopping_list: Vec<&str> = Vec::new();
|
||||||
|
shopping_list.push("milk");
|
||||||
|
}
|
32
generics/generics2.rs
Normal file
32
generics/generics2.rs
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
// generics2.rs
|
||||||
|
//
|
||||||
|
// This powerful wrapper provides the ability to store a positive integer value.
|
||||||
|
// Rewrite it using generics so that it supports wrapping ANY type.
|
||||||
|
//
|
||||||
|
// Execute `rustlings hint generics2` or use the `hint` watch subcommand for a
|
||||||
|
// hint.
|
||||||
|
|
||||||
|
struct Wrapper<T> {
|
||||||
|
value: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Wrapper<T> {
|
||||||
|
pub fn new(value: T) -> Self {
|
||||||
|
Wrapper { value }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn store_u32_in_wrapper() {
|
||||||
|
assert_eq!(Wrapper::new(42).value, 42);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn store_str_in_wrapper() {
|
||||||
|
assert_eq!(Wrapper::new("Foo").value, "Foo");
|
||||||
|
}
|
||||||
|
}
|
12
hashmaps/README.md
Normal file
12
hashmaps/README.md
Normal file
|
@ -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)
|
44
hashmaps/hashmaps1.rs
Normal file
44
hashmaps/hashmaps1.rs
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
// hashmaps1.rs
|
||||||
|
//
|
||||||
|
// A basket of fruits in the form of a hash map needs to be defined. The key
|
||||||
|
// represents the name of the fruit and the value represents how many of that
|
||||||
|
// particular fruit is in the basket. You have to put at least three different
|
||||||
|
// types of fruits (e.g apple, banana, mango) in the basket and the total count
|
||||||
|
// of all the fruits should be at least five.
|
||||||
|
//
|
||||||
|
// Make me compile and pass the tests!
|
||||||
|
//
|
||||||
|
// Execute `rustlings hint hashmaps1` or use the `hint` watch subcommand for a
|
||||||
|
// hint.
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
fn fruit_basket() -> HashMap<String, u32> {
|
||||||
|
let mut basket = HashMap::new();
|
||||||
|
|
||||||
|
// Two bananas are already given for you :)
|
||||||
|
basket.insert(String::from("banana"), 2);
|
||||||
|
|
||||||
|
// TODO: Put more fruits in your basket here.
|
||||||
|
basket.insert(String::from("apple"), 2);
|
||||||
|
basket.insert(String::from("orange"), 2);
|
||||||
|
|
||||||
|
basket
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn at_least_three_types_of_fruits() {
|
||||||
|
let basket = fruit_basket();
|
||||||
|
assert!(basket.len() >= 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn at_least_five_fruits() {
|
||||||
|
let basket = fruit_basket();
|
||||||
|
assert!(basket.values().sum::<u32>() >= 5);
|
||||||
|
}
|
||||||
|
}
|
94
hashmaps/hashmaps2.rs
Normal file
94
hashmaps/hashmaps2.rs
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
// hashmaps2.rs
|
||||||
|
//
|
||||||
|
// 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 these fruits!
|
||||||
|
//
|
||||||
|
// Make me pass the tests!
|
||||||
|
//
|
||||||
|
// Execute `rustlings hint hashmaps2` or use the `hint` watch subcommand for a
|
||||||
|
// hint.
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
#[derive(Hash, PartialEq, Eq)]
|
||||||
|
enum Fruit {
|
||||||
|
Apple,
|
||||||
|
Banana,
|
||||||
|
Mango,
|
||||||
|
Lychee,
|
||||||
|
Pineapple,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fruit_basket(basket: &mut HashMap<Fruit, u32>) {
|
||||||
|
let fruit_kinds = vec![
|
||||||
|
Fruit::Apple,
|
||||||
|
Fruit::Banana,
|
||||||
|
Fruit::Mango,
|
||||||
|
Fruit::Lychee,
|
||||||
|
Fruit::Pineapple,
|
||||||
|
];
|
||||||
|
|
||||||
|
for fruit in fruit_kinds {
|
||||||
|
// TODO: 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!
|
||||||
|
if !basket.contains_key(&fruit) {
|
||||||
|
basket.insert(fruit, 5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
// Don't modify this function!
|
||||||
|
fn get_fruit_basket() -> HashMap<Fruit, u32> {
|
||||||
|
let mut basket = HashMap::<Fruit, u32>::new();
|
||||||
|
basket.insert(Fruit::Apple, 4);
|
||||||
|
basket.insert(Fruit::Mango, 2);
|
||||||
|
basket.insert(Fruit::Lychee, 5);
|
||||||
|
|
||||||
|
basket
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_given_fruits_are_not_modified() {
|
||||||
|
let mut basket = get_fruit_basket();
|
||||||
|
fruit_basket(&mut basket);
|
||||||
|
assert_eq!(*basket.get(&Fruit::Apple).unwrap(), 4);
|
||||||
|
assert_eq!(*basket.get(&Fruit::Mango).unwrap(), 2);
|
||||||
|
assert_eq!(*basket.get(&Fruit::Lychee).unwrap(), 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn at_least_five_types_of_fruits() {
|
||||||
|
let mut basket = get_fruit_basket();
|
||||||
|
fruit_basket(&mut basket);
|
||||||
|
let count_fruit_kinds = basket.len();
|
||||||
|
assert!(count_fruit_kinds >= 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn greater_than_eleven_fruits() {
|
||||||
|
let mut basket = get_fruit_basket();
|
||||||
|
fruit_basket(&mut basket);
|
||||||
|
let count = basket.values().sum::<u32>();
|
||||||
|
assert!(count > 11);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn all_fruit_types_in_basket() {
|
||||||
|
let mut basket = get_fruit_basket();
|
||||||
|
fruit_basket(&mut basket);
|
||||||
|
for amount in basket.values() {
|
||||||
|
assert_ne!(amount, &0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
106
hashmaps/hashmaps3.rs
Normal file
106
hashmaps/hashmaps3.rs
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
// hashmaps3.rs
|
||||||
|
//
|
||||||
|
// A list of scores (one per line) of a soccer match is given. Each line is of
|
||||||
|
// the form : "<team_1_name>,<team_2_name>,<team_1_goals>,<team_2_goals>"
|
||||||
|
// Example: England,France,4,2 (England scored 4 goals, France 2).
|
||||||
|
//
|
||||||
|
// You have to build a scores table containing the name of the team, goals the
|
||||||
|
// team scored, and goals the team conceded. One approach to build the scores
|
||||||
|
// table is to use a Hashmap. The solution is partially written to use a
|
||||||
|
// Hashmap, complete it to pass the test.
|
||||||
|
//
|
||||||
|
// Make me pass the tests!
|
||||||
|
//
|
||||||
|
// Execute `rustlings hint hashmaps3` or use the `hint` watch subcommand for a
|
||||||
|
// hint.
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
// A structure to store the goal details of a team.
|
||||||
|
struct Team {
|
||||||
|
goals_scored: u8,
|
||||||
|
goals_conceded: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_scores_table(results: String) -> HashMap<String, Team> {
|
||||||
|
// The name of the team is the key and its associated struct is the value.
|
||||||
|
let mut scores: HashMap<String, Team> = HashMap::new();
|
||||||
|
|
||||||
|
for r in results.lines() {
|
||||||
|
let v: Vec<&str> = r.split(',').collect();
|
||||||
|
let team_1_name = v[0].to_string();
|
||||||
|
let team_1_score: u8 = v[2].parse().unwrap();
|
||||||
|
let team_2_name = v[1].to_string();
|
||||||
|
let team_2_score: u8 = v[3].parse().unwrap();
|
||||||
|
// TODO: Populate the scores table with details extracted from the
|
||||||
|
// current line. Keep in mind that goals scored by team_1
|
||||||
|
// will be the number of goals conceded from team_2, and similarly
|
||||||
|
// goals scored by team_2 will be the number of goals conceded by
|
||||||
|
// team_1.
|
||||||
|
|
||||||
|
scores
|
||||||
|
.entry(team_1_name)
|
||||||
|
.and_modify(|team| {
|
||||||
|
team.goals_scored += team_1_score;
|
||||||
|
team.goals_conceded += team_2_score
|
||||||
|
})
|
||||||
|
.or_insert(Team {
|
||||||
|
goals_scored: team_1_score,
|
||||||
|
goals_conceded: team_2_score,
|
||||||
|
});
|
||||||
|
|
||||||
|
scores
|
||||||
|
.entry(team_2_name)
|
||||||
|
.and_modify(|team| {
|
||||||
|
team.goals_scored += team_2_score;
|
||||||
|
team.goals_conceded += team_1_score
|
||||||
|
})
|
||||||
|
.or_insert(Team {
|
||||||
|
goals_scored: team_2_score,
|
||||||
|
goals_conceded: team_1_score,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
scores
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
fn get_results() -> String {
|
||||||
|
let results = "".to_string()
|
||||||
|
+ "England,France,4,2\n"
|
||||||
|
+ "France,Italy,3,1\n"
|
||||||
|
+ "Poland,Spain,2,0\n"
|
||||||
|
+ "Germany,England,2,1\n";
|
||||||
|
results
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn build_scores() {
|
||||||
|
let scores = build_scores_table(get_results());
|
||||||
|
|
||||||
|
let mut keys: Vec<&String> = scores.keys().collect();
|
||||||
|
keys.sort();
|
||||||
|
assert_eq!(
|
||||||
|
keys,
|
||||||
|
vec!["England", "France", "Germany", "Italy", "Poland", "Spain"]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn validate_team_score_1() {
|
||||||
|
let scores = build_scores_table(get_results());
|
||||||
|
let team = scores.get("England").unwrap();
|
||||||
|
assert_eq!(team.goals_scored, 5);
|
||||||
|
assert_eq!(team.goals_conceded, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn validate_team_score_2() {
|
||||||
|
let scores = build_scores_table(get_results());
|
||||||
|
let team = scores.get("Spain").unwrap();
|
||||||
|
assert_eq!(team.goals_scored, 0);
|
||||||
|
assert_eq!(team.goals_conceded, 2);
|
||||||
|
}
|
||||||
|
}
|
7
if/README.md
Normal file
7
if/README.md
Normal file
|
@ -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)
|
32
if/if1.rs
Normal file
32
if/if1.rs
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
// if1.rs
|
||||||
|
//
|
||||||
|
// Execute `rustlings hint if1` or use the `hint` watch subcommand for a hint.
|
||||||
|
|
||||||
|
pub fn bigger(a: i32, b: i32) -> i32 {
|
||||||
|
if a > b {
|
||||||
|
a
|
||||||
|
} else {
|
||||||
|
b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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));
|
||||||
|
}
|
||||||
|
}
|
37
if/if2.rs
Normal file
37
if/if2.rs
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
// if2.rs
|
||||||
|
//
|
||||||
|
// Step 1: Make me compile!
|
||||||
|
// Step 2: Get the bar_for_fuzz and default_to_baz tests passing!
|
||||||
|
//
|
||||||
|
// Execute `rustlings hint if2` or use the `hint` watch subcommand for a hint.
|
||||||
|
|
||||||
|
pub fn foo_if_fizz(fizzish: &str) -> &str {
|
||||||
|
if fizzish == "fizz" {
|
||||||
|
"foo"
|
||||||
|
} else if fizzish == "fuzz" {
|
||||||
|
"bar"
|
||||||
|
} else {
|
||||||
|
"baz"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No test changes needed!
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn foo_for_fizz() {
|
||||||
|
assert_eq!(foo_if_fizz("fizz"), "foo")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bar_for_fuzz() {
|
||||||
|
assert_eq!(foo_if_fizz("fuzz"), "bar")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn default_to_baz() {
|
||||||
|
assert_eq!(foo_if_fizz("literally anything"), "baz")
|
||||||
|
}
|
||||||
|
}
|
54
if/if3.rs
Normal file
54
if/if3.rs
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
// if3.rs
|
||||||
|
//
|
||||||
|
// Execute `rustlings hint if3` or use the `hint` watch subcommand for a hint.
|
||||||
|
|
||||||
|
pub fn animal_habitat(animal: &str) -> &'static str {
|
||||||
|
let identifier = if animal == "crab" {
|
||||||
|
1
|
||||||
|
} else if animal == "gopher" {
|
||||||
|
2
|
||||||
|
} else if animal == "snake" {
|
||||||
|
3
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
|
// DO NOT CHANGE THIS STATEMENT BELOW
|
||||||
|
let habitat = if identifier == 1 {
|
||||||
|
"Beach"
|
||||||
|
} else if identifier == 2 {
|
||||||
|
"Burrow"
|
||||||
|
} else if identifier == 3 {
|
||||||
|
"Desert"
|
||||||
|
} else {
|
||||||
|
"Unknown"
|
||||||
|
};
|
||||||
|
|
||||||
|
habitat
|
||||||
|
}
|
||||||
|
|
||||||
|
// No test changes needed.
|
||||||
|
#[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")
|
||||||
|
}
|
||||||
|
}
|
8
intro/README.md
Normal file
8
intro/README.md
Normal file
|
@ -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)
|
40
intro/intro1.rs
Normal file
40
intro/intro1.rs
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
// intro1.rs
|
||||||
|
//
|
||||||
|
// About this `I AM NOT DONE` thing:
|
||||||
|
// 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, remove the `I AM NOT DONE` comment below.
|
||||||
|
//
|
||||||
|
// If you're running this using `rustlings watch`: The exercise file will be
|
||||||
|
// reloaded when you change one of the lines below! Try adding a `println!`
|
||||||
|
// line, or try changing what it outputs in your terminal. Try removing a
|
||||||
|
// semicolon and see what happens!
|
||||||
|
//
|
||||||
|
// Execute `rustlings hint intro1` or use the `hint` watch subcommand for a
|
||||||
|
// hint.
|
||||||
|
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
println!("Hello and");
|
||||||
|
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 source for this exercise is in `exercises/intro/intro1.rs`. Have a look!");
|
||||||
|
println!(
|
||||||
|
"Going forward, the source of the exercises will always be in the success/failure output."
|
||||||
|
);
|
||||||
|
println!();
|
||||||
|
println!(
|
||||||
|
"If you want to use rust-analyzer, Rust's LSP implementation, make sure your editor is set"
|
||||||
|
);
|
||||||
|
println!("up, and then run `rustlings lsp` before continuing.")
|
||||||
|
}
|
12
intro/intro2.rs
Normal file
12
intro/intro2.rs
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
// intro2.rs
|
||||||
|
//
|
||||||
|
// Make the code print a greeting to the world.
|
||||||
|
//
|
||||||
|
// Execute `rustlings hint intro2` or use the `hint` watch subcommand for a
|
||||||
|
// hint.
|
||||||
|
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let world = "World";
|
||||||
|
println!("Hello {}!", world);
|
||||||
|
}
|
8
iterators/README.md
Normal file
8
iterators/README.md
Normal file
|
@ -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/)
|
23
iterators/iterators1.rs
Normal file
23
iterators/iterators1.rs
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
// iterators1.rs
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
//
|
||||||
|
// Make me compile by filling in the `???`s
|
||||||
|
//
|
||||||
|
// Execute `rustlings hint iterators1` or use the `hint` watch subcommand for a
|
||||||
|
// hint.
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn main() {
|
||||||
|
let my_fav_fruits = vec!["banana", "custard apple", "avocado", "peach", "raspberry"];
|
||||||
|
|
||||||
|
let mut my_iterable_fav_fruits = my_fav_fruits.iter(); // TODO: Step 1
|
||||||
|
|
||||||
|
assert_eq!(my_iterable_fav_fruits.next(), Some(&"banana"));
|
||||||
|
assert_eq!(my_iterable_fav_fruits.next(), Some(&"custard apple")); // TODO: Step 2
|
||||||
|
assert_eq!(my_iterable_fav_fruits.next(), Some(&"avocado"));
|
||||||
|
assert_eq!(my_iterable_fav_fruits.next(), Some(&"peach"));
|
||||||
|
assert_eq!(my_iterable_fav_fruits.next(), Some(&"raspberry"));
|
||||||
|
}
|
61
iterators/iterators2.rs
Normal file
61
iterators/iterators2.rs
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
// iterators2.rs
|
||||||
|
//
|
||||||
|
// In this exercise, you'll learn some of the unique advantages that iterators
|
||||||
|
// can offer. Follow the steps to complete the exercise.
|
||||||
|
//
|
||||||
|
// Execute `rustlings hint iterators2` or use the `hint` watch subcommand for a
|
||||||
|
// hint.
|
||||||
|
|
||||||
|
// Step 1.
|
||||||
|
// Complete the `capitalize_first` function.
|
||||||
|
// "hello" -> "Hello"
|
||||||
|
pub fn capitalize_first(input: &str) -> String {
|
||||||
|
let mut c = input.chars();
|
||||||
|
match c.next() {
|
||||||
|
None => String::new(),
|
||||||
|
Some(first) => first.to_string().to_uppercase() + c.as_str(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 2.
|
||||||
|
// Apply the `capitalize_first` function to a slice of string slices.
|
||||||
|
// Return a vector of strings.
|
||||||
|
// ["hello", "world"] -> ["Hello", "World"]
|
||||||
|
pub fn capitalize_words_vector(words: &[&str]) -> Vec<String> {
|
||||||
|
words.iter().map(|word| capitalize_first(word)).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 3.
|
||||||
|
// Apply the `capitalize_first` function again to a slice of string slices.
|
||||||
|
// Return a single string.
|
||||||
|
// ["hello", " ", "world"] -> "Hello World"
|
||||||
|
pub fn capitalize_words_string(words: &[&str]) -> String {
|
||||||
|
words.iter().map(|word| capitalize_first(word)).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_success() {
|
||||||
|
assert_eq!(capitalize_first("hello"), "Hello");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_empty() {
|
||||||
|
assert_eq!(capitalize_first(""), "");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_iterate_string_vec() {
|
||||||
|
let words = vec!["hello", "world"];
|
||||||
|
assert_eq!(capitalize_words_vector(&words), ["Hello", "World"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_iterate_into_string() {
|
||||||
|
let words = vec!["hello", " ", "world"];
|
||||||
|
assert_eq!(capitalize_words_string(&words), "Hello World");
|
||||||
|
}
|
||||||
|
}
|
101
iterators/iterators3.rs
Normal file
101
iterators/iterators3.rs
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
// iterators3.rs
|
||||||
|
//
|
||||||
|
// This is a bigger exercise than most of the others! You can do it! Here is
|
||||||
|
// your mission, should you choose to accept it:
|
||||||
|
// 1. Complete the divide function to get the first four tests to pass.
|
||||||
|
// 2. Get the remaining tests to pass by completing the result_with_list and
|
||||||
|
// list_of_results functions.
|
||||||
|
//
|
||||||
|
// Execute `rustlings hint iterators3` or use the `hint` watch subcommand for a
|
||||||
|
// hint.
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
pub enum DivisionError {
|
||||||
|
NotDivisible(NotDivisibleError),
|
||||||
|
DivideByZero,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
pub struct NotDivisibleError {
|
||||||
|
dividend: i32,
|
||||||
|
divisor: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate `a` divided by `b` if `a` is evenly divisible by `b`.
|
||||||
|
// Otherwise, return a suitable error.
|
||||||
|
pub fn divide(a: i32, b: i32) -> Result<i32, DivisionError> {
|
||||||
|
if b == 0 {
|
||||||
|
Err(DivisionError::DivideByZero)
|
||||||
|
} else if a == 0 {
|
||||||
|
Ok(0)
|
||||||
|
} else if a % b == 0 {
|
||||||
|
Ok(a / b)
|
||||||
|
} else {
|
||||||
|
Err(DivisionError::NotDivisible(NotDivisibleError {
|
||||||
|
dividend: a,
|
||||||
|
divisor: b,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Complete the function and return a value of the correct type so the test
|
||||||
|
// passes.
|
||||||
|
// Desired output: Ok([1, 11, 1426, 3])
|
||||||
|
fn result_with_list() -> Result<Vec<i32>, DivisionError> {
|
||||||
|
let numbers = vec![27, 297, 38502, 81];
|
||||||
|
let division_results = numbers.into_iter().map(|n| divide(n, 27)).collect::<Result<Vec<_>, _>>();
|
||||||
|
Ok(division_results.unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Complete the function and return a value of the correct type so the test
|
||||||
|
// passes.
|
||||||
|
// Desired output: [Ok(1), Ok(11), Ok(1426), Ok(3)]
|
||||||
|
fn list_of_results() -> Vec<Result<i32, DivisionError>> {
|
||||||
|
let numbers = vec![27, 297, 38502, 81];
|
||||||
|
let division_results = numbers.into_iter().map(|n| divide(n, 27)).collect();
|
||||||
|
division_results
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_success() {
|
||||||
|
assert_eq!(divide(81, 9), Ok(9));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_not_divisible() {
|
||||||
|
assert_eq!(
|
||||||
|
divide(81, 6),
|
||||||
|
Err(DivisionError::NotDivisible(NotDivisibleError {
|
||||||
|
dividend: 81,
|
||||||
|
divisor: 6
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_divide_by_0() {
|
||||||
|
assert_eq!(divide(81, 0), Err(DivisionError::DivideByZero));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_divide_0_by_something() {
|
||||||
|
assert_eq!(divide(0, 81), Ok(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_result_with_list() {
|
||||||
|
assert_eq!(format!("{:?}", result_with_list()), "Ok([1, 11, 1426, 3])");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_list_of_results() {
|
||||||
|
assert_eq!(
|
||||||
|
format!("{:?}", list_of_results()),
|
||||||
|
"[Ok(1), Ok(11), Ok(1426), Ok(3)]"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
42
iterators/iterators4.rs
Normal file
42
iterators/iterators4.rs
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
// iterators4.rs
|
||||||
|
//
|
||||||
|
// Execute `rustlings hint iterators4` or use the `hint` watch subcommand for a
|
||||||
|
// hint.
|
||||||
|
|
||||||
|
pub fn factorial(mut num: u64) -> u64 {
|
||||||
|
// Complete this function to return the factorial of num
|
||||||
|
// Do not use:
|
||||||
|
// - return
|
||||||
|
// Try not to use:
|
||||||
|
// - imperative style loops (for, while)
|
||||||
|
// - additional variables
|
||||||
|
// For an extra challenge, don't use:
|
||||||
|
// - recursion
|
||||||
|
// Execute `rustlings hint iterators4` for hints.
|
||||||
|
|
||||||
|
(1..=num).product()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn factorial_of_0() {
|
||||||
|
assert_eq!(1, factorial(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn factorial_of_1() {
|
||||||
|
assert_eq!(1, factorial(1));
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn factorial_of_2() {
|
||||||
|
assert_eq!(2, factorial(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn factorial_of_4() {
|
||||||
|
assert_eq!(24, factorial(4));
|
||||||
|
}
|
||||||
|
}
|
156
iterators/iterators5.rs
Normal file
156
iterators/iterators5.rs
Normal file
|
@ -0,0 +1,156 @@
|
||||||
|
// iterators5.rs
|
||||||
|
//
|
||||||
|
// Let's define a simple model to track Rustlings exercise progress. Progress
|
||||||
|
// will be modelled using a hash map. The name of the exercise is the key and
|
||||||
|
// the progress is the value. Two counting functions were created to count the
|
||||||
|
// number of exercises with a given progress. Recreate this counting
|
||||||
|
// functionality using iterators. Try not to use imperative loops (for, while).
|
||||||
|
// Only the two iterator methods (count_iterator and count_collection_iterator)
|
||||||
|
// need to be modified.
|
||||||
|
//
|
||||||
|
// Execute `rustlings hint iterators5` or use the `hint` watch subcommand for a
|
||||||
|
// hint.
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||||
|
enum Progress {
|
||||||
|
None,
|
||||||
|
Some,
|
||||||
|
Complete,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn count_for(map: &HashMap<String, Progress>, value: Progress) -> usize {
|
||||||
|
let mut count = 0;
|
||||||
|
for val in map.values() {
|
||||||
|
if val == &value {
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
count
|
||||||
|
}
|
||||||
|
|
||||||
|
fn count_iterator(map: &HashMap<String, Progress>, value: Progress) -> usize {
|
||||||
|
// map is a hashmap with String keys and Progress values.
|
||||||
|
// map = { "variables1": Complete, "from_str": None, ... }
|
||||||
|
map.iter().filter(|val| val.1 == &value).count()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn count_collection_for(collection: &[HashMap<String, Progress>], value: Progress) -> usize {
|
||||||
|
let mut count = 0;
|
||||||
|
for map in collection {
|
||||||
|
for val in map.values() {
|
||||||
|
if val == &value {
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
count
|
||||||
|
}
|
||||||
|
|
||||||
|
fn count_collection_iterator(collection: &[HashMap<String, Progress>], value: Progress) -> usize {
|
||||||
|
// collection is a slice of hashmaps.
|
||||||
|
// collection = [{ "variables1": Complete, "from_str": None, ... },
|
||||||
|
// { "variables2": Complete, ... }, ... ]
|
||||||
|
collection
|
||||||
|
.iter()
|
||||||
|
.fold(0, |acc, x| acc + count_iterator(x, value))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn count_complete() {
|
||||||
|
let map = get_map();
|
||||||
|
assert_eq!(3, count_iterator(&map, Progress::Complete));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn count_some() {
|
||||||
|
let map = get_map();
|
||||||
|
assert_eq!(1, count_iterator(&map, Progress::Some));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn count_none() {
|
||||||
|
let map = get_map();
|
||||||
|
assert_eq!(2, count_iterator(&map, Progress::None));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn count_complete_equals_for() {
|
||||||
|
let map = get_map();
|
||||||
|
let progress_states = vec![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!(
|
||||||
|
6,
|
||||||
|
count_collection_iterator(&collection, Progress::Complete)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn count_collection_some() {
|
||||||
|
let collection = get_vec_map();
|
||||||
|
assert_eq!(1, count_collection_iterator(&collection, Progress::Some));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn count_collection_none() {
|
||||||
|
let collection = get_vec_map();
|
||||||
|
assert_eq!(4, count_collection_iterator(&collection, Progress::None));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn count_collection_equals_for() {
|
||||||
|
let progress_states = vec![Progress::Complete, Progress::Some, Progress::None];
|
||||||
|
let collection = get_vec_map();
|
||||||
|
|
||||||
|
for progress_state in progress_states {
|
||||||
|
assert_eq!(
|
||||||
|
count_collection_for(&collection, progress_state),
|
||||||
|
count_collection_iterator(&collection, progress_state)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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]
|
||||||
|
}
|
||||||
|
}
|
22
lifetimes/README.md
Normal file
22
lifetimes/README.md
Normal file
|
@ -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)
|
25
lifetimes/lifetimes1.rs
Normal file
25
lifetimes/lifetimes1.rs
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
// lifetimes1.rs
|
||||||
|
//
|
||||||
|
// The Rust compiler needs to know how to check whether supplied references are
|
||||||
|
// valid, so that it can let the programmer know if a reference is at risk of
|
||||||
|
// going out of scope before it is used. Remember, references are borrows and do
|
||||||
|
// not own their own data. What if their owner goes out of scope?
|
||||||
|
//
|
||||||
|
// Execute `rustlings hint lifetimes1` or use the `hint` watch subcommand for a
|
||||||
|
// hint.
|
||||||
|
|
||||||
|
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("abcd");
|
||||||
|
let string2 = "xyz";
|
||||||
|
|
||||||
|
let result = longest(string1.as_str(), string2);
|
||||||
|
println!("The longest string is '{}'", result);
|
||||||
|
}
|
25
lifetimes/lifetimes2.rs
Normal file
25
lifetimes/lifetimes2.rs
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
// lifetimes2.rs
|
||||||
|
//
|
||||||
|
// So if the compiler is just validating the references passed to the annotated
|
||||||
|
// parameters and the return type, what do we need to change?
|
||||||
|
//
|
||||||
|
// Execute `rustlings hint lifetimes2` or use the `hint` watch subcommand for a
|
||||||
|
// hint.
|
||||||
|
|
||||||
|
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
|
||||||
|
if x.len() > y.len() {
|
||||||
|
x
|
||||||
|
} else {
|
||||||
|
y
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let string1 = String::from("long string is long");
|
||||||
|
let result;
|
||||||
|
{
|
||||||
|
let string2 = String::from("xyz");
|
||||||
|
result = longest(string1.as_str(), string2.as_str());
|
||||||
|
println!("The longest string is '{}'", result);
|
||||||
|
}
|
||||||
|
}
|
19
lifetimes/lifetimes3.rs
Normal file
19
lifetimes/lifetimes3.rs
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
// lifetimes3.rs
|
||||||
|
//
|
||||||
|
// Lifetimes are also needed when structs hold references.
|
||||||
|
//
|
||||||
|
// Execute `rustlings hint lifetimes3` or use the `hint` watch subcommand for a
|
||||||
|
// hint.
|
||||||
|
|
||||||
|
struct Book<'a> {
|
||||||
|
author: &'a str,
|
||||||
|
title: &'a str,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let name = String::from("Jill Smith");
|
||||||
|
let title = String::from("Fish Flying");
|
||||||
|
let book = Book { author: &name, title: &title };
|
||||||
|
|
||||||
|
println!("{} by {}", book.title, book.author);
|
||||||
|
}
|
14
macros/README.md
Normal file
14
macros/README.md
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
# Macros
|
||||||
|
|
||||||
|
Rust's macro system is very powerful, but also kind of difficult to wrap your
|
||||||
|
head around. We're not going to teach you how to write your own fully-featured
|
||||||
|
macros. Instead, we'll show you how to use and create them.
|
||||||
|
|
||||||
|
If you'd like to learn more about writing your own macros, the
|
||||||
|
[macrokata](https://github.com/tfpk/macrokata) project has a similar style
|
||||||
|
of exercises to Rustlings, but is all about learning to write Macros.
|
||||||
|
|
||||||
|
## Further information
|
||||||
|
|
||||||
|
- [Macros](https://doc.rust-lang.org/book/ch19-06-macros.html)
|
||||||
|
- [The Little Book of Rust Macros](https://veykril.github.io/tlborm/)
|
14
macros/macros1.rs
Normal file
14
macros/macros1.rs
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
// macros1.rs
|
||||||
|
//
|
||||||
|
// Execute `rustlings hint macros1` or use the `hint` watch subcommand for a
|
||||||
|
// hint.
|
||||||
|
|
||||||
|
macro_rules! my_macro {
|
||||||
|
() => {
|
||||||
|
println!("Check out my macro!");
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
my_macro!();
|
||||||
|
}
|
14
macros/macros2.rs
Normal file
14
macros/macros2.rs
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
// macros2.rs
|
||||||
|
//
|
||||||
|
// Execute `rustlings hint macros2` or use the `hint` watch subcommand for a
|
||||||
|
// hint.
|
||||||
|
|
||||||
|
macro_rules! my_macro {
|
||||||
|
() => {
|
||||||
|
println!("Check out my macro!");
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
my_macro!();
|
||||||
|
}
|
19
macros/macros3.rs
Normal file
19
macros/macros3.rs
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
// macros3.rs
|
||||||
|
//
|
||||||
|
// Make me compile, without taking the macro out of the module!
|
||||||
|
//
|
||||||
|
// Execute `rustlings hint macros3` or use the `hint` watch subcommand for a
|
||||||
|
// hint.
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
mod macros {
|
||||||
|
macro_rules! my_macro {
|
||||||
|
() => {
|
||||||
|
println!("Check out my macro!");
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
my_macro!();
|
||||||
|
}
|
19
macros/macros4.rs
Normal file
19
macros/macros4.rs
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
// macros4.rs
|
||||||
|
//
|
||||||
|
// Execute `rustlings hint macros4` or use the `hint` watch subcommand for a
|
||||||
|
// hint.
|
||||||
|
|
||||||
|
#[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);
|
||||||
|
}
|
7
modules/README.md
Normal file
7
modules/README.md
Normal file
|
@ -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)
|
20
modules/modules1.rs
Normal file
20
modules/modules1.rs
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
// modules1.rs
|
||||||
|
//
|
||||||
|
// Execute `rustlings hint modules1` or use the `hint` watch subcommand for a
|
||||||
|
// hint.
|
||||||
|
|
||||||
|
mod sausage_factory {
|
||||||
|
// Don't let anybody outside of this module see this!
|
||||||
|
fn get_secret_recipe() -> String {
|
||||||
|
String::from("Ginger")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn make_sausage() {
|
||||||
|
get_secret_recipe();
|
||||||
|
println!("sausage!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
sausage_factory::make_sausage();
|
||||||
|
}
|
32
modules/modules2.rs
Normal file
32
modules/modules2.rs
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
// modules2.rs
|
||||||
|
//
|
||||||
|
// You can bring module paths into scopes and provide new names for them with
|
||||||
|
// the 'use' and 'as' keywords. Fix these 'use' statements to make the code
|
||||||
|
// compile.
|
||||||
|
//
|
||||||
|
// Execute `rustlings hint modules2` or use the `hint` watch subcommand for a
|
||||||
|
// hint.
|
||||||
|
|
||||||
|
mod delicious_snacks {
|
||||||
|
// TODO: Fix these use statements
|
||||||
|
pub use self::fruits::PEAR as fruit;
|
||||||
|
pub use self::veggies::CUCUMBER as veggie;
|
||||||
|
|
||||||
|
mod fruits {
|
||||||
|
pub const PEAR: &'static str = "Pear";
|
||||||
|
pub const APPLE: &'static str = "Apple";
|
||||||
|
}
|
||||||
|
|
||||||
|
mod veggies {
|
||||||
|
pub const CUCUMBER: &'static str = "Cucumber";
|
||||||
|
pub const CARROT: &'static str = "Carrot";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
println!(
|
||||||
|
"favorite snacks: {} and {}",
|
||||||
|
delicious_snacks::fruit,
|
||||||
|
delicious_snacks::veggie
|
||||||
|
);
|
||||||
|
}
|
19
modules/modules3.rs
Normal file
19
modules/modules3.rs
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
// modules3.rs
|
||||||
|
//
|
||||||
|
// You can use the 'use' keyword to bring module paths from modules from
|
||||||
|
// anywhere and especially from the Rust standard library into your scope. Bring
|
||||||
|
// SystemTime and UNIX_EPOCH from the std::time module. Bonus style points if
|
||||||
|
// you can do it with one line!
|
||||||
|
//
|
||||||
|
// Execute `rustlings hint modules3` or use the `hint` watch subcommand for a
|
||||||
|
// hint.
|
||||||
|
|
||||||
|
// TODO: Complete this use statement
|
||||||
|
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!"),
|
||||||
|
}
|
||||||
|
}
|
10
move_semantics/README.md
Normal file
10
move_semantics/README.md
Normal file
|
@ -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)
|
21
move_semantics/move_semantics1.rs
Normal file
21
move_semantics/move_semantics1.rs
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
// move_semantics1.rs
|
||||||
|
//
|
||||||
|
// Execute `rustlings hint move_semantics1` or use the `hint` watch subcommand
|
||||||
|
// for a hint.
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn main() {
|
||||||
|
let vec0 = vec![22, 44, 66];
|
||||||
|
|
||||||
|
let vec1 = fill_vec(vec0);
|
||||||
|
|
||||||
|
assert_eq!(vec1, vec![22, 44, 66, 88]);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fill_vec(vec: Vec<i32>) -> Vec<i32> {
|
||||||
|
let mut vec = vec;
|
||||||
|
|
||||||
|
vec.push(88);
|
||||||
|
|
||||||
|
vec
|
||||||
|
}
|
24
move_semantics/move_semantics2.rs
Normal file
24
move_semantics/move_semantics2.rs
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
// move_semantics2.rs
|
||||||
|
//
|
||||||
|
// Make the test pass by finding a way to keep both Vecs separate!
|
||||||
|
//
|
||||||
|
// Execute `rustlings hint move_semantics2` or use the `hint` watch subcommand
|
||||||
|
// for a hint.
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn main() {
|
||||||
|
let vec0 = vec![22, 44, 66];
|
||||||
|
|
||||||
|
let mut vec1 = fill_vec(vec0.clone());
|
||||||
|
|
||||||
|
assert_eq!(vec0, vec![22, 44, 66]);
|
||||||
|
assert_eq!(vec1, vec![22, 44, 66, 88]);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fill_vec(vec: Vec<i32>) -> Vec<i32> {
|
||||||
|
let mut vec = vec;
|
||||||
|
|
||||||
|
vec.push(88);
|
||||||
|
|
||||||
|
vec
|
||||||
|
}
|
22
move_semantics/move_semantics3.rs
Normal file
22
move_semantics/move_semantics3.rs
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
// move_semantics3.rs
|
||||||
|
//
|
||||||
|
// Make me compile without adding new lines -- just changing existing lines! (no
|
||||||
|
// lines with multiple semicolons necessary!)
|
||||||
|
//
|
||||||
|
// Execute `rustlings hint move_semantics3` or use the `hint` watch subcommand
|
||||||
|
// for a hint.
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn main() {
|
||||||
|
let vec0 = vec![22, 44, 66];
|
||||||
|
|
||||||
|
let vec1 = fill_vec(vec0);
|
||||||
|
|
||||||
|
assert_eq!(vec1, vec![22, 44, 66, 88]);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fill_vec(mut vec: Vec<i32>) -> Vec<i32> {
|
||||||
|
vec.push(88);
|
||||||
|
|
||||||
|
vec
|
||||||
|
}
|
25
move_semantics/move_semantics4.rs
Normal file
25
move_semantics/move_semantics4.rs
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
// move_semantics4.rs
|
||||||
|
//
|
||||||
|
// Refactor this code so that instead of passing `vec0` into the `fill_vec`
|
||||||
|
// function, the Vector gets created in the function itself and passed back to
|
||||||
|
// the main function.
|
||||||
|
//
|
||||||
|
// Execute `rustlings hint move_semantics4` or use the `hint` watch subcommand
|
||||||
|
// for a hint.
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn main() {
|
||||||
|
|
||||||
|
let mut vec1 = fill_vec();
|
||||||
|
|
||||||
|
assert_eq!(vec1, vec![22, 44, 66, 88]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// `fill_vec()` no longer takes `vec: Vec<i32>` as argument - don't change this!
|
||||||
|
fn fill_vec() -> Vec<i32> {
|
||||||
|
let mut vec = vec![22, 44, 66];
|
||||||
|
|
||||||
|
vec.push(88);
|
||||||
|
|
||||||
|
vec
|
||||||
|
}
|
17
move_semantics/move_semantics5.rs
Normal file
17
move_semantics/move_semantics5.rs
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
// move_semantics5.rs
|
||||||
|
//
|
||||||
|
// Make me compile only by reordering the lines in `main()`, but without adding,
|
||||||
|
// changing or removing any of them.
|
||||||
|
//
|
||||||
|
// Execute `rustlings hint move_semantics5` or use the `hint` watch subcommand
|
||||||
|
// for a hint.
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn main() {
|
||||||
|
let mut x = 100;
|
||||||
|
let y = &mut x;
|
||||||
|
*y += 100;
|
||||||
|
let z = &mut x;
|
||||||
|
*z += 1000;
|
||||||
|
assert_eq!(x, 1200);
|
||||||
|
}
|
26
move_semantics/move_semantics6.rs
Normal file
26
move_semantics/move_semantics6.rs
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
// move_semantics6.rs
|
||||||
|
//
|
||||||
|
// You can't change anything except adding or removing references.
|
||||||
|
//
|
||||||
|
// Execute `rustlings hint move_semantics6` or use the `hint` watch subcommand
|
||||||
|
// for a hint.
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let data = "Rust is great!".to_string();
|
||||||
|
|
||||||
|
get_char(&data);
|
||||||
|
|
||||||
|
string_uppercase(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should not take ownership
|
||||||
|
fn get_char(data: &String) -> char {
|
||||||
|
data.chars().last().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should take ownership
|
||||||
|
fn string_uppercase(mut data: String) {
|
||||||
|
data = data.to_uppercase();
|
||||||
|
|
||||||
|
println!("{}", data);
|
||||||
|
}
|
21
options/README.md
Normal file
21
options/README.md
Normal file
|
@ -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/stable/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)
|
44
options/options1.rs
Normal file
44
options/options1.rs
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
// options1.rs
|
||||||
|
//
|
||||||
|
// Execute `rustlings hint options1` or use the `hint` watch subcommand for a
|
||||||
|
// hint.
|
||||||
|
|
||||||
|
// This function returns how much icecream there is left in the fridge.
|
||||||
|
// If it's before 10PM, there's 5 pieces left. At 10PM, someone eats them
|
||||||
|
// all, so there'll be no more left :(
|
||||||
|
fn maybe_icecream(time_of_day: u16) -> Option<u16> {
|
||||||
|
// We use the 24-hour system here, so 10PM is a value of 22 and 12AM is a
|
||||||
|
// value of 0 The Option output should gracefully handle cases where
|
||||||
|
// time_of_day > 23.
|
||||||
|
// TODO: Complete the function body - remember to return an Option!
|
||||||
|
|
||||||
|
if time_of_day < 22 {
|
||||||
|
Some(5)
|
||||||
|
} else if time_of_day > 24 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn check_icecream() {
|
||||||
|
assert_eq!(maybe_icecream(9), Some(5));
|
||||||
|
assert_eq!(maybe_icecream(10), Some(5));
|
||||||
|
assert_eq!(maybe_icecream(23), Some(0));
|
||||||
|
assert_eq!(maybe_icecream(22), Some(0));
|
||||||
|
assert_eq!(maybe_icecream(25), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn raw_value() {
|
||||||
|
// TODO: Fix this test. How do you get at the value contained in the
|
||||||
|
// Option?
|
||||||
|
let icecreams = maybe_icecream(12);
|
||||||
|
assert_eq!(icecreams, Some(5));
|
||||||
|
}
|
||||||
|
}
|
40
options/options2.rs
Normal file
40
options/options2.rs
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
// options2.rs
|
||||||
|
//
|
||||||
|
// Execute `rustlings hint options2` or use the `hint` watch subcommand for a
|
||||||
|
// hint.
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
#[test]
|
||||||
|
fn simple_option() {
|
||||||
|
let target = "rustlings";
|
||||||
|
let optional_target = Some(target);
|
||||||
|
|
||||||
|
// TODO: Make this an if let statement whose value is "Some" type
|
||||||
|
if let Some(word) = optional_target {
|
||||||
|
assert_eq!(word, target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn layered_option() {
|
||||||
|
let range = 10;
|
||||||
|
let mut optional_integers: Vec<Option<i8>> = vec![None];
|
||||||
|
|
||||||
|
for i in 1..(range + 1) {
|
||||||
|
optional_integers.push(Some(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut cursor = range;
|
||||||
|
|
||||||
|
// TODO: make this a while let statement - remember that vector.pop also
|
||||||
|
// adds another layer of Option<T>. You can stack `Option<T>`s into
|
||||||
|
// while let and if let.
|
||||||
|
while let Some(Some(integer)) = optional_integers.pop() {
|
||||||
|
assert_eq!(integer, cursor);
|
||||||
|
cursor -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(cursor, 0);
|
||||||
|
}
|
||||||
|
}
|
19
options/options3.rs
Normal file
19
options/options3.rs
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
// options3.rs
|
||||||
|
//
|
||||||
|
// Execute `rustlings hint options3` or use the `hint` watch subcommand for a
|
||||||
|
// hint.
|
||||||
|
|
||||||
|
struct Point {
|
||||||
|
x: i32,
|
||||||
|
y: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let y: Option<Point> = Some(Point { x: 100, y: 200 });
|
||||||
|
|
||||||
|
match y {
|
||||||
|
Some(ref p) => println!("Co-ordinates are {},{} ", p.x, p.y),
|
||||||
|
_ => panic!("no match!"),
|
||||||
|
}
|
||||||
|
y; // Fix without deleting this line.
|
||||||
|
}
|
9
primitive_types/README.md
Normal file
9
primitive_types/README.md
Normal file
|
@ -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/stable/book/ch03-02-data-types.html)
|
||||||
|
- [The Slice Type](https://doc.rust-lang.org/stable/book/ch04-03-slices.html)
|
18
primitive_types/primitive_types1.rs
Normal file
18
primitive_types/primitive_types1.rs
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
// primitive_types1.rs
|
||||||
|
//
|
||||||
|
// Fill in the rest of the line that has code missing! No hints, there's no
|
||||||
|
// tricks, just get used to typing these :)
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// Booleans (`bool`)
|
||||||
|
|
||||||
|
let is_morning = true;
|
||||||
|
if is_morning {
|
||||||
|
println!("Good morning!");
|
||||||
|
}
|
||||||
|
|
||||||
|
let is_evening = true;
|
||||||
|
if is_evening {
|
||||||
|
println!("Good evening!");
|
||||||
|
}
|
||||||
|
}
|
31
primitive_types/primitive_types2.rs
Normal file
31
primitive_types/primitive_types2.rs
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
// primitive_types2.rs
|
||||||
|
//
|
||||||
|
// Fill in the rest of the line that has code missing! No hints, there's no
|
||||||
|
// tricks, just get used to typing these :)
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// Characters (`char`)
|
||||||
|
|
||||||
|
// Note the _single_ quotes, these are different from the double quotes
|
||||||
|
// you've been seeing around.
|
||||||
|
let my_first_initial = 'C';
|
||||||
|
if my_first_initial.is_alphabetic() {
|
||||||
|
println!("Alphabetical!");
|
||||||
|
} else if my_first_initial.is_numeric() {
|
||||||
|
println!("Numerical!");
|
||||||
|
} else {
|
||||||
|
println!("Neither alphabetic nor numeric!");
|
||||||
|
}
|
||||||
|
|
||||||
|
let your_character = '3';
|
||||||
|
// Finish this line like the example! What's your favorite character?
|
||||||
|
// Try a letter, try a number, try a special character, try a character
|
||||||
|
// from a different language than your own, try an emoji!
|
||||||
|
if your_character.is_alphabetic() {
|
||||||
|
println!("Alphabetical!");
|
||||||
|
} else if your_character.is_numeric() {
|
||||||
|
println!("Numerical!");
|
||||||
|
} else {
|
||||||
|
println!("Neither alphabetic nor numeric!");
|
||||||
|
}
|
||||||
|
}
|
17
primitive_types/primitive_types3.rs
Normal file
17
primitive_types/primitive_types3.rs
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
// primitive_types3.rs
|
||||||
|
//
|
||||||
|
// Create an array with at least 100 elements in it where the ??? is.
|
||||||
|
//
|
||||||
|
// Execute `rustlings hint primitive_types3` or use the `hint` watch subcommand
|
||||||
|
// for a hint.
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let a = [69; 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")
|
||||||
|
}
|
||||||
|
}
|
15
primitive_types/primitive_types4.rs
Normal file
15
primitive_types/primitive_types4.rs
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
// primitive_types4.rs
|
||||||
|
//
|
||||||
|
// Get a slice out of Array a where the ??? is so that the test passes.
|
||||||
|
//
|
||||||
|
// Execute `rustlings hint primitive_types4` or use the `hint` watch subcommand
|
||||||
|
// for a hint.
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn slice_out_of_array() {
|
||||||
|
let a: [u8; 5] = [1, 2, 3, 4, 5];
|
||||||
|
|
||||||
|
let nice_slice = &a[1..4];
|
||||||
|
|
||||||
|
assert_eq!([2, 3, 4], nice_slice)
|
||||||
|
}
|
13
primitive_types/primitive_types5.rs
Normal file
13
primitive_types/primitive_types5.rs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
// primitive_types5.rs
|
||||||
|
//
|
||||||
|
// Destructure the `cat` tuple so that the println will work.
|
||||||
|
//
|
||||||
|
// Execute `rustlings hint primitive_types5` or use the `hint` watch subcommand
|
||||||
|
// for a hint.
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let cat = ("Furry McFurson", 3.5);
|
||||||
|
let (name, age) = cat;
|
||||||
|
|
||||||
|
println!("{} is {} years old.", name, age);
|
||||||
|
}
|
17
primitive_types/primitive_types6.rs
Normal file
17
primitive_types/primitive_types6.rs
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
// primitive_types6.rs
|
||||||
|
//
|
||||||
|
// Use a tuple index to access the second element of `numbers`. You can put the
|
||||||
|
// expression for the second element where ??? is so that the test passes.
|
||||||
|
//
|
||||||
|
// Execute `rustlings hint primitive_types6` or use the `hint` watch subcommand
|
||||||
|
// for a hint.
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn indexing_tuple() {
|
||||||
|
let numbers = (1, 2, 3);
|
||||||
|
// Replace below ??? with the tuple indexing syntax.
|
||||||
|
let second = numbers.1;
|
||||||
|
|
||||||
|
assert_eq!(2, second,
|
||||||
|
"This is not the 2nd number in the tuple!")
|
||||||
|
}
|
38
quiz1.rs
Normal file
38
quiz1.rs
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
// quiz1.rs
|
||||||
|
//
|
||||||
|
// This is a quiz for the following sections:
|
||||||
|
// - Variables
|
||||||
|
// - Functions
|
||||||
|
// - If
|
||||||
|
//
|
||||||
|
// Mary is buying apples. The price of an apple is calculated as follows:
|
||||||
|
// - An apple costs 2 rustbucks.
|
||||||
|
// - If Mary buys more than 40 apples, each apple only costs 1 rustbuck!
|
||||||
|
// Write a function that calculates the price of an order of apples given the
|
||||||
|
// quantity bought.
|
||||||
|
//
|
||||||
|
// No hints this time ;)
|
||||||
|
|
||||||
|
fn calculate_price_of_apples(qty: u8) -> u8 {
|
||||||
|
let price = if qty > 40 {
|
||||||
|
1
|
||||||
|
} else {
|
||||||
|
2
|
||||||
|
};
|
||||||
|
|
||||||
|
qty * price
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't modify this function!
|
||||||
|
#[test]
|
||||||
|
fn verify_test() {
|
||||||
|
let price1 = calculate_price_of_apples(35);
|
||||||
|
let price2 = calculate_price_of_apples(40);
|
||||||
|
let price3 = calculate_price_of_apples(41);
|
||||||
|
let price4 = calculate_price_of_apples(65);
|
||||||
|
|
||||||
|
assert_eq!(70, price1);
|
||||||
|
assert_eq!(80, price2);
|
||||||
|
assert_eq!(41, price3);
|
||||||
|
assert_eq!(65, price4);
|
||||||
|
}
|
70
quiz2.rs
Normal file
70
quiz2.rs
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
// quiz2.rs
|
||||||
|
//
|
||||||
|
// This is a quiz for the following sections:
|
||||||
|
// - Strings
|
||||||
|
// - Vecs
|
||||||
|
// - Move semantics
|
||||||
|
// - Modules
|
||||||
|
// - Enums
|
||||||
|
//
|
||||||
|
// 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 a 2-length tuple,
|
||||||
|
// the first element is the string, the second one is the command.
|
||||||
|
// - The output element is going to be a Vector of strings.
|
||||||
|
//
|
||||||
|
// No hints this time!
|
||||||
|
|
||||||
|
pub enum Command {
|
||||||
|
Uppercase,
|
||||||
|
Trim,
|
||||||
|
Append(usize),
|
||||||
|
}
|
||||||
|
|
||||||
|
mod my_module {
|
||||||
|
use super::Command;
|
||||||
|
|
||||||
|
// TODO: Complete the function signature!
|
||||||
|
pub fn transformer(input: Vec<(String, Command)>) -> Vec<String> {
|
||||||
|
let mut output: Vec<String> = vec![];
|
||||||
|
|
||||||
|
for (string, command) in input.iter() {
|
||||||
|
match command {
|
||||||
|
Command::Uppercase => output.push(string.to_uppercase()),
|
||||||
|
Command::Trim => output.push(string.trim().to_string()),
|
||||||
|
Command::Append(n) => output.push(format!(
|
||||||
|
"{}{}",
|
||||||
|
string.to_string(),
|
||||||
|
std::iter::repeat("bar").take(*n).collect::<String>()
|
||||||
|
)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
output
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
// TODO: What do we need to import to have `transformer` in scope?
|
||||||
|
use super::Command;
|
||||||
|
use crate::my_module::transformer;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn it_works() {
|
||||||
|
let output = transformer(vec![
|
||||||
|
("hello".into(), Command::Uppercase),
|
||||||
|
(" all roads lead to rome! ".into(), Command::Trim),
|
||||||
|
("foo".into(), Command::Append(1)),
|
||||||
|
("bar".into(), Command::Append(5)),
|
||||||
|
]);
|
||||||
|
assert_eq!(output[0], "HELLO");
|
||||||
|
assert_eq!(output[1], "all roads lead to rome!");
|
||||||
|
assert_eq!(output[2], "foobar");
|
||||||
|
assert_eq!(output[3], "barbarbarbarbarbar");
|
||||||
|
}
|
||||||
|
}
|
64
quiz3.rs
Normal file
64
quiz3.rs
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
// quiz3.rs
|
||||||
|
//
|
||||||
|
// This quiz tests:
|
||||||
|
// - Generics
|
||||||
|
// - Traits
|
||||||
|
//
|
||||||
|
// 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. Change the Grade in the second test to
|
||||||
|
// "A+" to show that your changes allow alphabetical grades.
|
||||||
|
//
|
||||||
|
// Execute `rustlings hint quiz3` or use the `hint` watch subcommand for a hint.
|
||||||
|
|
||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
pub struct ReportCard<T> {
|
||||||
|
pub grade: T,
|
||||||
|
pub student_name: String,
|
||||||
|
pub student_age: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Display> ReportCard<T> {
|
||||||
|
pub fn print(&self) -> String {
|
||||||
|
format!("{} ({}) - achieved a grade of {}",
|
||||||
|
&self.student_name, &self.student_age, &self.grade)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn generate_numeric_report_card() {
|
||||||
|
let report_card = ReportCard {
|
||||||
|
grade: 2.1,
|
||||||
|
student_name: "Tom Wriggle".to_string(),
|
||||||
|
student_age: 12,
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
report_card.print(),
|
||||||
|
"Tom Wriggle (12) - achieved a grade of 2.1"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn generate_alphabetic_report_card() {
|
||||||
|
// TODO: Make sure to change the grade here after you finish the exercise.
|
||||||
|
let report_card = ReportCard {
|
||||||
|
grade: "A+",
|
||||||
|
student_name: "Gary Plotter".to_string(),
|
||||||
|
student_age: 11,
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
report_card.print(),
|
||||||
|
"Gary Plotter (11) - achieved a grade of A+"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
12
smart_pointers/README.md
Normal file
12
smart_pointers/README.md
Normal file
|
@ -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)
|
43
smart_pointers/arc1.rs
Normal file
43
smart_pointers/arc1.rs
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
// arc1.rs
|
||||||
|
//
|
||||||
|
// In this exercise, we are given a Vec of u32 called "numbers" with values
|
||||||
|
// ranging from 0 to 99 -- [ 0, 1, 2, ..., 98, 99 ] We would like to use this
|
||||||
|
// set of numbers within 8 different threads simultaneously. Each thread is
|
||||||
|
// going to get the sum of every eighth value, with an offset.
|
||||||
|
//
|
||||||
|
// The first thread (offset 0), will sum 0, 8, 16, ...
|
||||||
|
// The second thread (offset 1), will sum 1, 9, 17, ...
|
||||||
|
// The third thread (offset 2), will sum 2, 10, 18, ...
|
||||||
|
// ...
|
||||||
|
// The eighth thread (offset 7), will sum 7, 15, 23, ...
|
||||||
|
//
|
||||||
|
// Because we are using threads, our values need to be thread-safe. Therefore,
|
||||||
|
// we are using Arc. We need to make a change in each of the two TODOs.
|
||||||
|
//
|
||||||
|
// Make this code compile by filling in a value for `shared_numbers` where the
|
||||||
|
// first TODO comment is, and create an initial binding for `child_numbers`
|
||||||
|
// where the second TODO comment is. Try not to create any copies of the
|
||||||
|
// `numbers` Vec!
|
||||||
|
//
|
||||||
|
// Execute `rustlings hint arc1` or use the `hint` watch subcommand for a hint.
|
||||||
|
|
||||||
|
#![forbid(unused_imports)] // Do not change this, (or the next) line.
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::thread;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let numbers: Vec<_> = (0..100u32).collect();
|
||||||
|
let shared_numbers = Arc::new(numbers);
|
||||||
|
let mut joinhandles = Vec::new();
|
||||||
|
|
||||||
|
for offset in 0..8 {
|
||||||
|
let child_numbers = shared_numbers.clone();
|
||||||
|
joinhandles.push(thread::spawn(move || {
|
||||||
|
let sum: u32 = child_numbers.iter().filter(|&&n| n % 8 == offset).sum();
|
||||||
|
println!("Sum of offset {} is {}", offset, sum);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
for handle in joinhandles.into_iter() {
|
||||||
|
handle.join().unwrap();
|
||||||
|
}
|
||||||
|
}
|
56
smart_pointers/box1.rs
Normal file
56
smart_pointers/box1.rs
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
// box1.rs
|
||||||
|
//
|
||||||
|
// At compile time, Rust needs to know how much space a type takes up. This
|
||||||
|
// becomes problematic for recursive types, where a value can have as part of
|
||||||
|
// itself another value of the same type. To get around the issue, we can use a
|
||||||
|
// `Box` - a smart pointer used to store data on the heap, which also allows us
|
||||||
|
// to wrap a recursive type.
|
||||||
|
//
|
||||||
|
// The recursive type we're implementing in this exercise is the `cons list` - a
|
||||||
|
// data structure frequently found in functional programming languages. Each
|
||||||
|
// item in a cons list contains two elements: the value of the current item and
|
||||||
|
// the next item. The last item is a value called `Nil`.
|
||||||
|
//
|
||||||
|
// Step 1: use a `Box` in the enum definition to make the code compile
|
||||||
|
// Step 2: create both empty and non-empty cons lists by replacing `todo!()`
|
||||||
|
//
|
||||||
|
// Note: the tests should not be changed
|
||||||
|
//
|
||||||
|
// Execute `rustlings hint box1` or use the `hint` watch subcommand for a hint.
|
||||||
|
|
||||||
|
#[derive(PartialEq, Debug)]
|
||||||
|
pub enum List {
|
||||||
|
Cons(i32, Box<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()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_empty_list() -> List {
|
||||||
|
List::Nil
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_non_empty_list() -> List {
|
||||||
|
List::Cons(3, Box::new(List::Nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_create_empty_list() {
|
||||||
|
assert_eq!(List::Nil, create_empty_list())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_create_non_empty_list() {
|
||||||
|
assert_ne!(create_empty_list(), create_non_empty_list())
|
||||||
|
}
|
||||||
|
}
|
79
smart_pointers/cow1.rs
Normal file
79
smart_pointers/cow1.rs
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
// cow1.rs
|
||||||
|
//
|
||||||
|
// This exercise explores the Cow, or Clone-On-Write type. Cow is a
|
||||||
|
// clone-on-write smart pointer. It can enclose and provide immutable access to
|
||||||
|
// borrowed data, and clone the data lazily when mutation or ownership is
|
||||||
|
// required. The type is designed to work with general borrowed data via the
|
||||||
|
// Borrow trait.
|
||||||
|
//
|
||||||
|
// This exercise is meant to show you what to expect when passing data to Cow.
|
||||||
|
// Fix the unit tests by checking for Cow::Owned(_) and Cow::Borrowed(_) at the
|
||||||
|
// TODO markers.
|
||||||
|
//
|
||||||
|
// Execute `rustlings hint cow1` or use the `hint` watch subcommand for a hint.
|
||||||
|
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
fn abs_all<'a, 'b>(input: &'a mut Cow<'b, [i32]>) -> &'a mut Cow<'b, [i32]> {
|
||||||
|
for i in 0..input.len() {
|
||||||
|
let v = input[i];
|
||||||
|
if v < 0 {
|
||||||
|
// Clones into a vector if not already owned.
|
||||||
|
input.to_mut()[i] = -v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
input
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reference_mutation() -> Result<(), &'static str> {
|
||||||
|
// Clone occurs because `input` needs to be mutated.
|
||||||
|
let slice = [-1, 0, 1];
|
||||||
|
let mut input = Cow::from(&slice[..]);
|
||||||
|
match abs_all(&mut input) {
|
||||||
|
Cow::Owned(_) => Ok(()),
|
||||||
|
_ => Err("Expected owned value"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reference_no_mutation() -> Result<(), &'static str> {
|
||||||
|
// No clone occurs because `input` doesn't need to be mutated.
|
||||||
|
let slice = [0, 1, 2];
|
||||||
|
let mut input = Cow::from(&slice[..]);
|
||||||
|
match abs_all(&mut input) {
|
||||||
|
Cow::Borrowed(_) => Ok(()),
|
||||||
|
_ => Err("Expected borrowed value"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn owned_no_mutation() -> Result<(), &'static str> {
|
||||||
|
// We can also pass `slice` without `&` so Cow owns it directly. In this
|
||||||
|
// case no mutation occurs and thus also no clone, but the result is
|
||||||
|
// still owned because it was never borrowed or mutated.
|
||||||
|
let slice = vec![0, 1, 2];
|
||||||
|
let mut input = Cow::from(slice);
|
||||||
|
match abs_all(&mut input) {
|
||||||
|
Cow::Owned(_) => Ok(()),
|
||||||
|
_ => Err("Expected owned value"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn owned_mutation() -> Result<(), &'static str> {
|
||||||
|
// Of course this is also the case if a mutation does occur. In this
|
||||||
|
// case the call to `to_mut()` in the abs_all() function returns a
|
||||||
|
// reference to the same data as before.
|
||||||
|
let slice = vec![-1, 0, 1];
|
||||||
|
let mut input = Cow::from(slice);
|
||||||
|
match abs_all(&mut input) {
|
||||||
|
Cow::Owned(_) => Ok(()),
|
||||||
|
_ => Err("Expected owned value"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue