Compare commits
42 Commits
bd9faa049f
...
my_solutio
| Author | SHA1 | Date | |
|---|---|---|---|
| 7a1ca00837 | |||
| 1789d43730 | |||
| 604a5a20ae | |||
| 0829174983 | |||
| ebe07acd1e | |||
| 4a4fc2ea0d | |||
| 91e6bf5ae4 | |||
| 3f57819e4c | |||
| d54a1e160b | |||
| 4913da035f | |||
| 1f0823a6a4 | |||
| 56075fa40e | |||
| 852cd5f0b9 | |||
| 1c16ac8c22 | |||
| d3b4c0d653 | |||
| 8b78ea3a83 | |||
| 0e5cdc6d10 | |||
| 1d876eff10 | |||
| dfcb9736b2 | |||
| c1e7c3fadd | |||
| 978a99b00d | |||
| 34189d212e | |||
| 9d1869fc9c | |||
| 3e56cf1287 | |||
| f91cc0089a | |||
| 211f23cea4 | |||
| 1e516bf0a5 | |||
| 03a0a77394 | |||
| bb27bfad41 | |||
| 4d12facc2f | |||
| 1251a6c1b1 | |||
|
|
056611ac1c | ||
|
|
e99a15390e | ||
|
|
ffb2f08b67 | ||
|
|
baca47f782 | ||
|
|
d442b61795 | ||
|
|
d77272a5b4 | ||
|
|
deb4ccb643 | ||
|
|
35a2ff1efe | ||
|
|
5140cd3b37 | ||
|
|
3020ef6c3f | ||
|
|
f74fbd4800 |
@@ -1,3 +1,6 @@
|
||||
[workspace]
|
||||
members = ["exercises/*/*", "helpers/common", "helpers/mdbook-exercise-linker", "helpers/ticket_fields"]
|
||||
resolver = "2"
|
||||
|
||||
[profile.dev]
|
||||
overflow-checks = false
|
||||
|
||||
@@ -95,8 +95,8 @@ Ownership can be transferred.
|
||||
If you own a value, for example, you can transfer ownership to another variable:
|
||||
|
||||
```rust
|
||||
let a = 42; // <--- `a` is the owner of the value `42`
|
||||
let b = a; // <--- `b` is now the owner of the value `42`
|
||||
let a = "hello, world".to_string(); // <--- `a` is the owner of the String
|
||||
let b = a; // <--- `b` is now the owner of the String
|
||||
```
|
||||
|
||||
Rust's ownership system is baked into the type system: each function has to declare in its signature
|
||||
|
||||
@@ -58,7 +58,7 @@ You've already encountered a few macros in past exercises:
|
||||
Rust macros are **code generators**.\
|
||||
They generate new Rust code based on the input you provide, and that generated code is then compiled alongside
|
||||
the rest of your program. Some macros are built into Rust's standard library, but you can also
|
||||
write your own. We won't be creating our macro in this course, but you can find some useful
|
||||
write your own. We won't be creating our own macro in this course, but you can find some useful
|
||||
pointers in the ["Further reading" section](#further-reading).
|
||||
|
||||
### Inspection
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ let y = point.1;
|
||||
|
||||
### Tuples
|
||||
|
||||
It's weird say that something is tuple-like when we haven't seen tuples yet!\
|
||||
It's weird to say that something is tuple-like when we haven't seen tuples yet!\
|
||||
Tuples are another example of a primitive Rust type.
|
||||
They group together a fixed number of values with (potentially different) types:
|
||||
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
|
||||
## Error reporting
|
||||
|
||||
In the previous exercise you had to destructure the `InvalidTitle` variant to extract the error message and
|
||||
In the previous exercise you had to destructure the `TitleError` variant to extract the error message and
|
||||
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.
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ assert_eq!(numbers.get(0), Some(&1));
|
||||
assert_eq!(numbers.get(3), None);
|
||||
```
|
||||
|
||||
Access is bounds-checked, just element access with arrays. It has O(1) complexity.
|
||||
Access is bounds-checked, just like element access with arrays. It has O(1) complexity.
|
||||
|
||||
## Memory layout
|
||||
|
||||
|
||||
@@ -12,4 +12,4 @@ We'll have the opportunity to touch most of Rust's core concurrency features, in
|
||||
- Shared state, using `Arc`, `Mutex` and `RwLock`
|
||||
- `Send` and `Sync`, the traits that encode Rust's concurrency guarantees
|
||||
|
||||
We'll also discuss various design patterns for multithreaded systems and some their trade-offs.
|
||||
We'll also discuss various design patterns for multithreaded systems and some of their trade-offs.
|
||||
|
||||
@@ -74,7 +74,7 @@ struct TicketStore {
|
||||
```
|
||||
|
||||
This approach is more efficient, but it has a downside: `TicketStore` has to become **aware** of the multithreaded
|
||||
nature of the system; up until now, `TicketStore` has been blissfully ignored the existence of threads.\
|
||||
nature of the system; up until now, `TicketStore` has been blissfully ignoring the existence of threads.\
|
||||
Let's go for it anyway.
|
||||
|
||||
## Who holds the lock?
|
||||
|
||||
@@ -48,7 +48,7 @@ If we remove the channels, we need to introduce (another) lock to synchronize ac
|
||||
|
||||
If we use a `Mutex`, then it makes no sense to use an additional `RwLock` for each ticket: the `Mutex` will
|
||||
already serialize access to the entire store, so we wouldn't be able to read tickets in parallel anyway.\
|
||||
If we use a `RwLock`, instead, we can read tickets in parallel. We just to pause all reads while inserting
|
||||
If we use a `RwLock`, instead, we can read tickets in parallel. We just need to pause all reads while inserting
|
||||
or removing a ticket.
|
||||
|
||||
Let's go down this path and see where it leads us.
|
||||
|
||||
@@ -99,7 +99,7 @@ pub async fn run() {
|
||||
if let Ok(reason) = e.try_into_panic() {
|
||||
// The task has panicked
|
||||
// We resume unwinding the panic,
|
||||
// thus propagating it to the current thread
|
||||
// thus propagating it to the current task
|
||||
panic::resume_unwind(reason);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,8 +28,7 @@ project and on [exercism.io](https://exercism.io)'s Rust track.
|
||||
Check out [the Rust book](https://doc.rust-lang.org/book/title-page.html) and
|
||||
["Programming Rust"](https://www.oreilly.com/library/view/programming-rust-2nd/9781492052586/)
|
||||
if you're looking for a different perspective on the same concepts we covered throughout this course.
|
||||
The material doesn't overlap perfectly, therefore you'll certainly learn something new along the
|
||||
way.
|
||||
You'll certainly learn something new since they don't cover exactly the same topics; Rust has a lot of surface area!
|
||||
|
||||
### Advanced material
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/// Return `true` if `n` is even, `false` otherwise.
|
||||
fn is_even(n: u32) -> bool {
|
||||
todo!()
|
||||
n % 2 == 0
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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(),
|
||||
// };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
fn intro() -> &'static str {
|
||||
// TODO: fix me 👇
|
||||
"I'm ready to __!"
|
||||
"I'm ready to learn about traits!"
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -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::*;
|
||||
|
||||
@@ -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!()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
// You _could_ pass this test by just returning `v.iter().sum()`,
|
||||
// but that would defeat the purpose of the exercise.
|
||||
//
|
||||
// Hint: you won't be able to get the spawn threads to _borrow_
|
||||
// Hint: you won't be able to get the spawned threads to _borrow_
|
||||
// slices of the vector directly. You'll need to allocate new
|
||||
// vectors for each half of the original vector. We'll see why
|
||||
// this is necessary in the next exercise.
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// TODO: Set `move_forward` to `true` in `ready` when you think you're done with this exercise.
|
||||
// Feel free to call an instructor to verify your solution!
|
||||
use channels::data::TicketDraft;
|
||||
use channels::{launch, Command};
|
||||
use std::time::Duration;
|
||||
@@ -24,8 +26,6 @@ fn ready() {
|
||||
// since our server doesn't expose any **read** actions.
|
||||
// We have no way to know if the inserts are actually happening and if they
|
||||
// are happening correctly.
|
||||
// Set `move_forward` to `true` when you think you're done with this exercise.
|
||||
// Feel free to call an instructor to verify your solution!
|
||||
let move_forward = false;
|
||||
|
||||
assert!(move_forward);
|
||||
|
||||
Reference in New Issue
Block a user