mirror of
https://github.com/mainmatter/100-exercises-to-learn-rust
synced 2024-12-26 21:58:49 +01:00
Solutions
This commit is contained in:
parent
3f60de3712
commit
97dffa67c8
106 changed files with 1018 additions and 197 deletions
11
Cargo.lock
generated
11
Cargo.lock
generated
|
@ -205,6 +205,7 @@ dependencies = [
|
||||||
name = "bounded"
|
name = "bounded"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"thiserror",
|
||||||
"ticket_fields",
|
"ticket_fields",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -443,6 +444,9 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deps"
|
name = "deps"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deref"
|
name = "deref"
|
||||||
|
@ -1439,6 +1443,9 @@ version = "0.1.0"
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "outro_04"
|
name = "outro_04"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "outro_08"
|
name = "outro_08"
|
||||||
|
@ -2175,6 +2182,7 @@ name = "thiserror_"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"common",
|
"common",
|
||||||
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2316,6 +2324,9 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tryfrom"
|
name = "tryfrom"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tungstenite"
|
name = "tungstenite"
|
||||||
|
|
|
@ -12,3 +12,6 @@ resolver = "2"
|
||||||
# regardless of the "global" setting for `overflow-checks` on the `dev` profile.
|
# regardless of the "global" setting for `overflow-checks` on the `dev` profile.
|
||||||
[profile.dev.package.copy]
|
[profile.dev.package.copy]
|
||||||
overflow-checks = true
|
overflow-checks = true
|
||||||
|
|
||||||
|
[profile.dev]
|
||||||
|
overflow-checks = false
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
// You can also find solutions to all exercises in the `solutions` git branch.
|
// You can also find solutions to all exercises in the `solutions` git branch.
|
||||||
fn greeting() -> &'static str {
|
fn greeting() -> &'static str {
|
||||||
// TODO: fix me 👇
|
// TODO: fix me 👇
|
||||||
"I'm ready to __!"
|
"I'm ready to learn Rust!"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Your solutions will be automatically verified by a set of tests.
|
// Your solutions will be automatically verified by a set of tests.
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
// partner in this course and it'll often guide you in the right direction!
|
// 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.
|
// 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.
|
// Don't touch the function body.
|
||||||
a + b * 2
|
a + b * 2
|
||||||
}
|
}
|
||||||
|
@ -16,4 +16,4 @@ mod tests {
|
||||||
fn case() {
|
fn case() {
|
||||||
assert_eq!(compute(1, 2), 5);
|
assert_eq!(compute(1, 2), 5);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
fn intro() -> &'static str {
|
fn intro() -> &'static str {
|
||||||
// TODO: fix me 👇
|
// TODO: fix me 👇
|
||||||
"I'm ready to __!"
|
"I'm ready to build a calculator in Rust!"
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
fn compute(a: u32, b: u32) -> u32 {
|
fn compute(a: u32, b: u32) -> u32 {
|
||||||
// TODO: change the line below to fix the compiler error and make the tests pass.
|
let multiplier: u32 = 4;
|
||||||
let multiplier: u8 = 4;
|
// ^ You could also omit `: u32` entirely.
|
||||||
|
// The compiler will infer the correct type based on the usage below.
|
||||||
a + b * multiplier
|
a + b * multiplier
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
pub fn speed(start: u32, end: u32, time_elapsed: u32) -> u32 {
|
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
|
// 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?
|
// Do you need to annotate the type of `distance`? Why or why not?
|
||||||
|
let distance = end - start;
|
||||||
|
|
||||||
// Don't change the line below
|
// Don't change the line below
|
||||||
distance / time_elapsed
|
distance / time_elapsed
|
||||||
|
|
|
@ -2,7 +2,13 @@
|
||||||
/// `13` if `n` is divisible by `3`,
|
/// `13` if `n` is divisible by `3`,
|
||||||
/// `17` otherwise.
|
/// `17` otherwise.
|
||||||
fn magic_number(n: u32) -> u32 {
|
fn magic_number(n: u32) -> u32 {
|
||||||
todo!()
|
if n % 2 == 0 {
|
||||||
|
12
|
||||||
|
} else if n % 3 == 0 {
|
||||||
|
13
|
||||||
|
} else {
|
||||||
|
17
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -2,6 +2,9 @@
|
||||||
/// calculate the average speed of the journey.
|
/// calculate the average speed of the journey.
|
||||||
fn speed(start: u32, end: u32, time_elapsed: u32) -> u32 {
|
fn speed(start: u32, end: u32, time_elapsed: u32) -> u32 {
|
||||||
// TODO: Panic with a custom message if `time_elapsed` is 0
|
// 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
|
(end - start) / time_elapsed
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,13 @@
|
||||||
// `factorial(2)` to return `2`, and so on.
|
// `factorial(2)` to return `2`, and so on.
|
||||||
//
|
//
|
||||||
// Use only what you learned! No loops yet, so you'll have to use recursion!
|
// Use only what you learned! No loops yet, so you'll have to use recursion!
|
||||||
|
fn factorial(n: u32) -> u32 {
|
||||||
|
if n == 0 {
|
||||||
|
1
|
||||||
|
} else {
|
||||||
|
n * factorial(n - 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|
|
@ -4,7 +4,13 @@ pub fn factorial(n: u32) -> u32 {
|
||||||
// interprets as "I'll get back to this later", thus
|
// interprets as "I'll get back to this later", thus
|
||||||
// suppressing type errors.
|
// suppressing type errors.
|
||||||
// It panics at runtime.
|
// It panics at runtime.
|
||||||
todo!()
|
let mut result = 1;
|
||||||
|
let mut i = 1;
|
||||||
|
while i <= n {
|
||||||
|
result *= i;
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
// Rewrite the factorial function using a `for` loop.
|
// Rewrite the factorial function using a `for` loop.
|
||||||
pub fn factorial(n: u32) -> u32 {
|
pub fn factorial(n: u32) -> u32 {
|
||||||
todo!()
|
let mut result = 1;
|
||||||
|
for i in 1..=n {
|
||||||
|
result *= i;
|
||||||
|
}
|
||||||
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
pub fn factorial(n: u32) -> u32 {
|
pub fn factorial(n: u32) -> u32 {
|
||||||
let mut result = 1;
|
let mut result: u32 = 1;
|
||||||
for i in 1..=n {
|
for i in 1..=n {
|
||||||
// Use saturating multiplication to stop at the maximum value of u32
|
// Use saturating multiplication to stop at the maximum value of u32
|
||||||
// rather than overflowing and wrapping around
|
// rather than overflowing and wrapping around
|
||||||
result *= i;
|
result = result.saturating_mul(i);
|
||||||
}
|
}
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn u16_to_u32() {
|
fn u16_to_u32() {
|
||||||
let v: u32 = todo!();
|
let v: u32 = 47;
|
||||||
assert_eq!(47u16 as u32, v);
|
assert_eq!(47u16 as u32, v);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,15 +23,15 @@ mod tests {
|
||||||
|
|
||||||
// You could solve this by using exactly the same expression as above,
|
// 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
|
// but that would defeat the purpose of the exercise. Instead, use a genuine
|
||||||
// `i8` value that is equivalent to `255` when converted to `u8`.
|
// `i8` value that is equivalent to `255` when converted from `u8`.
|
||||||
let y: i8 = todo!();
|
let y: i8 = -1;
|
||||||
|
|
||||||
assert_eq!(x, y);
|
assert_eq!(x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn bool_to_u8() {
|
fn bool_to_u8() {
|
||||||
let v: u8 = todo!();
|
let v: u8 = 1;
|
||||||
assert_eq!(true as u8, v);
|
assert_eq!(true as u8, v);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
fn intro() -> &'static str {
|
fn intro() -> &'static str {
|
||||||
// TODO: fix me 👇
|
// TODO: fix me 👇
|
||||||
"I'm ready to __!"
|
"I'm ready to start modelling a software ticket!"
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -4,6 +4,16 @@
|
||||||
//
|
//
|
||||||
// It should also have a method named `is_available` that returns a `true` if the quantity is
|
// It should also have a method named `is_available` that returns a `true` if the quantity is
|
||||||
// greater than 0, otherwise `false`.
|
// greater than 0, otherwise `false`.
|
||||||
|
struct Order {
|
||||||
|
price: u32,
|
||||||
|
quantity: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Order {
|
||||||
|
fn is_available(&self) -> bool {
|
||||||
|
self.quantity > 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|
|
@ -18,7 +18,24 @@ impl Ticket {
|
||||||
// as well as some `String` methods. Use the documentation of Rust's standard library
|
// 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
|
// 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 {
|
fn new(title: String, description: String, status: String) -> Self {
|
||||||
todo!();
|
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");
|
||||||
|
}
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
title,
|
title,
|
||||||
description,
|
description,
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
mod helpers {
|
mod helpers {
|
||||||
// TODO: Make this code compile, either by adding a `use` statement or by using
|
// TODO: Make this code compile, either by adding a `use` statement or by using
|
||||||
// the appropriate path to refer to the `Ticket` struct.
|
// the appropriate path to refer to the `Ticket` struct.
|
||||||
|
use super::Ticket;
|
||||||
|
|
||||||
fn create_todo_ticket(title: String, description: String) -> Ticket {
|
fn create_todo_ticket(title: String, description: String) -> Ticket {
|
||||||
Ticket::new(title, description, "To-Do".into())
|
Ticket::new(title, description, "To-Do".into())
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
mod ticket {
|
mod ticket {
|
||||||
struct Ticket {
|
pub struct Ticket {
|
||||||
title: String,
|
title: String,
|
||||||
description: String,
|
description: String,
|
||||||
status: String,
|
status: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Ticket {
|
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() {
|
if title.is_empty() {
|
||||||
panic!("Title cannot be empty");
|
panic!("Title cannot be empty");
|
||||||
}
|
}
|
||||||
|
@ -55,7 +55,7 @@ mod tests {
|
||||||
//
|
//
|
||||||
// TODO: Once you have verified that the below does not compile,
|
// TODO: Once you have verified that the below does not compile,
|
||||||
// comment the line out to move on to the next exercise!
|
// 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() {
|
fn encapsulation_cannot_be_violated() {
|
||||||
|
@ -68,10 +68,10 @@ mod tests {
|
||||||
//
|
//
|
||||||
// TODO: Once you have verified that the below does not compile,
|
// TODO: Once you have verified that the below does not compile,
|
||||||
// comment the lines out to move on to the next exercise!
|
// comment the lines out to move on to the next exercise!
|
||||||
let ticket = Ticket {
|
// let ticket = Ticket {
|
||||||
title: "A title".into(),
|
// title: "A title".into(),
|
||||||
description: "A description".into(),
|
// description: "A description".into(),
|
||||||
status: "To-Do".into(),
|
// status: "To-Do".into(),
|
||||||
};
|
// };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,6 +34,17 @@ pub mod ticket {
|
||||||
// - `title` that returns the `title` field.
|
// - `title` that returns the `title` field.
|
||||||
// - `description` that returns the `description` field.
|
// - `description` that returns the `description` field.
|
||||||
// - `status` that returns the `status` 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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,16 +34,16 @@ impl Ticket {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn title(self) -> String {
|
pub fn title(&self) -> &String {
|
||||||
self.title
|
&self.title
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn description(self) -> String {
|
pub fn description(&self) -> &String {
|
||||||
self.description
|
&self.description
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn status(self) -> String {
|
pub fn status(&self) -> &String {
|
||||||
self.status
|
&self.status
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,21 +11,9 @@ pub struct Ticket {
|
||||||
|
|
||||||
impl Ticket {
|
impl Ticket {
|
||||||
pub fn new(title: String, description: String, status: String) -> Ticket {
|
pub fn new(title: String, description: String, status: String) -> Ticket {
|
||||||
if title.is_empty() {
|
validate_title(&title);
|
||||||
panic!("Title cannot be empty");
|
validate_description(&description);
|
||||||
}
|
validate_status(&status);
|
||||||
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");
|
|
||||||
}
|
|
||||||
|
|
||||||
Ticket {
|
Ticket {
|
||||||
title,
|
title,
|
||||||
|
@ -45,6 +33,45 @@ impl Ticket {
|
||||||
pub fn status(&self) -> &String {
|
pub fn status(&self) -> &String {
|
||||||
&self.status
|
&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)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -6,16 +6,16 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn u16_size() {
|
fn u16_size() {
|
||||||
assert_eq!(size_of::<u16>(), todo!());
|
assert_eq!(size_of::<u16>(), 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn i32_size() {
|
fn i32_size() {
|
||||||
assert_eq!(size_of::<i32>(), todo!());
|
assert_eq!(size_of::<i32>(), 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn bool_size() {
|
fn bool_size() {
|
||||||
assert_eq!(size_of::<bool>(), todo!());
|
assert_eq!(size_of::<bool>(), 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn string_size() {
|
fn string_size() {
|
||||||
assert_eq!(size_of::<String>(), todo!());
|
assert_eq!(size_of::<String>(), 24);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -21,8 +21,8 @@ mod tests {
|
||||||
// This is a tricky question!
|
// This is a tricky question!
|
||||||
// The "intuitive" answer happens to be the correct answer this time,
|
// The "intuitive" answer happens to be the correct answer this time,
|
||||||
// but, in general, the memory layout of structs is a more complex topic.
|
// but, in general, the memory layout of structs is a more complex topic.
|
||||||
// If you're curious, check out the "Type layout" section of The Rust Reference
|
// If you're curious, check out the "Data layout" section of the Rustonomicon
|
||||||
// https://doc.rust-lang.org/reference/type-layout.html for more information.
|
// https://doc.rust-lang.org/nomicon/data.html for more information.
|
||||||
assert_eq!(size_of::<Ticket>(), todo!());
|
assert_eq!(size_of::<Ticket>(), 72);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,18 +11,21 @@ mod tests {
|
||||||
use super::Ticket;
|
use super::Ticket;
|
||||||
use std::mem::size_of;
|
use std::mem::size_of;
|
||||||
|
|
||||||
|
// All numbers below assume you're running on a 64-bit system.
|
||||||
|
// If you're running on a 32-bit system, pointers will be 4 bytes long instead of 8.
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn u16_ref_size() {
|
fn u16_ref_size() {
|
||||||
assert_eq!(size_of::<&u16>(), todo!());
|
assert_eq!(size_of::<&u16>(), 8);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn u64_mut_ref_size() {
|
fn u64_mut_ref_size() {
|
||||||
assert_eq!(size_of::<&mut u64>(), todo!());
|
assert_eq!(size_of::<&mut u64>(), 8);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ticket_ref_size() {
|
fn ticket_ref_size() {
|
||||||
assert_eq!(size_of::<&Ticket>(), todo!());
|
assert_eq!(size_of::<&Ticket>(), 8);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
// We'll pick the concept up again in a later chapter after covering traits and
|
// We'll pick the concept up again in a later chapter after covering traits and
|
||||||
// interior mutability.
|
// interior mutability.
|
||||||
fn outro() -> &'static str {
|
fn outro() -> &'static str {
|
||||||
"I have a basic understanding of __!"
|
"I have a basic understanding of destructors!"
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -11,3 +11,74 @@
|
||||||
// Integration here has a very specific meaning: they test **the public API** of your project.
|
// 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
|
// You'll need to pay attention to the visibility of your types and methods; integration
|
||||||
// tests can't access private or `pub(crate)` items.
|
// 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
fn intro() -> &'static str {
|
fn intro() -> &'static str {
|
||||||
// TODO: fix me 👇
|
// TODO: fix me 👇
|
||||||
"I'm ready to __!"
|
"I'm ready to learn about traits!"
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -2,6 +2,21 @@
|
||||||
// even, otherwise `false`.
|
// even, otherwise `false`.
|
||||||
//
|
//
|
||||||
// Then implement the trait for `u32` and `i32`.
|
// 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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|
|
@ -3,9 +3,3 @@
|
||||||
// a foreign type (`u32`, from `std`).
|
// a foreign type (`u32`, from `std`).
|
||||||
// Look at the compiler error to get familiar with what it looks like.
|
// 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.
|
// Then delete the code below and move on to the next exercise.
|
||||||
|
|
||||||
impl PartialEq for u32 {
|
|
||||||
fn eq(&self, _other: &Self) -> bool {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -8,7 +8,13 @@ struct Ticket {
|
||||||
|
|
||||||
// TODO: Implement the `PartialEq` trait for `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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
// print both sides of the comparison to the terminal.
|
// 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!
|
// If the compared type doesn't implement `Debug`, it doesn't know how to represent them!
|
||||||
|
|
||||||
#[derive(PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
struct Ticket {
|
struct Ticket {
|
||||||
title: String,
|
title: String,
|
||||||
description: String,
|
description: String,
|
||||||
|
|
|
@ -6,7 +6,10 @@
|
||||||
// collections (e.g. BTreeMap).
|
// collections (e.g. BTreeMap).
|
||||||
|
|
||||||
/// Return the minimum of two values.
|
/// Return the minimum of two values.
|
||||||
pub fn min<T>(left: T, right: T) -> T {
|
pub fn min<T: Ord>(left: T, right: T) -> T {
|
||||||
|
// `Ord` guarantees that the values can be compared.
|
||||||
|
// `PartialOrd` would also make the compiler happy, but it would have different semantics:
|
||||||
|
// it'd either return the minimum value or `right` if they can't be compared.
|
||||||
if left <= right {
|
if left <= right {
|
||||||
left
|
left
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -31,15 +31,15 @@ impl Ticket {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn title(&self) -> &String {
|
pub fn title(&self) -> &str {
|
||||||
&self.title
|
&self.title
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn description(&self) -> &String {
|
pub fn description(&self) -> &str {
|
||||||
&self.description
|
&self.description
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn status(&self) -> &String {
|
pub fn status(&self) -> &str {
|
||||||
&self.status
|
&self.status
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,11 +12,11 @@ pub struct Ticket {
|
||||||
|
|
||||||
impl Ticket {
|
impl Ticket {
|
||||||
pub fn title(&self) -> &str {
|
pub fn title(&self) -> &str {
|
||||||
todo!()
|
self.title.trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn description(&self) -> &str {
|
pub fn description(&self) -> &str {
|
||||||
todo!()
|
self.description.trim()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,5 +3,5 @@ pub fn example() {
|
||||||
// via `std::mem::size_of` will result in a compile-time error.
|
// via `std::mem::size_of` will result in a compile-time error.
|
||||||
//
|
//
|
||||||
// TODO: Comment out the following line and move on to the next exercise.
|
// TODO: Comment out the following line and move on to the next exercise.
|
||||||
std::mem::size_of::<str>();
|
// std::mem::size_of::<str>();
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,12 @@ pub struct WrappingU32 {
|
||||||
value: u32,
|
value: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<u32> for WrappingU32 {
|
||||||
|
fn from(value: u32) -> Self {
|
||||||
|
WrappingU32 { value }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn example() {
|
fn example() {
|
||||||
let wrapping: WrappingU32 = 42.into();
|
let wrapping: WrappingU32 = 42.into();
|
||||||
let wrapping = WrappingU32::from(42);
|
let wrapping = WrappingU32::from(42);
|
||||||
|
|
|
@ -13,6 +13,36 @@
|
||||||
// You don't have to though: it's perfectly okay to write three separate
|
// You don't have to though: it's perfectly okay to write three separate
|
||||||
// implementations manually. Venture further only if you're curious.
|
// implementations manually. Venture further only if you're curious.
|
||||||
|
|
||||||
|
pub trait Power<Exponent = Self> {
|
||||||
|
type Output;
|
||||||
|
|
||||||
|
fn power(&self, n: Exponent) -> Self::Output;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Power<u16> for u32 {
|
||||||
|
type Output = u32;
|
||||||
|
|
||||||
|
fn power(&self, n: u16) -> Self::Output {
|
||||||
|
self.pow(n.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Power<&u32> for u32 {
|
||||||
|
type Output = u32;
|
||||||
|
|
||||||
|
fn power(&self, n: &u32) -> Self::Output {
|
||||||
|
self.power(*n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Power<u32> for u32 {
|
||||||
|
type Output = u32;
|
||||||
|
|
||||||
|
fn power(&self, n: u32) -> Self::Output {
|
||||||
|
self.pow(n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::Power;
|
use super::Power;
|
||||||
|
|
|
@ -2,9 +2,10 @@
|
||||||
// to get the code to compile.
|
// to get the code to compile.
|
||||||
|
|
||||||
pub fn summary(ticket: Ticket) -> (Ticket, Summary) {
|
pub fn summary(ticket: Ticket) -> (Ticket, Summary) {
|
||||||
(ticket, ticket.summary())
|
(ticket.clone(), ticket.summary())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct Ticket {
|
pub struct Ticket {
|
||||||
pub title: String,
|
pub title: String,
|
||||||
pub description: String,
|
pub description: String,
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// TODO: implement the necessary traits to make the test compile and pass.
|
// TODO: implement the necessary traits to make the test compile and pass.
|
||||||
// You *can't* modify the test.
|
// You *can't* modify the test.
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
pub struct WrappingU32 {
|
pub struct WrappingU32 {
|
||||||
value: u32,
|
value: u32,
|
||||||
}
|
}
|
||||||
|
@ -11,6 +12,14 @@ impl WrappingU32 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl std::ops::Add for WrappingU32 {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
fn add(self, rhs: Self) -> Self::Output {
|
||||||
|
Self::new(self.value.wrapping_add(rhs.value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
@ -1,6 +1,27 @@
|
||||||
// TODO: implement a so-called "Drop bomb": a type that panics when dropped
|
// TODO: implement a so-called "Drop bomb": a type that panics when dropped
|
||||||
// unless a certain operation has been performed on it.
|
// unless a certain operation has been performed on it.
|
||||||
// You can see the expected API in the tests below.
|
// You can see the expected API in the tests below.
|
||||||
|
pub struct DropBomb {
|
||||||
|
defused: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DropBomb {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
DropBomb { defused: false }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn defuse(&mut self) {
|
||||||
|
self.defused = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for DropBomb {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if !self.defused {
|
||||||
|
panic!("Boom!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|
|
@ -8,3 +8,75 @@
|
||||||
// It should be possible to print its debug representation.
|
// It should be possible to print its debug representation.
|
||||||
//
|
//
|
||||||
// Tests are located in the `tests` folder—pay attention to the visibility of your types and methods.
|
// Tests are located in the `tests` folder—pay attention to the visibility of your types and methods.
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
|
pub struct SaturatingU16 {
|
||||||
|
value: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u16> for SaturatingU16 {
|
||||||
|
fn from(value: u16) -> Self {
|
||||||
|
Self { value }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u8> for SaturatingU16 {
|
||||||
|
fn from(value: u8) -> Self {
|
||||||
|
Self {
|
||||||
|
value: value.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&u16> for SaturatingU16 {
|
||||||
|
fn from(value: &u16) -> Self {
|
||||||
|
(*value).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&u8> for SaturatingU16 {
|
||||||
|
fn from(value: &u8) -> Self {
|
||||||
|
(*value).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Add for SaturatingU16 {
|
||||||
|
type Output = SaturatingU16;
|
||||||
|
|
||||||
|
fn add(self, rhs: Self) -> Self::Output {
|
||||||
|
self + rhs.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Add<&SaturatingU16> for SaturatingU16 {
|
||||||
|
type Output = SaturatingU16;
|
||||||
|
|
||||||
|
fn add(self, rhs: &SaturatingU16) -> Self::Output {
|
||||||
|
self + *rhs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Add<u16> for SaturatingU16 {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
fn add(self, rhs: u16) -> Self::Output {
|
||||||
|
let sum = self.value.saturating_add(rhs);
|
||||||
|
Self {
|
||||||
|
value: sum,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Add<&u16> for SaturatingU16 {
|
||||||
|
type Output = SaturatingU16;
|
||||||
|
|
||||||
|
fn add(self, rhs: &u16) -> Self::Output {
|
||||||
|
self + *rhs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq<u16> for SaturatingU16 {
|
||||||
|
fn eq(&self, other: &u16) -> bool {
|
||||||
|
self.value == *other
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
fn intro() -> &'static str {
|
fn intro() -> &'static str {
|
||||||
// TODO: fix me 👇
|
// TODO: fix me 👇
|
||||||
"I'm ready to __!"
|
"I'm ready to refine the `Ticket` type!"
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -7,15 +7,18 @@
|
||||||
struct Ticket {
|
struct Ticket {
|
||||||
title: String,
|
title: String,
|
||||||
description: String,
|
description: String,
|
||||||
status: String,
|
status: Status,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Copy, Clone)]
|
||||||
enum Status {
|
enum Status {
|
||||||
// TODO: add the missing variants
|
ToDo,
|
||||||
|
InProgress,
|
||||||
|
Done,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Ticket {
|
impl Ticket {
|
||||||
pub fn new(title: String, description: String, status: String) -> Ticket {
|
pub fn new(title: String, description: String, status: Status) -> Ticket {
|
||||||
if title.is_empty() {
|
if title.is_empty() {
|
||||||
panic!("Title cannot be empty");
|
panic!("Title cannot be empty");
|
||||||
}
|
}
|
||||||
|
@ -28,9 +31,6 @@ impl Ticket {
|
||||||
if description.len() > 500 {
|
if description.len() > 500 {
|
||||||
panic!("Description cannot be longer than 500 bytes");
|
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");
|
|
||||||
}
|
|
||||||
|
|
||||||
Ticket {
|
Ticket {
|
||||||
title,
|
title,
|
||||||
|
@ -47,7 +47,7 @@ impl Ticket {
|
||||||
&self.description
|
&self.description
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn status(&self) -> &String {
|
pub fn status(&self) -> &Status {
|
||||||
&self.status
|
&self.status
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,12 @@ enum Shape {
|
||||||
impl Shape {
|
impl Shape {
|
||||||
// TODO: Implement the `n_sides` method using a `match`.
|
// TODO: Implement the `n_sides` method using a `match`.
|
||||||
pub fn n_sides(&self) -> u8 {
|
pub fn n_sides(&self) -> u8 {
|
||||||
todo!()
|
match self {
|
||||||
|
Shape::Circle => 0,
|
||||||
|
Shape::Square | Shape::Rectangle => 4,
|
||||||
|
Shape::Triangle => 3,
|
||||||
|
Shape::Pentagon => 5,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,10 @@ impl Ticket {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn assigned_to(&self) -> &str {
|
pub fn assigned_to(&self) -> &str {
|
||||||
todo!()
|
match &self.status {
|
||||||
|
Status::InProgress { assigned_to } => assigned_to,
|
||||||
|
_ => panic!("Only `In-Progress` tickets can be assigned to someone"),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,11 @@ impl Shape {
|
||||||
// TODO: Implement the `radius` method using
|
// TODO: Implement the `radius` method using
|
||||||
// either an `if let` or a `let/else`.
|
// either an `if let` or a `let/else`.
|
||||||
pub fn radius(&self) -> f64 {
|
pub fn radius(&self) -> f64 {
|
||||||
todo!()
|
if let Shape::Circle { radius } = self {
|
||||||
|
*radius
|
||||||
|
} else {
|
||||||
|
panic!("Not a circle");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -36,7 +36,10 @@ impl Ticket {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn assigned_to(&self) -> Option<&String> {
|
pub fn assigned_to(&self) -> Option<&String> {
|
||||||
todo!()
|
match &self.status {
|
||||||
|
Status::InProgress { assigned_to } => Some(assigned_to),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,25 +16,25 @@ enum Status {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Ticket {
|
impl Ticket {
|
||||||
pub fn new(title: String, description: String, status: Status) -> Ticket {
|
pub fn new(title: String, description: String, status: Status) -> Result<Ticket, String> {
|
||||||
if title.is_empty() {
|
if title.is_empty() {
|
||||||
panic!("Title cannot be empty");
|
return Err("Title cannot be empty".into());
|
||||||
}
|
}
|
||||||
if title.len() > 50 {
|
if title.len() > 50 {
|
||||||
panic!("Title cannot be longer than 50 bytes");
|
return Err("Title cannot be longer than 50 bytes".into());
|
||||||
}
|
}
|
||||||
if description.is_empty() {
|
if description.is_empty() {
|
||||||
panic!("Description cannot be empty");
|
return Err("Description cannot be empty".into());
|
||||||
}
|
}
|
||||||
if description.len() > 500 {
|
if description.len() > 500 {
|
||||||
panic!("Description cannot be longer than 500 bytes");
|
return Err("Description cannot be longer than 500 bytes".into());
|
||||||
}
|
}
|
||||||
|
|
||||||
Ticket {
|
Ok(Ticket {
|
||||||
title,
|
title,
|
||||||
description,
|
description,
|
||||||
status,
|
status,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,16 @@
|
||||||
// When the description is invalid, instead, it should use a default description:
|
// When the description is invalid, instead, it should use a default description:
|
||||||
// "Description not provided".
|
// "Description not provided".
|
||||||
fn easy_ticket(title: String, description: String, status: Status) -> Ticket {
|
fn easy_ticket(title: String, description: String, status: Status) -> Ticket {
|
||||||
todo!()
|
match Ticket::new(title.clone(), description, status.clone()) {
|
||||||
|
Ok(ticket) => ticket,
|
||||||
|
Err(error) => {
|
||||||
|
if error.contains("Description") {
|
||||||
|
Ticket::new(title, "Description not provided".to_string(), status).unwrap()
|
||||||
|
} else {
|
||||||
|
panic!("{error}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
|
|
|
@ -1,14 +1,24 @@
|
||||||
// TODO: Use two variants, one for a title error and one for a description error.
|
// TODO: Use two variants, one for a title error and one for a description error.
|
||||||
// Each variant should contain a string with the explanation of what went wrong exactly.
|
// Each variant should contain a string with the explanation of what went wrong exactly.
|
||||||
// You'll have to update the implementation of `Ticket::new` as well.
|
// You'll have to update the implementation of `Ticket::new` as well.
|
||||||
enum TicketNewError {}
|
#[derive(Debug)]
|
||||||
|
enum TicketNewError {
|
||||||
|
TitleError(String),
|
||||||
|
DescriptionError(String),
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: `easy_ticket` should panic when the title is invalid, using the error message
|
// TODO: `easy_ticket` should panic when the title is invalid, using the error message
|
||||||
// stored inside the relevant variant of the `TicketNewError` enum.
|
// stored inside the relevant variant of the `TicketNewError` enum.
|
||||||
// When the description is invalid, instead, it should use a default description:
|
// When the description is invalid, instead, it should use a default description:
|
||||||
// "Description not provided".
|
// "Description not provided".
|
||||||
fn easy_ticket(title: String, description: String, status: Status) -> Ticket {
|
fn easy_ticket(title: String, description: String, status: Status) -> Ticket {
|
||||||
todo!()
|
match Ticket::new(title.clone(), description, status.clone()) {
|
||||||
|
Ok(ticket) => ticket,
|
||||||
|
Err(TicketNewError::DescriptionError(_)) => {
|
||||||
|
Ticket::new(title, "Description not provided".to_string(), status).unwrap()
|
||||||
|
}
|
||||||
|
Err(TicketNewError::TitleError(error)) => panic!("{error}"),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
|
@ -32,16 +42,24 @@ impl Ticket {
|
||||||
status: Status,
|
status: Status,
|
||||||
) -> Result<Ticket, TicketNewError> {
|
) -> Result<Ticket, TicketNewError> {
|
||||||
if title.is_empty() {
|
if title.is_empty() {
|
||||||
return Err("Title cannot be empty".to_string());
|
return Err(TicketNewError::TitleError(
|
||||||
|
"Title cannot be empty".to_string(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
if title.len() > 50 {
|
if title.len() > 50 {
|
||||||
return Err("Title cannot be longer than 50 bytes".to_string());
|
return Err(TicketNewError::TitleError(
|
||||||
|
"Title cannot be longer than 50 bytes".to_string(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
if description.is_empty() {
|
if description.is_empty() {
|
||||||
return Err("Description cannot be empty".to_string());
|
return Err(TicketNewError::DescriptionError(
|
||||||
|
"Description cannot be empty".to_string(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
if description.len() > 500 {
|
if description.len() > 500 {
|
||||||
return Err("Description cannot be longer than 500 bytes".to_string());
|
return Err(TicketNewError::DescriptionError(
|
||||||
|
"Description cannot be longer than 500 bytes".to_string(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Ticket {
|
Ok(Ticket {
|
||||||
|
|
|
@ -3,17 +3,37 @@
|
||||||
// The docs for the `std::fmt` module are a good place to start and look for examples:
|
// The docs for the `std::fmt` module are a good place to start and look for examples:
|
||||||
// https://doc.rust-lang.org/std/fmt/index.html#write
|
// https://doc.rust-lang.org/std/fmt/index.html#write
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
enum TicketNewError {
|
enum TicketNewError {
|
||||||
TitleError(String),
|
TitleError(String),
|
||||||
DescriptionError(String),
|
DescriptionError(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for TicketNewError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
TicketNewError::TitleError(msg) => write!(f, "{}", msg),
|
||||||
|
TicketNewError::DescriptionError(msg) => write!(f, "{}", msg),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for TicketNewError {}
|
||||||
|
|
||||||
// TODO: `easy_ticket` should panic when the title is invalid, using the error message
|
// TODO: `easy_ticket` should panic when the title is invalid, using the error message
|
||||||
// stored inside the relevant variant of the `TicketNewError` enum.
|
// stored inside the relevant variant of the `TicketNewError` enum.
|
||||||
// When the description is invalid, instead, it should use a default description:
|
// When the description is invalid, instead, it should use a default description:
|
||||||
// "Description not provided".
|
// "Description not provided".
|
||||||
fn easy_ticket(title: String, description: String, status: Status) -> Ticket {
|
fn easy_ticket(title: String, description: String, status: Status) -> Ticket {
|
||||||
todo!()
|
match Ticket::new(title.clone(), description, status.clone()) {
|
||||||
|
Ok(ticket) => ticket,
|
||||||
|
Err(err) => match err {
|
||||||
|
TicketNewError::TitleError(_) => panic!("{err}"),
|
||||||
|
TicketNewError::DescriptionError(_) => {
|
||||||
|
Ticket::new(title, "Description not provided".to_string(), status).unwrap()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
|
|
3
exercises/05_ticket_v2/10_packages/src/lib.rs
Normal file
3
exercises/05_ticket_v2/10_packages/src/lib.rs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
pub fn hello_world() {
|
||||||
|
println!("Hello, world!");
|
||||||
|
}
|
|
@ -2,3 +2,6 @@
|
||||||
name = "deps"
|
name = "deps"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow = "1.0.82"
|
||||||
|
|
|
@ -4,6 +4,7 @@ version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
thiserror = "1"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
common = { path = "../../../helpers/common" }
|
common = { path = "../../../helpers/common" }
|
||||||
|
|
|
@ -3,10 +3,15 @@
|
||||||
// a `String` field into each variant.
|
// a `String` field into each variant.
|
||||||
// You'll also have to add `thiserror` as a dependency in the `Cargo.toml` file.
|
// You'll also have to add `thiserror` as a dependency in the `Cargo.toml` file.
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
enum TicketNewError {
|
enum TicketNewError {
|
||||||
|
#[error("Title cannot be empty")]
|
||||||
TitleCannotBeEmpty,
|
TitleCannotBeEmpty,
|
||||||
|
#[error("Title cannot be longer than 50 bytes")]
|
||||||
TitleTooLong,
|
TitleTooLong,
|
||||||
|
#[error("Description cannot be empty")]
|
||||||
DescriptionCannotBeEmpty,
|
DescriptionCannotBeEmpty,
|
||||||
|
#[error("Description cannot be longer than 500 bytes")]
|
||||||
DescriptionTooLong,
|
DescriptionTooLong,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,3 +4,4 @@ version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
thiserror = "1.0.59"
|
||||||
|
|
|
@ -8,6 +8,35 @@ enum Status {
|
||||||
Done,
|
Done,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
#[error("{invalid_status} is not a valid status")]
|
||||||
|
struct ParseStatusError {
|
||||||
|
invalid_status: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<String> for Status {
|
||||||
|
type Error = ParseStatusError;
|
||||||
|
|
||||||
|
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||||
|
value.as_str().try_into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&str> for Status {
|
||||||
|
type Error = ParseStatusError;
|
||||||
|
|
||||||
|
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||||
|
match value.to_lowercase().as_str() {
|
||||||
|
"todo" => Ok(Status::ToDo),
|
||||||
|
"inprogress" => Ok(Status::InProgress),
|
||||||
|
"done" => Ok(Status::Done),
|
||||||
|
_ => Err(ParseStatusError {
|
||||||
|
invalid_status: value.to_string(),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::status::Status;
|
use crate::status::{ParseStatusError, Status};
|
||||||
|
|
||||||
// We've seen how to declare modules in one of the earliest exercises, but
|
// We've seen how to declare modules in one of the earliest exercises, but
|
||||||
// we haven't seen how to extract them into separate files.
|
// we haven't seen how to extract them into separate files.
|
||||||
|
@ -23,6 +23,8 @@ pub enum TicketNewError {
|
||||||
DescriptionCannotBeEmpty,
|
DescriptionCannotBeEmpty,
|
||||||
#[error("Description cannot be longer than 500 bytes")]
|
#[error("Description cannot be longer than 500 bytes")]
|
||||||
DescriptionTooLong,
|
DescriptionTooLong,
|
||||||
|
#[error("{0}")]
|
||||||
|
InvalidStatus(#[from] ParseStatusError),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
|
@ -48,6 +50,7 @@ impl Ticket {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Parse the status string into a `Status` enum.
|
// TODO: Parse the status string into a `Status` enum.
|
||||||
|
let status = Status::try_from(status)?;
|
||||||
|
|
||||||
Ok(Ticket {
|
Ok(Ticket {
|
||||||
title,
|
title,
|
||||||
|
|
|
@ -2,3 +2,6 @@
|
||||||
name = "outro_04"
|
name = "outro_04"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
thiserror = "1.0.59"
|
||||||
|
|
|
@ -1,9 +1,45 @@
|
||||||
// TODO: Implement `TryFrom<String>` and `TryFrom<&str>` for the `TicketDescription` type,
|
// TODO: Implement `TryFrom<String>` and `TryFrom<&str>` for the `TicketDescription` type,
|
||||||
// enforcing that the description is not empty and is not longer than 500 bytes.
|
// enforcing that the description is not empty and is not longer than 500 bytes.
|
||||||
// Implement the traits required to make the tests pass too.
|
// Implement the traits required to make the tests pass too.
|
||||||
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
pub struct TicketDescription(String);
|
pub struct TicketDescription(String);
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub enum TicketDescriptionError {
|
||||||
|
#[error("The description cannot be empty")]
|
||||||
|
Empty,
|
||||||
|
#[error("The description cannot be longer than 500 bytes")]
|
||||||
|
TooLong,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<String> for TicketDescription {
|
||||||
|
type Error = TicketDescriptionError;
|
||||||
|
|
||||||
|
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||||
|
validate(&value)?;
|
||||||
|
Ok(Self(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&str> for TicketDescription {
|
||||||
|
type Error = TicketDescriptionError;
|
||||||
|
|
||||||
|
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||||
|
validate(value)?;
|
||||||
|
Ok(Self(value.to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate(description: &str) -> Result<(), TicketDescriptionError> {
|
||||||
|
if description.is_empty() {
|
||||||
|
Err(TicketDescriptionError::Empty)
|
||||||
|
} else if description.len() > 500 {
|
||||||
|
Err(TicketDescriptionError::TooLong)
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
@ -1,12 +1,43 @@
|
||||||
// TODO: Implement `TryFrom<String>` and `TryFrom<&str>` for the `Status` enum.
|
// TODO: Implement `TryFrom<String>` and `TryFrom<&str>` for the `Status` enum.
|
||||||
// The parsing should be case-insensitive.
|
// The parsing should be case-insensitive.
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
pub enum Status {
|
pub enum Status {
|
||||||
ToDo,
|
ToDo,
|
||||||
InProgress,
|
InProgress,
|
||||||
Done,
|
Done,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TryFrom<String> for Status {
|
||||||
|
type Error = ParseStatusError;
|
||||||
|
|
||||||
|
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||||
|
let value = value.to_lowercase();
|
||||||
|
match value.as_str() {
|
||||||
|
"todo" => Ok(Status::ToDo),
|
||||||
|
"inprogress" => Ok(Status::InProgress),
|
||||||
|
"done" => Ok(Status::Done),
|
||||||
|
_ => Err(ParseStatusError {
|
||||||
|
invalid_status: value,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&str> for Status {
|
||||||
|
type Error = ParseStatusError;
|
||||||
|
|
||||||
|
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||||
|
value.to_lowercase().try_into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
#[error("`{invalid_status}` is not a valid status. Use one of: ToDo, InProgress, Done")]
|
||||||
|
pub struct ParseStatusError {
|
||||||
|
invalid_status: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
@ -2,8 +2,45 @@
|
||||||
// enforcing that the title is not empty and is not longer than 50 bytes.
|
// enforcing that the title is not empty and is not longer than 50 bytes.
|
||||||
// Implement the traits required to make the tests pass too.
|
// Implement the traits required to make the tests pass too.
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
pub struct TicketTitle(String);
|
pub struct TicketTitle(String);
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub enum TicketTitleError {
|
||||||
|
#[error("The title cannot be empty")]
|
||||||
|
Empty,
|
||||||
|
#[error("The title cannot be longer than 50 bytes")]
|
||||||
|
TooLong,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<String> for TicketTitle {
|
||||||
|
type Error = TicketTitleError;
|
||||||
|
|
||||||
|
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||||
|
validate(&value)?;
|
||||||
|
Ok(Self(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&str> for TicketTitle {
|
||||||
|
type Error = TicketTitleError;
|
||||||
|
|
||||||
|
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||||
|
validate(value)?;
|
||||||
|
Ok(Self(value.to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate(title: &str) -> Result<(), TicketTitleError> {
|
||||||
|
if title.is_empty() {
|
||||||
|
Err(TicketTitleError::Empty)
|
||||||
|
} else if title.len() > 50 {
|
||||||
|
Err(TicketTitleError::TooLong)
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
fn intro() -> &'static str {
|
fn intro() -> &'static str {
|
||||||
// TODO: fix me 👇
|
// TODO: fix me 👇
|
||||||
"I'm ready to __!"
|
"I'm ready to build a ticket management system!"
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// TODO: Flesh out the `WeekTemperatures` struct and its method implementations to pass the tests.
|
// TODO: Flesh out the `WeekTemperatures` struct and its method implementations to pass the tests.
|
||||||
|
|
||||||
pub struct WeekTemperatures {
|
pub struct WeekTemperatures {
|
||||||
// TODO
|
temperatures: [Option<i32>; 7],
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum Weekday {
|
pub enum Weekday {
|
||||||
|
@ -16,15 +16,31 @@ pub enum Weekday {
|
||||||
|
|
||||||
impl WeekTemperatures {
|
impl WeekTemperatures {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
todo!()
|
WeekTemperatures {
|
||||||
|
temperatures: [None; 7],
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_temperature(&self, day: Weekday) -> Option<i32> {
|
pub fn get_temperature(&self, day: Weekday) -> Option<i32> {
|
||||||
todo!()
|
let index = weekday2index(&day);
|
||||||
|
self.temperatures[index]
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_temperature(&mut self, day: Weekday, temperature: i32) {
|
pub fn set_temperature(&mut self, day: Weekday, temperature: i32) {
|
||||||
todo!()
|
let index = weekday2index(&day);
|
||||||
|
self.temperatures[index] = Some(temperature);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn weekday2index(day: &Weekday) -> usize {
|
||||||
|
match day {
|
||||||
|
Weekday::Monday => 0,
|
||||||
|
Weekday::Tuesday => 1,
|
||||||
|
Weekday::Wednesday => 2,
|
||||||
|
Weekday::Thursday => 3,
|
||||||
|
Weekday::Friday => 4,
|
||||||
|
Weekday::Saturday => 5,
|
||||||
|
Weekday::Sunday => 6,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,11 +11,12 @@
|
||||||
// We expect `fibonacci(0)` to return `0`, `fibonacci(1)` to return `1`,
|
// We expect `fibonacci(0)` to return `0`, `fibonacci(1)` to return `1`,
|
||||||
// `fibonacci(2)` to return `1`, and so on.
|
// `fibonacci(2)` to return `1`, and so on.
|
||||||
pub fn fibonacci(n: u32) -> u32 {
|
pub fn fibonacci(n: u32) -> u32 {
|
||||||
// TODO: implement the `fibonacci` function
|
let n = n as usize;
|
||||||
//
|
let mut memo = vec![0, 1];
|
||||||
// Hint: use a `Vec` to memoize the results you have already calculated
|
for i in 2..=n {
|
||||||
// so that you don't have to recalculate them several times.
|
memo.push(memo[i - 1] + memo[i - 2]);
|
||||||
todo!()
|
}
|
||||||
|
memo[n]
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -12,6 +12,6 @@ mod tests {
|
||||||
// Can you guess what the new capacity will be?
|
// Can you guess what the new capacity will be?
|
||||||
// Beware that the standard library makes no guarantees about the
|
// Beware that the standard library makes no guarantees about the
|
||||||
// algorithm used to resize the vector, so this may change in the future.
|
// algorithm used to resize the vector, so this may change in the future.
|
||||||
assert_eq!(v.capacity(), todo!());
|
assert_eq!(v.capacity(), 4);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,6 +39,15 @@ impl TicketStore {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl IntoIterator for TicketStore {
|
||||||
|
type Item = Ticket;
|
||||||
|
type IntoIter = std::vec::IntoIter<Self::Item>;
|
||||||
|
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
self.tickets.into_iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
@ -34,6 +34,10 @@ impl TicketStore {
|
||||||
pub fn add_ticket(&mut self, ticket: Ticket) {
|
pub fn add_ticket(&mut self, ticket: Ticket) {
|
||||||
self.tickets.push(ticket);
|
self.tickets.push(ticket);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn iter(&self) -> std::slice::Iter<Ticket> {
|
||||||
|
self.tickets.iter()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -36,6 +36,15 @@ impl TicketStore {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a> IntoIterator for &'a TicketStore {
|
||||||
|
type Item = &'a Ticket;
|
||||||
|
type IntoIter = std::slice::Iter<'a, Ticket>;
|
||||||
|
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
self.tickets.iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
@ -31,6 +31,13 @@ impl TicketStore {
|
||||||
pub fn add_ticket(&mut self, ticket: Ticket) {
|
pub fn add_ticket(&mut self, ticket: Ticket) {
|
||||||
self.tickets.push(ticket);
|
self.tickets.push(ticket);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn to_dos(&self) -> Vec<&Ticket> {
|
||||||
|
self.tickets
|
||||||
|
.iter()
|
||||||
|
.filter(|ticket| ticket.status == Status::ToDo)
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -31,6 +31,12 @@ impl TicketStore {
|
||||||
pub fn add_ticket(&mut self, ticket: Ticket) {
|
pub fn add_ticket(&mut self, ticket: Ticket) {
|
||||||
self.tickets.push(ticket);
|
self.tickets.push(ticket);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn in_progress(&self) -> impl Iterator<Item = &Ticket> {
|
||||||
|
self.tickets
|
||||||
|
.iter()
|
||||||
|
.filter(|ticket| ticket.status == Status::InProgress)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -33,7 +33,7 @@ impl TicketStore {
|
||||||
// that can be infallibly converted into a `Ticket`.
|
// that can be infallibly converted into a `Ticket`.
|
||||||
// This can make it nicer to use the method, as it removes the syntax noise of `.into()`
|
// This can make it nicer to use the method, as it removes the syntax noise of `.into()`
|
||||||
// from the calling site. It can worsen the quality of the compiler error messages, though.
|
// from the calling site. It can worsen the quality of the compiler error messages, though.
|
||||||
pub fn add_ticket(&mut self, ticket: impl Into<Ticket>) {
|
pub fn add_ticket<T: Into<Ticket>>(&mut self, ticket: T) {
|
||||||
self.tickets.push(ticket.into());
|
self.tickets.push(ticket.into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
// TODO: Define a function named `sum` that takes a reference to a slice of `u32` and returns the sum of all
|
// TODO: Define a function named `sum` that takes a reference to a slice of `u32` and returns the sum of all
|
||||||
// elements in the slice.
|
// elements in the slice.
|
||||||
|
|
||||||
|
pub fn sum(slice: &[u32]) -> u32 {
|
||||||
|
slice.iter().sum()
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
@ -1,6 +1,12 @@
|
||||||
// TODO: Define a function named `squared` that raises all `i32`s within a slice to the power of 2.
|
// TODO: Define a function named `squared` that raises all `i32`s within a slice to the power of 2.
|
||||||
// The slice should be modified in place.
|
// The slice should be modified in place.
|
||||||
|
|
||||||
|
pub fn squared(slice: &mut [i32]) {
|
||||||
|
for i in slice.iter_mut() {
|
||||||
|
*i *= *i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
@ -11,6 +11,7 @@ use ticket_fields::{TicketDescription, TicketTitle};
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct TicketStore {
|
pub struct TicketStore {
|
||||||
tickets: Vec<Ticket>,
|
tickets: Vec<Ticket>,
|
||||||
|
counter: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
|
@ -41,11 +42,25 @@ impl TicketStore {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
tickets: Vec::new(),
|
tickets: Vec::new(),
|
||||||
|
counter: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_ticket(&mut self, ticket: Ticket) {
|
pub fn add_ticket(&mut self, draft: TicketDraft) -> TicketId {
|
||||||
|
let id = self.counter;
|
||||||
|
self.counter += 1;
|
||||||
|
let ticket = Ticket {
|
||||||
|
id: TicketId(id),
|
||||||
|
title: draft.title,
|
||||||
|
description: draft.description,
|
||||||
|
status: Status::ToDo,
|
||||||
|
};
|
||||||
self.tickets.push(ticket);
|
self.tickets.push(ticket);
|
||||||
|
TicketId(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self, id: TicketId) -> Option<&Ticket> {
|
||||||
|
self.tickets.iter().find(|ticket| ticket.id == id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
// TODO: Implement `Index<&TicketId>` and `Index<TicketId>` for `TicketStore`.
|
// TODO: Implement `Index<&TicketId>` and `Index<TicketId>` for `TicketStore`.
|
||||||
|
|
||||||
|
use std::ops::Index;
|
||||||
use ticket_fields::{TicketDescription, TicketTitle};
|
use ticket_fields::{TicketDescription, TicketTitle};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -58,6 +59,22 @@ impl TicketStore {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Index<TicketId> for TicketStore {
|
||||||
|
type Output = Ticket;
|
||||||
|
|
||||||
|
fn index(&self, id: TicketId) -> &Self::Output {
|
||||||
|
self.tickets.iter().find(|&t| t.id == id).unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Index<&TicketId> for TicketStore {
|
||||||
|
type Output = Ticket;
|
||||||
|
|
||||||
|
fn index(&self, id: &TicketId) -> &Self::Output {
|
||||||
|
&self[*id]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::{Status, TicketDraft, TicketStore};
|
use crate::{Status, TicketDraft, TicketStore};
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// TODO: Implement `IndexMut<&TicketId>` and `IndexMut<TicketId>` for `TicketStore`.
|
// TODO: Implement `IndexMut<&TicketId>` and `IndexMut<TicketId>` for `TicketStore`.
|
||||||
|
|
||||||
use std::ops::Index;
|
use std::ops::{Index, IndexMut};
|
||||||
use ticket_fields::{TicketDescription, TicketTitle};
|
use ticket_fields::{TicketDescription, TicketTitle};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -75,6 +75,18 @@ impl Index<&TicketId> for TicketStore {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl IndexMut<TicketId> for TicketStore {
|
||||||
|
fn index_mut(&mut self, index: TicketId) -> &mut Self::Output {
|
||||||
|
self.tickets.iter_mut().find(|t| t.id == index).unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IndexMut<&TicketId> for TicketStore {
|
||||||
|
fn index_mut(&mut self, index: &TicketId) -> &mut Self::Output {
|
||||||
|
&mut self[*index]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::{Status, TicketDraft, TicketStore};
|
use crate::{Status, TicketDraft, TicketStore};
|
||||||
|
|
|
@ -11,7 +11,7 @@ pub struct TicketStore {
|
||||||
counter: u64,
|
counter: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||||
pub struct TicketId(u64);
|
pub struct TicketId(u64);
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
@ -38,7 +38,7 @@ pub enum Status {
|
||||||
impl TicketStore {
|
impl TicketStore {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
tickets: todo!(),
|
tickets: HashMap::new(),
|
||||||
counter: 0,
|
counter: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -52,16 +52,16 @@ impl TicketStore {
|
||||||
description: ticket.description,
|
description: ticket.description,
|
||||||
status: Status::ToDo,
|
status: Status::ToDo,
|
||||||
};
|
};
|
||||||
todo!();
|
self.tickets.insert(id, ticket);
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get(&self, id: TicketId) -> Option<&Ticket> {
|
pub fn get(&self, id: TicketId) -> Option<&Ticket> {
|
||||||
todo!()
|
self.tickets.get(&id)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_mut(&mut self, id: TicketId) -> Option<&mut Ticket> {
|
pub fn get_mut(&mut self, id: TicketId) -> Option<&mut Ticket> {
|
||||||
todo!()
|
self.tickets.get_mut(&id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ pub struct TicketStore {
|
||||||
counter: u64,
|
counter: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Ord, PartialOrd, Eq)]
|
||||||
pub struct TicketId(u64);
|
pub struct TicketId(u64);
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
@ -40,7 +40,7 @@ pub enum Status {
|
||||||
impl TicketStore {
|
impl TicketStore {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
tickets: todo!(),
|
tickets: BTreeMap::new(),
|
||||||
counter: 0,
|
counter: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -54,16 +54,16 @@ impl TicketStore {
|
||||||
description: ticket.description,
|
description: ticket.description,
|
||||||
status: Status::ToDo,
|
status: Status::ToDo,
|
||||||
};
|
};
|
||||||
todo!();
|
self.tickets.insert(id, ticket);
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get(&self, id: TicketId) -> Option<&Ticket> {
|
pub fn get(&self, id: TicketId) -> Option<&Ticket> {
|
||||||
todo!()
|
self.tickets.get(&id)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_mut(&mut self, id: TicketId) -> Option<&mut Ticket> {
|
pub fn get_mut(&mut self, id: TicketId) -> Option<&mut Ticket> {
|
||||||
todo!()
|
self.tickets.get_mut(&id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,6 +95,15 @@ impl IndexMut<&TicketId> for TicketStore {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a> IntoIterator for &'a TicketStore {
|
||||||
|
type Item = &'a Ticket;
|
||||||
|
type IntoIter = std::collections::btree_map::Values<'a, TicketId, Ticket>;
|
||||||
|
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
self.tickets.values()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::{Status, TicketDraft, TicketId, TicketStore};
|
use crate::{Status, TicketDraft, TicketId, TicketStore};
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
fn intro() -> &'static str {
|
fn intro() -> &'static str {
|
||||||
// TODO: fix me 👇
|
// TODO: fix me 👇
|
||||||
"I'm ready to _!"
|
"I'm ready to build a concurrent ticket management system!"
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -15,7 +15,15 @@
|
||||||
use std::thread;
|
use std::thread;
|
||||||
|
|
||||||
pub fn sum(v: Vec<i32>) -> i32 {
|
pub fn sum(v: Vec<i32>) -> i32 {
|
||||||
todo!()
|
let mid = v.len() / 2;
|
||||||
|
let (v1, v2) = v.split_at(mid);
|
||||||
|
let v1 = v1.to_vec();
|
||||||
|
let v2 = v2.to_vec();
|
||||||
|
|
||||||
|
let handle1 = thread::spawn(move || v1.into_iter().sum::<i32>());
|
||||||
|
let handle2 = thread::spawn(move || v2.into_iter().sum::<i32>());
|
||||||
|
|
||||||
|
handle1.join().unwrap() + handle2.join().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -4,7 +4,13 @@
|
||||||
use std::thread;
|
use std::thread;
|
||||||
|
|
||||||
pub fn sum(slice: &'static [i32]) -> i32 {
|
pub fn sum(slice: &'static [i32]) -> i32 {
|
||||||
todo!()
|
let mid = slice.len() / 2;
|
||||||
|
let (slice1, slice2) = slice.split_at(mid);
|
||||||
|
|
||||||
|
let handle1 = thread::spawn(move || slice1.iter().sum::<i32>());
|
||||||
|
let handle2 = thread::spawn(move || slice2.iter().sum::<i32>());
|
||||||
|
|
||||||
|
handle1.join().unwrap() + handle2.join().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -6,7 +6,14 @@
|
||||||
use std::thread;
|
use std::thread;
|
||||||
|
|
||||||
pub fn sum(v: Vec<i32>) -> i32 {
|
pub fn sum(v: Vec<i32>) -> i32 {
|
||||||
todo!()
|
let v = v.leak();
|
||||||
|
let mid = v.len() / 2;
|
||||||
|
let (v1, v2) = v.split_at(mid);
|
||||||
|
|
||||||
|
let handle1 = thread::spawn(move || v1.into_iter().sum::<i32>());
|
||||||
|
let handle2 = thread::spawn(move || v2.into_iter().sum::<i32>());
|
||||||
|
|
||||||
|
handle1.join().unwrap() + handle2.join().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -3,7 +3,14 @@
|
||||||
// Don't perform any heap allocation. Don't leak any memory.
|
// Don't perform any heap allocation. Don't leak any memory.
|
||||||
|
|
||||||
pub fn sum(v: Vec<i32>) -> i32 {
|
pub fn sum(v: Vec<i32>) -> i32 {
|
||||||
todo!()
|
let mid = v.len() / 2;
|
||||||
|
let (left, right) = v.split_at(mid);
|
||||||
|
|
||||||
|
std::thread::scope(|s| {
|
||||||
|
let left = s.spawn(|| left.iter().sum::<i32>());
|
||||||
|
let right = s.spawn(|| right.iter().sum::<i32>());
|
||||||
|
left.join().unwrap() + right.join().unwrap()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
|
use crate::data::TicketDraft;
|
||||||
|
use crate::store::TicketStore;
|
||||||
use std::sync::mpsc::{Receiver, Sender};
|
use std::sync::mpsc::{Receiver, Sender};
|
||||||
|
|
||||||
pub mod data;
|
pub mod data;
|
||||||
pub mod store;
|
pub mod store;
|
||||||
|
|
||||||
pub enum Command {
|
pub enum Command {
|
||||||
Insert(todo!()),
|
Insert(TicketDraft),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start the system by spawning the server thread.
|
// Start the system by spawning the server thread.
|
||||||
|
@ -20,4 +22,13 @@ pub fn launch() -> Sender<Command> {
|
||||||
// Enter a loop: wait for a command to show up in
|
// Enter a loop: wait for a command to show up in
|
||||||
// the channel, then execute it, then start waiting
|
// the channel, then execute it, then start waiting
|
||||||
// for the next command.
|
// for the next command.
|
||||||
pub fn server(receiver: Receiver<Command>) {}
|
pub fn server(receiver: Receiver<Command>) {
|
||||||
|
let mut store = TicketStore::new();
|
||||||
|
while let Ok(command) = receiver.recv() {
|
||||||
|
match command {
|
||||||
|
Command::Insert(ticket_draft) => {
|
||||||
|
store.add_ticket(ticket_draft);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -26,7 +26,7 @@ fn ready() {
|
||||||
// since our server doesn't expose any **read** actions.
|
// since our server doesn't expose any **read** actions.
|
||||||
// We have no way to know if the inserts are actually happening and if they
|
// We have no way to know if the inserts are actually happening and if they
|
||||||
// are happening correctly.
|
// are happening correctly.
|
||||||
let move_forward = false;
|
let move_forward = true;
|
||||||
|
|
||||||
assert!(move_forward);
|
assert!(move_forward);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,18 +6,18 @@ use std::rc::Rc;
|
||||||
|
|
||||||
pub struct DropTracker<T> {
|
pub struct DropTracker<T> {
|
||||||
value: T,
|
value: T,
|
||||||
counter: todo!(),
|
counter: Rc<RefCell<usize>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> DropTracker<T> {
|
impl<T> DropTracker<T> {
|
||||||
pub fn new(value: T, counter: todo!()) -> Self {
|
pub fn new(value: T, counter: Rc<RefCell<usize>>) -> Self {
|
||||||
Self { value, counter }
|
Self { value, counter }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Drop for DropTracker<T> {
|
impl<T> Drop for DropTracker<T> {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
todo!()
|
*self.counter.borrow_mut() += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,20 @@
|
||||||
|
use crate::data::{Ticket, TicketDraft};
|
||||||
|
use crate::store::{TicketId, TicketStore};
|
||||||
use std::sync::mpsc::{Receiver, Sender};
|
use std::sync::mpsc::{Receiver, Sender};
|
||||||
use crate::store::TicketStore;
|
|
||||||
|
|
||||||
pub mod data;
|
pub mod data;
|
||||||
pub mod store;
|
pub mod store;
|
||||||
|
|
||||||
// Refer to the tests to understand the expected schema.
|
// Refer to the tests to understand the expected schema.
|
||||||
pub enum Command {
|
pub enum Command {
|
||||||
Insert { todo!() },
|
Insert {
|
||||||
Get { todo!() }
|
draft: TicketDraft,
|
||||||
|
response_sender: Sender<TicketId>,
|
||||||
|
},
|
||||||
|
Get {
|
||||||
|
id: TicketId,
|
||||||
|
response_sender: Sender<Option<Ticket>>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn launch() -> Sender<Command> {
|
pub fn launch() -> Sender<Command> {
|
||||||
|
@ -21,19 +28,25 @@ pub fn server(receiver: Receiver<Command>) {
|
||||||
let mut store = TicketStore::new();
|
let mut store = TicketStore::new();
|
||||||
loop {
|
loop {
|
||||||
match receiver.recv() {
|
match receiver.recv() {
|
||||||
Ok(Command::Insert {}) => {
|
Ok(Command::Insert {
|
||||||
todo!()
|
draft,
|
||||||
|
response_sender,
|
||||||
|
}) => {
|
||||||
|
let id = store.add_ticket(draft);
|
||||||
|
let _ = response_sender.send(id);
|
||||||
}
|
}
|
||||||
Ok(Command::Get {
|
Ok(Command::Get {
|
||||||
todo!()
|
id,
|
||||||
|
response_sender,
|
||||||
}) => {
|
}) => {
|
||||||
todo!()
|
let ticket = store.get(id);
|
||||||
|
let _ = response_sender.send(ticket.cloned());
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
// There are no more senders, so we can safely break
|
// There are no more senders, so we can safely break
|
||||||
// and shut down the server.
|
// and shut down the server.
|
||||||
break
|
break;
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,23 +7,39 @@ pub mod store;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
// TODO: flesh out the client implementation.
|
// TODO: flesh out the client implementation.
|
||||||
pub struct TicketStoreClient {}
|
pub struct TicketStoreClient {
|
||||||
|
sender: Sender<Command>,
|
||||||
|
}
|
||||||
|
|
||||||
impl TicketStoreClient {
|
impl TicketStoreClient {
|
||||||
// Feel free to panic on all errors, for simplicity.
|
// Feel free to panic on all errors, for simplicity.
|
||||||
pub fn insert(&self, draft: TicketDraft) -> TicketId {
|
pub fn insert(&self, draft: TicketDraft) -> TicketId {
|
||||||
todo!()
|
let (response_sender, response_receiver) = std::sync::mpsc::channel();
|
||||||
|
self.sender
|
||||||
|
.send(Command::Insert {
|
||||||
|
draft,
|
||||||
|
response_channel: response_sender,
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
response_receiver.recv().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get(&self, id: TicketId) -> Option<Ticket> {
|
pub fn get(&self, id: TicketId) -> Option<Ticket> {
|
||||||
todo!()
|
let (response_sender, response_receiver) = std::sync::mpsc::channel();
|
||||||
|
self.sender
|
||||||
|
.send(Command::Get {
|
||||||
|
id,
|
||||||
|
response_channel: response_sender,
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
response_receiver.recv().unwrap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn launch() -> TicketStoreClient {
|
pub fn launch() -> TicketStoreClient {
|
||||||
let (sender, receiver) = std::sync::mpsc::channel();
|
let (sender, receiver) = std::sync::mpsc::channel();
|
||||||
std::thread::spawn(move || server(receiver));
|
std::thread::spawn(move || server(receiver));
|
||||||
todo!()
|
TicketStoreClient { sender }
|
||||||
}
|
}
|
||||||
|
|
||||||
// No longer public! This becomes an internal detail of the library now.
|
// No longer public! This becomes an internal detail of the library now.
|
||||||
|
|
|
@ -4,4 +4,5 @@ version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
thiserror = "1.0.60"
|
||||||
ticket_fields = { path = "../../../helpers/ticket_fields" }
|
ticket_fields = { path = "../../../helpers/ticket_fields" }
|
||||||
|
|
|
@ -1,44 +1,62 @@
|
||||||
// TODO: Convert the implementation to use bounded channels.
|
// TODO: Convert the implementation to use bounded channels.
|
||||||
use crate::data::{Ticket, TicketDraft};
|
use crate::data::{Ticket, TicketDraft};
|
||||||
use crate::store::{TicketId, TicketStore};
|
use crate::store::{TicketId, TicketStore};
|
||||||
use std::sync::mpsc::{Receiver, Sender};
|
use std::sync::mpsc::{Receiver, Sender, SyncSender, TrySendError};
|
||||||
|
|
||||||
pub mod data;
|
pub mod data;
|
||||||
pub mod store;
|
pub mod store;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct TicketStoreClient {
|
pub struct TicketStoreClient {
|
||||||
sender: todo!(),
|
sender: SyncSender<Command>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TicketStoreClient {
|
impl TicketStoreClient {
|
||||||
pub fn insert(&self, draft: TicketDraft) -> Result<TicketId, todo!()> {
|
pub fn insert(&self, draft: TicketDraft) -> Result<TicketId, OverloadedError> {
|
||||||
todo!()
|
let (response_sender, response_receiver) = std::sync::mpsc::sync_channel(1);
|
||||||
|
self.sender
|
||||||
|
.try_send(Command::Insert {
|
||||||
|
draft,
|
||||||
|
response_channel: response_sender,
|
||||||
|
})
|
||||||
|
.map_err(|_| OverloadedError)?;
|
||||||
|
Ok(response_receiver.recv().unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get(&self, id: TicketId) -> Result<Option<Ticket>, todo!()> {
|
pub fn get(&self, id: TicketId) -> Result<Option<Ticket>, OverloadedError> {
|
||||||
todo!()
|
let (response_sender, response_receiver) = std::sync::mpsc::sync_channel(1);
|
||||||
|
self.sender
|
||||||
|
.try_send(Command::Get {
|
||||||
|
id,
|
||||||
|
response_channel: response_sender,
|
||||||
|
})
|
||||||
|
.map_err(|_| OverloadedError)?;
|
||||||
|
Ok(response_receiver.recv().unwrap())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
#[error("The store is overloaded")]
|
||||||
|
pub struct OverloadedError;
|
||||||
|
|
||||||
pub fn launch(capacity: usize) -> TicketStoreClient {
|
pub fn launch(capacity: usize) -> TicketStoreClient {
|
||||||
todo!();
|
let (sender, receiver) = std::sync::mpsc::sync_channel(capacity);
|
||||||
std::thread::spawn(move || server(receiver));
|
std::thread::spawn(move || server(receiver));
|
||||||
todo!()
|
TicketStoreClient { sender }
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Command {
|
enum Command {
|
||||||
Insert {
|
Insert {
|
||||||
draft: TicketDraft,
|
draft: TicketDraft,
|
||||||
response_channel: todo!(),
|
response_channel: SyncSender<TicketId>,
|
||||||
},
|
},
|
||||||
Get {
|
Get {
|
||||||
id: TicketId,
|
id: TicketId,
|
||||||
response_channel: todo!(),
|
response_channel: SyncSender<Option<Ticket>>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn server(receiver: Receiver<Command>) {
|
fn server(receiver: Receiver<Command>) {
|
||||||
let mut store = TicketStore::new();
|
let mut store = TicketStore::new();
|
||||||
loop {
|
loop {
|
||||||
match receiver.recv() {
|
match receiver.recv() {
|
||||||
|
@ -47,14 +65,14 @@ pub fn server(receiver: Receiver<Command>) {
|
||||||
response_channel,
|
response_channel,
|
||||||
}) => {
|
}) => {
|
||||||
let id = store.add_ticket(draft);
|
let id = store.add_ticket(draft);
|
||||||
todo!()
|
let _ = response_channel.send(id);
|
||||||
}
|
}
|
||||||
Ok(Command::Get {
|
Ok(Command::Get {
|
||||||
id,
|
id,
|
||||||
response_channel,
|
response_channel,
|
||||||
}) => {
|
}) => {
|
||||||
let ticket = store.get(id);
|
let ticket = store.get(id);
|
||||||
todo!()
|
let _ = response_channel.send(ticket.cloned());
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
// There are no more senders, so we can safely break
|
// There are no more senders, so we can safely break
|
||||||
|
|
|
@ -35,7 +35,16 @@ impl TicketStoreClient {
|
||||||
Ok(response_receiver.recv().unwrap())
|
Ok(response_receiver.recv().unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(&self, ticket_patch: TicketPatch) -> Result<(), OverloadedError> {}
|
pub fn update(&self, ticket_patch: TicketPatch) -> Result<(), OverloadedError> {
|
||||||
|
let (response_sender, response_receiver) = sync_channel(1);
|
||||||
|
self.sender
|
||||||
|
.try_send(Command::Update {
|
||||||
|
patch: ticket_patch,
|
||||||
|
response_channel: response_sender,
|
||||||
|
})
|
||||||
|
.map_err(|_| OverloadedError)?;
|
||||||
|
Ok(response_receiver.recv().unwrap())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
@ -85,7 +94,18 @@ pub fn server(receiver: Receiver<Command>) {
|
||||||
patch,
|
patch,
|
||||||
response_channel,
|
response_channel,
|
||||||
}) => {
|
}) => {
|
||||||
todo!()
|
if let Some(ticket) = store.get_mut(patch.id) {
|
||||||
|
if let Some(title) = patch.title {
|
||||||
|
ticket.title = title;
|
||||||
|
}
|
||||||
|
if let Some(description) = patch.description {
|
||||||
|
ticket.description = description;
|
||||||
|
}
|
||||||
|
if let Some(status) = patch.status {
|
||||||
|
ticket.status = status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let _ = response_channel.send(());
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
// There are no more senders, so we can safely break
|
// There are no more senders, so we can safely break
|
||||||
|
|
|
@ -28,13 +28,13 @@ impl TicketStore {
|
||||||
description: ticket.description,
|
description: ticket.description,
|
||||||
status: Status::ToDo,
|
status: Status::ToDo,
|
||||||
};
|
};
|
||||||
todo!();
|
self.tickets.insert(id, Arc::new(Mutex::new(ticket)));
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
|
|
||||||
// The `get` method should return a handle to the ticket
|
// The `get` method should return a handle to the ticket
|
||||||
// which allows the caller to either read or modify the ticket.
|
// which allows the caller to either read or modify the ticket.
|
||||||
pub fn get(&self, id: TicketId) -> Option<todo!()> {
|
pub fn get(&self, id: TicketId) -> Option<Arc<Mutex<Ticket>>> {
|
||||||
todo!()
|
self.tickets.get(&id).cloned()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
|
use std::sync::{Arc, Mutex, RwLock};
|
||||||
// TODO: Replace `Mutex` with `RwLock` in the `TicketStore` struct and
|
// TODO: Replace `Mutex` with `RwLock` in the `TicketStore` struct and
|
||||||
// all other relevant places to allow multiple readers to access the ticket store concurrently.
|
// all other relevant places to allow multiple readers to access the ticket store concurrently.
|
||||||
use std::sync::mpsc::{sync_channel, Receiver, SyncSender, TrySendError};
|
use std::sync::mpsc::{sync_channel, Receiver, SyncSender};
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
|
|
||||||
use crate::data::{Ticket, TicketDraft};
|
use crate::data::{Ticket, TicketDraft};
|
||||||
use crate::store::{TicketId, TicketStore};
|
use crate::store::{TicketId, TicketStore};
|
||||||
|
@ -26,7 +26,7 @@ impl TicketStoreClient {
|
||||||
Ok(response_receiver.recv().unwrap())
|
Ok(response_receiver.recv().unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get(&self, id: TicketId) -> Result<Option<Arc<Mutex<Ticket>>>, OverloadedError> {
|
pub fn get(&self, id: TicketId) -> Result<Option<Arc<RwLock<Ticket>>>, OverloadedError> {
|
||||||
let (response_sender, response_receiver) = sync_channel(1);
|
let (response_sender, response_receiver) = sync_channel(1);
|
||||||
self.sender
|
self.sender
|
||||||
.try_send(Command::Get {
|
.try_send(Command::Get {
|
||||||
|
@ -55,7 +55,7 @@ enum Command {
|
||||||
},
|
},
|
||||||
Get {
|
Get {
|
||||||
id: TicketId,
|
id: TicketId,
|
||||||
response_channel: SyncSender<Option<Arc<Mutex<Ticket>>>>,
|
response_channel: SyncSender<Option<Arc<RwLock<Ticket>>>>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
use crate::data::{Status, Ticket, TicketDraft};
|
use crate::data::{Status, Ticket, TicketDraft};
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, RwLock};
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub struct TicketId(u64);
|
pub struct TicketId(u64);
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct TicketStore {
|
pub struct TicketStore {
|
||||||
tickets: BTreeMap<TicketId, Arc<Mutex<Ticket>>>,
|
tickets: BTreeMap<TicketId, Arc<RwLock<Ticket>>>,
|
||||||
counter: u64,
|
counter: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,14 +28,14 @@ impl TicketStore {
|
||||||
description: ticket.description,
|
description: ticket.description,
|
||||||
status: Status::ToDo,
|
status: Status::ToDo,
|
||||||
};
|
};
|
||||||
let ticket = Arc::new(Mutex::new(ticket));
|
let ticket = Arc::new(RwLock::new(ticket));
|
||||||
self.tickets.insert(id, ticket);
|
self.tickets.insert(id, ticket);
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
|
|
||||||
// The `get` method should return a handle to the ticket
|
// The `get` method should return a handle to the ticket
|
||||||
// which allows the caller to either read or modify the ticket.
|
// which allows the caller to either read or modify the ticket.
|
||||||
pub fn get(&self, id: TicketId) -> Option<Arc<Mutex<Ticket>>> {
|
pub fn get(&self, id: TicketId) -> Option<Arc<RwLock<Ticket>>> {
|
||||||
self.tickets.get(&id).cloned()
|
self.tickets.get(&id).cloned()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ use without_channels::store::TicketStore;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn works() {
|
fn works() {
|
||||||
let store = todo!();
|
let store = Arc::new(RwLock::new(TicketStore::new()));
|
||||||
|
|
||||||
let store1 = store.clone();
|
let store1 = store.clone();
|
||||||
let client1 = spawn(move || {
|
let client1 = spawn(move || {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// Not much to be exercised on `Sync`, just a thing to remember.
|
// Not much to be exercised on `Sync`, just a thing to remember.
|
||||||
fn outro() -> &'static str {
|
fn outro() -> &'static str {
|
||||||
"I have a good understanding of __!"
|
"I have a good understanding of Send and Sync!"
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
fn intro() -> &'static str {
|
fn intro() -> &'static str {
|
||||||
// TODO: fix me 👇
|
// TODO: fix me 👇
|
||||||
"I'm ready to _!"
|
"I'm ready to learn about futures!"
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -11,7 +11,11 @@ use tokio::net::TcpListener;
|
||||||
// - `tokio::net::TcpStream::split` to obtain a reader and a writer from the socket
|
// - `tokio::net::TcpStream::split` to obtain a reader and a writer from the socket
|
||||||
// - `tokio::io::copy` to copy data from the reader to the writer
|
// - `tokio::io::copy` to copy data from the reader to the writer
|
||||||
pub async fn echo(listener: TcpListener) -> Result<(), anyhow::Error> {
|
pub async fn echo(listener: TcpListener) -> Result<(), anyhow::Error> {
|
||||||
todo!()
|
loop {
|
||||||
|
let (mut socket, _) = listener.accept().await?;
|
||||||
|
let (mut reader, mut writer) = socket.split();
|
||||||
|
tokio::io::copy(&mut reader, &mut writer).await?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue