Compare commits

..

33 Commits

Author SHA1 Message Date
7a1ca00837 solution to exercise 04_05 2024-07-13 17:39:46 +02:00
1789d43730 solution to exercise 04_04 2024-07-13 17:30:01 +02:00
604a5a20ae solution to exercise 04_03 2024-07-01 16:30:43 +02:00
0829174983 solution to exercise 04_02 2024-07-01 16:23:02 +02:00
ebe07acd1e solution to exercise 04_01 2024-07-01 16:15:42 +02:00
4a4fc2ea0d solution to exercise 03_12 2024-06-27 16:38:30 +02:00
91e6bf5ae4 solution to exercise 03_11 2024-06-27 16:29:31 +02:00
3f57819e4c solution to exercise 03_10 2024-06-27 16:24:32 +02:00
d54a1e160b solution to exercise 03_09 2024-06-27 16:16:19 +02:00
4913da035f solution to exercise 03_08 2024-06-27 16:08:42 +02:00
1f0823a6a4 solution to exercise 03_07 2024-06-23 18:22:03 +02:00
56075fa40e solution to exercise 03_06 2024-06-23 18:08:59 +02:00
852cd5f0b9 solution to exercise 03_05 2024-06-23 17:58:03 +02:00
1c16ac8c22 solution to exercise 03_04 2024-06-23 17:54:39 +02:00
d3b4c0d653 solution to exercise 03_03 2024-06-19 15:03:51 +02:00
8b78ea3a83 solution to exercise 03_02 2024-06-19 14:56:36 +02:00
0e5cdc6d10 solution to exercise 03_01 2024-06-19 14:46:00 +02:00
1d876eff10 solution to exercise 03_00 2024-06-19 14:41:41 +02:00
dfcb9736b2 solution to exercise 02_10 2024-06-17 11:31:06 +02:00
c1e7c3fadd solution to exercise 02_09 2024-06-17 11:22:25 +02:00
978a99b00d solution to exercise 02_08 2024-06-17 11:15:21 +02:00
34189d212e solution to exercise 02_07 2024-06-17 10:59:03 +02:00
9d1869fc9c solution to exercise 02_06 2024-06-17 10:53:22 +02:00
3e56cf1287 solution to exercise 02_05 2024-06-15 21:01:03 +02:00
f91cc0089a solution to exercise 02_04 2024-06-13 17:04:21 +02:00
211f23cea4 solution to exercise 02_03 2024-06-13 16:55:06 +02:00
1e516bf0a5 solution to exercise 02_02 2024-06-13 16:46:44 +02:00
03a0a77394 solution to exercise 02_01 2024-06-13 16:40:40 +02:00
bb27bfad41 solution to exercise 02_00 2024-06-13 16:33:07 +02:00
4d12facc2f solution to exercise 01 2024-06-12 17:12:56 +02:00
1251a6c1b1 solution to exercise 00 2024-06-12 17:06:43 +02:00
Felix Pherry
056611ac1c Fix: typos (#84)
Co-authored-by: Felix Pherry <182051.FELIX@klgroup.local>
2024-06-05 11:59:04 +02:00
Felix Pherry
e99a15390e fix(book): Correct type parameter naming convention to pascal case (#79)
* fix(book): Correct type parameter naming convention to pascal case

* Update book/src/04_traits/05_trait_bounds.md

---------

Co-authored-by: Felix Pherry <182051.FELIX@klgroup.local>
Co-authored-by: Luca Palmieri <20745048+LukeMathWalker@users.noreply.github.com>
2024-06-01 08:31:48 +02:00
34 changed files with 245 additions and 69 deletions

View File

@@ -1,3 +1,6 @@
[workspace]
members = ["exercises/*/*", "helpers/common", "helpers/mdbook-exercise-linker", "helpers/ticket_fields"]
resolver = "2"
[profile.dev]
overflow-checks = false

View File

@@ -58,7 +58,7 @@ We can do better using **generics**.\
Generics allow us to write code that works with a **type parameter** instead of a concrete type:
```rust
fn print_if_even<T>(n: T)
fn print_if_even<T>(n: T)
where
T: IsEven + Debug
{
@@ -125,7 +125,7 @@ body is present.
All the examples above used a **`where` clause** to specify trait bounds:
```rust
fn print_if_even<T>(n: T)
fn print_if_even<T>(n: T)
where
T: IsEven + Debug
// ^^^^^^^^^^^^^^^^^
@@ -160,7 +160,7 @@ fn print_if_even<Number: IsEven + Debug>(n: Number) {
It is actually **desirable** to use meaningful names when there are multiple type parameters at play or when the name
`T` doesn't convey enough information about the type's role in the function.
Maximize clarity and readability when naming type parameters, just as you would with variables or function parameters.
Follow Rust's conventions though: use camel case for type parameter names.
Follow Rust's conventions, though: use [upper camel case for type parameter names](https://rust-lang.github.io/api-guidelines/naming.html#casing-conforms-to-rfc-430-c-case).
## The function signature is king

View File

@@ -7,7 +7,7 @@ pass it to the `panic!` macro.\
This is a (rudimentary) example of **error reporting**: transforming an error type into a representation that can be
shown to a user, a service operator, or a developer.
It's not practical for each Rust developer to come up with their own error reporting strategy: it'd a waste of time
It's not practical for each Rust developer to come up with their own error reporting strategy: it'd be a waste of time
and it wouldn't compose well across projects.
That's why Rust provides the `std::error::Error` trait.

View File

@@ -17,7 +17,7 @@
// You can also find solutions to all exercises in the `solutions` git branch.
fn greeting() -> &'static str {
// TODO: fix me 👇
"I'm ready to __!"
"I'm ready to learn Rust!"
}
// Your solutions will be automatically verified by a set of tests.

View File

@@ -3,7 +3,7 @@
// partner in this course and it'll often guide you in the right direction!
//
// The input parameters should have the same type of the return type.
fn compute(a, b) -> u32 {
fn compute(a: u32, b: u32) -> u32 {
// Don't touch the function body.
a + b * 2
}
@@ -16,4 +16,4 @@ mod tests {
fn case() {
assert_eq!(compute(1, 2), 5);
}
}
}

View File

@@ -1,6 +1,6 @@
fn intro() -> &'static str {
// TODO: fix me 👇
"I'm ready to __!"
"I'm ready to build a calculator in Rust!"
}
#[cfg(test)]

View File

@@ -1,6 +1,6 @@
fn compute(a: u32, b: u32) -> u32 {
// TODO: change the line below to fix the compiler error and make the tests pass.
a + b * 4u8
a + b * 4u32
}
#[cfg(test)]

View File

@@ -8,7 +8,7 @@
pub fn speed(start: u32, end: u32, time_elapsed: u32) -> u32 {
// TODO: define a variable named `distance` with the right value to get tests to pass
// Do you need to annotate the type of `distance`? Why or why not?
let distance: u32 = end - start;
// Don't change the line below
distance / time_elapsed
}

View File

@@ -1,6 +1,6 @@
/// Return `true` if `n` is even, `false` otherwise.
fn is_even(n: u32) -> bool {
todo!()
n % 2 == 0
}
#[cfg(test)]

View File

@@ -2,7 +2,9 @@
/// calculate the average speed of the journey.
fn speed(start: u32, end: u32, time_elapsed: u32) -> u32 {
// TODO: Panic with a custom message if `time_elapsed` is 0
if time_elapsed == 0 {
panic!("The journey took no time at all, that's impossible!");
}
(end - start) / time_elapsed
}

View File

@@ -9,6 +9,13 @@
// `factorial(2)` to return `2`, and so on.
//
// Use only what you learned! No loops yet, so you'll have to use recursion!
fn factorial(n: u16) -> u16 {
if n == 0 {
1
} else {
n * factorial(n - 1)
}
}
#[cfg(test)]
mod tests {

View File

@@ -4,7 +4,13 @@ pub fn factorial(n: u32) -> u32 {
// interprets as "I'll get back to this later", thus
// suppressing type errors.
// It panics at runtime.
todo!()
let mut result: u32 = 1; // base case
let mut i: u32 = 1;
while i <= n {
result *= i;
i += 1;
}
result
}
#[cfg(test)]

View File

@@ -1,6 +1,10 @@
// Rewrite the factorial function using a `for` loop.
pub fn factorial(n: u32) -> u32 {
todo!()
let mut result: u32 = 1; // base case
for i in 2..=n {
result *= i;
}
result
}
#[cfg(test)]

View File

@@ -1,9 +1,9 @@
pub fn factorial(n: u32) -> u32 {
let mut result = 1;
let mut result: u32 = 1;
for i in 1..=n {
// Use saturating multiplication to stop at the maximum value of u32
// rather than overflowing and wrapping around
result *= i;
result = result.saturating_mul(i);
}
result
}

View File

@@ -6,7 +6,7 @@ mod tests {
#[test]
fn u16_to_u32() {
let v: u32 = todo!();
let v: u32 = 47;
assert_eq!(47u16 as u32, v);
}
@@ -24,14 +24,14 @@ mod tests {
// You could solve this by using exactly the same expression as above,
// but that would defeat the purpose of the exercise. Instead, use a genuine
// `i8` value that is equivalent to `255` when converted from `u8`.
let y: i8 = todo!();
let y: i8 = -1;
assert_eq!(x, y);
}
#[test]
fn bool_to_u8() {
let v: u8 = todo!();
let v: u8 = 1;
assert_eq!(true as u8, v);
}
}

View File

@@ -1,6 +1,6 @@
fn intro() -> &'static str {
// TODO: fix me 👇
"I'm ready to __!"
"I'm ready to start modelling a software ticket!"
}
#[cfg(test)]

View File

@@ -4,6 +4,16 @@
//
// It should also have a method named `is_available` that returns a `true` if the quantity is
// greater than 0, otherwise `false`.
struct Order {
price: u8,
quantity: u8,
}
impl Order {
fn is_available(self) -> bool {
self.quantity > 0
}
}
#[cfg(test)]
mod tests {

View File

@@ -17,7 +17,22 @@ impl Ticket {
// as well as some `String` methods. Use the documentation of Rust's standard library
// to find the most appropriate options -> https://doc.rust-lang.org/std/string/struct.String.html
fn new(title: String, description: String, status: String) -> Self {
todo!();
if status != "To-Do" && status != "In Progress" && status != "Done" {
panic!("Only `To-Do`, `In Progress`, and `Done` statuses are allowed")
}
if title.is_empty() {
panic!("Title cannot be empty")
}
if description.is_empty() {
panic!("Description cannot be empty")
}
if title.len() > 50 {
panic!("Title cannot be longer than 50 bytes")
}
if description.len() > 500 {
panic!("Description cannot be longer than 500 bytes")
}
Self {
title,
description,

View File

@@ -1,6 +1,7 @@
mod helpers {
// TODO: Make this code compile, either by adding a `use` statement or by using
// the appropriate path to refer to the `Ticket` struct.
use super::Ticket;
fn create_todo_ticket(title: String, description: String) -> Ticket {
Ticket::new(title, description, "To-Do".into())

View File

@@ -1,12 +1,12 @@
mod ticket {
struct Ticket {
pub struct Ticket {
title: String,
description: String,
status: String,
}
impl Ticket {
fn new(title: String, description: String, status: String) -> Ticket {
pub fn new(title: String, description: String, status: String) -> Ticket {
if title.is_empty() {
panic!("Title cannot be empty");
}
@@ -55,7 +55,7 @@ mod tests {
//
// TODO: Once you have verified that the below does not compile,
// comment the line out to move on to the next exercise!
assert_eq!(ticket.description, "A description");
// assert_eq!(ticket.description, "A description");
}
fn encapsulation_cannot_be_violated() {
@@ -68,10 +68,10 @@ mod tests {
//
// TODO: Once you have verified that the below does not compile,
// comment the lines out to move on to the next exercise!
let ticket = Ticket {
title: "A title".into(),
description: "A description".into(),
status: "To-Do".into(),
};
// let ticket = Ticket {
// title: "A title".into(),
// description: "A description".into(),
// status: "To-Do".into(),
// };
}
}

View File

@@ -34,6 +34,17 @@ pub mod ticket {
// - `title` that returns the `title` field.
// - `description` that returns the `description` field.
// - `status` that returns the `status` field.
pub fn title(self) -> String {
self.title
}
pub fn description(self) -> String {
self.description
}
pub fn status(self) -> String {
self.status
}
}
}

View File

@@ -34,16 +34,16 @@ impl Ticket {
}
}
pub fn title(self) -> String {
self.title
pub fn title(&self) -> &String {
&self.title
}
pub fn description(self) -> String {
self.description
pub fn description(&self) -> &String {
&self.description
}
pub fn status(self) -> String {
self.status
pub fn status(&self) -> &String {
&self.status
}
}

View File

@@ -11,21 +11,9 @@ pub struct Ticket {
impl Ticket {
pub fn new(title: String, description: String, status: String) -> Ticket {
if title.is_empty() {
panic!("Title cannot be empty");
}
if title.len() > 50 {
panic!("Title cannot be longer than 50 bytes");
}
if description.is_empty() {
panic!("Description cannot be empty");
}
if description.len() > 500 {
panic!("Description cannot be longer than 500 bytes");
}
if status != "To-Do" && status != "In Progress" && status != "Done" {
panic!("Only `To-Do`, `In Progress`, and `Done` statuses are allowed");
}
validate_title(&title);
validate_description(&description);
validate_status(&status);
Ticket {
title,
@@ -45,6 +33,45 @@ impl Ticket {
pub fn status(&self) -> &String {
&self.status
}
pub fn set_title(&mut self, title: String) {
validate_title(&title);
self.title = title;
}
pub fn set_description(&mut self, description: String) {
validate_description(&description);
self.description = description;
}
pub fn set_status(&mut self, status: String) {
validate_status(&status);
self.status = status;
}
}
fn validate_title(title: &String) {
if title.is_empty() {
panic!("Title cannot be empty");
}
if title.len() > 50 {
panic!("Title cannot be longer than 50 bytes");
}
}
fn validate_description(description: &String) {
if description.is_empty() {
panic!("Description cannot be empty");
}
if description.len() > 500 {
panic!("Description cannot be longer than 500 bytes");
}
}
fn validate_status(status: &String) {
if status != "To-Do" && status != "In Progress" && status != "Done" {
panic!("Only `To-Do`, `In Progress`, and `Done` statuses are allowed");
}
}
#[cfg(test)]

View File

@@ -6,16 +6,16 @@ mod tests {
#[test]
fn u16_size() {
assert_eq!(size_of::<u16>(), todo!());
assert_eq!(size_of::<u16>(), 2);
}
#[test]
fn i32_size() {
assert_eq!(size_of::<i32>(), todo!());
assert_eq!(size_of::<i32>(), 4);
}
#[test]
fn bool_size() {
assert_eq!(size_of::<bool>(), todo!());
assert_eq!(size_of::<bool>(), 1);
}
}

View File

@@ -13,7 +13,7 @@ mod tests {
#[test]
fn string_size() {
assert_eq!(size_of::<String>(), todo!());
assert_eq!(size_of::<String>(), 24);
}
#[test]
@@ -23,6 +23,6 @@ mod tests {
// but, in general, the memory layout of structs is a more complex topic.
// If you're curious, check out the "Data layout" section of the Rustonomicon
// https://doc.rust-lang.org/nomicon/data.html for more information.
assert_eq!(size_of::<Ticket>(), todo!());
assert_eq!(size_of::<Ticket>(), 72);
}
}

View File

@@ -13,16 +13,16 @@ mod tests {
#[test]
fn u16_ref_size() {
assert_eq!(size_of::<&u16>(), todo!());
assert_eq!(size_of::<&u16>(), 8);
}
#[test]
fn u64_mut_ref_size() {
assert_eq!(size_of::<&mut u64>(), todo!());
assert_eq!(size_of::<&mut u64>(), 8);
}
#[test]
fn ticket_ref_size() {
assert_eq!(size_of::<&Ticket>(), todo!());
assert_eq!(size_of::<&Ticket>(), 8);
}
}

View File

@@ -2,7 +2,7 @@
// We'll pick the concept up again in a later chapter after covering traits and
// interior mutability.
fn outro() -> &'static str {
"I have a basic understanding of __!"
"I have a basic understanding of destructors!"
}
#[cfg(test)]

View File

@@ -11,3 +11,74 @@
// Integration here has a very specific meaning: they test **the public API** of your project.
// You'll need to pay attention to the visibility of your types and methods; integration
// tests can't access private or `pub(crate)` items.
pub struct Order {
product_name: String,
quantity: u32,
unit_price: u32,
}
impl Order {
pub fn new(product_name: String, quantity: u32, unit_price: u32) -> Order {
validate_product_name(&product_name);
validate_quantity(&quantity);
validate_unit_price(&unit_price);
Order {
product_name,
quantity,
unit_price,
}
}
pub fn product_name(&self) -> &String {
&self.product_name
}
pub fn quantity(&self) -> &u32 {
&self.quantity
}
pub fn unit_price(&self) -> &u32 {
&self.unit_price
}
pub fn set_product_name(&mut self, product_name: String) {
validate_product_name(&product_name);
self.product_name = product_name;
}
pub fn set_quantity(&mut self, quantity: u32) {
validate_quantity(&quantity);
self.quantity = quantity;
}
pub fn set_unit_price(&mut self, unit_price: u32) {
validate_unit_price(&unit_price);
self.unit_price = unit_price;
}
pub fn total(&self) -> u32 {
self.quantity * self.unit_price
}
}
fn validate_product_name(product_name: &String) {
if product_name.is_empty() {
panic!("Product name cannot be empty");
}
if product_name.len() > 300 {
panic!("Product name cannot be longer than 300 bytes");
}
}
fn validate_quantity(quantity: &u32) {
if quantity == &0 {
panic!("Quantity must be greater than zero");
}
}
fn validate_unit_price(unit_price: &u32) {
if unit_price == &0 {
panic!("Unit price must be greater than zero");
}
}

View File

@@ -1,6 +1,6 @@
fn intro() -> &'static str {
// TODO: fix me 👇
"I'm ready to __!"
"I'm ready to learn about traits!"
}
#[cfg(test)]

View File

@@ -3,6 +3,22 @@
//
// Then implement the trait for `u32` and `i32`.
pub trait IsEven {
fn is_even(self) -> bool;
}
impl IsEven for u32 {
fn is_even(self) -> bool {
self % 2 == 0
}
}
impl IsEven for i32 {
fn is_even(self) -> bool {
self % 2 == 0
}
}
#[cfg(test)]
mod tests {
use super::*;

View File

@@ -3,9 +3,3 @@
// a foreign type (`u32`, from `std`).
// Look at the compiler error to get familiar with what it looks like.
// Then delete the code below and move on to the next exercise.
impl PartialEq for u32 {
fn eq(&self, _other: &Self) -> bool {
todo!()
}
}

View File

@@ -8,7 +8,13 @@ struct Ticket {
// TODO: Implement the `PartialEq` trait for `Ticket`.
impl PartialEq for Ticket {}
impl PartialEq for Ticket {
fn eq(&self, other: &Self) -> bool {
self.title == other.title
&& self.description == other.description
&& self.status == other.status
}
}
#[cfg(test)]
mod tests {

View File

@@ -8,7 +8,7 @@
// print both sides of the comparison to the terminal.
// If the compared type doesn't implement `Debug`, it doesn't know how to represent them!
#[derive(PartialEq)]
#[derive(Debug, PartialEq)]
struct Ticket {
title: String,
description: String,

View File

@@ -6,7 +6,10 @@
// collections (e.g. BTreeMap).
/// Return the minimum of two values.
pub fn min<T>(left: T, right: T) -> T {
pub fn min<T>(left: T, right: T) -> T
where
T: Ord,
{
if left <= right {
left
} else {