This commit is contained in:
yangrui.0 2024-10-22 20:37:18 +08:00
parent 800cd6d542
commit 90a1d15565
37 changed files with 378 additions and 92 deletions

View File

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

View File

@ -5,6 +5,17 @@
// 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 {
use super::*;

View File

@ -18,7 +18,21 @@ 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 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")
}
if !["To-Do", "In Progress", "Done"].contains(&status.as_str()) {
panic!("Only `To-Do`, `In Progress`, and `Done` statuses are allowed")
}
Self {
title,
description,

View File

@ -2,6 +2,8 @@ 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 crate::Ticket;
fn create_todo_ticket(title: String, description: String) -> Ticket {
Ticket::new(title, description, "To-Do".into())
}

View File

@ -1,12 +1,12 @@
mod ticket {
struct Ticket {
title: String,
description: String,
status: String,
pub struct Ticket {
pub title: String,
pub description: String,
pub 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");
}

View File

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

View File

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

View File

@ -3,6 +3,7 @@
// Even better, extract that logic and reuse it in both places. You can use
// private functions or private static methods for that.
#[derive(Default)]
pub struct Ticket {
title: String,
description: String,
@ -11,27 +12,12 @@ 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");
}
let mut t = Self::default();
t.set_title(title);
t.set_description(description);
t.set_status(status);
Ticket {
title,
description,
status,
}
t
}
pub fn title(&self) -> &String {
@ -45,6 +31,32 @@ impl Ticket {
pub fn status(&self) -> &String {
&self.status
}
pub fn set_title(&mut self, title: String) {
if title.is_empty() {
panic!("Title cannot be empty");
}
if title.len() > 50 {
panic!("Title cannot be longer than 50 bytes");
}
self.title = title;
}
pub fn set_status(&mut self, status: String) {
if status != "To-Do" && status != "In Progress" && status != "Done" {
panic!("Only `To-Do`, `In Progress`, and `Done` statuses are allowed");
}
self.status = status;
}
pub fn set_description(&mut self, description: String) {
if description.is_empty() {
panic!("Description cannot be empty");
}
if description.len() > 500 {
panic!("Description cannot be longer than 500 bytes");
}
self.description = description;
}
}
#[cfg(test)]

View File

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

View File

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

View File

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

View File

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

View File

@ -11,3 +11,61 @@
// 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.
use core::panic;
#[derive(Default)]
pub struct Order {
product_name: String,
quantity: usize,
unit_price: usize,
}
impl Order {
pub fn new(product_name: String, quantity: usize, unit_price: usize) -> Self {
let mut s = Order::default();
s.set_product_name(product_name);
s.set_quantity(quantity);
s.set_unit_price(unit_price);
s
}
pub fn product_name(&self) -> &str {
&self.product_name
}
pub fn quantity(&self) -> &usize {
&self.quantity
}
pub fn unit_price(&self) -> &usize {
&self.unit_price
}
pub fn total(&self) -> usize {
self.quantity * self.unit_price
}
pub fn set_product_name(&mut self, name: String) {
if name.is_empty() {
panic!("product_name is empty")
}
if name.len() > 300 {
panic!("product_name is too long")
}
self.product_name = name;
}
pub fn set_quantity(&mut self, quantity: usize) {
if quantity == 0 {
panic!("quantity is zero")
}
self.quantity = quantity;
}
pub fn set_unit_price(&mut self, unit_price: usize) {
if unit_price == 0 {
panic!("unit_price is zero")
}
self.unit_price = unit_price;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -12,11 +12,11 @@ pub struct Ticket {
impl Ticket {
pub fn title(&self) -> &str {
todo!()
self.title.trim()
}
pub fn description(&self) -> &str {
todo!()
self.description.trim()
}
}

View File

@ -3,5 +3,5 @@ pub fn example() {
// 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.
std::mem::size_of::<str>();
// std::mem::size_of::<str>();
}

View File

@ -4,6 +4,12 @@ pub struct WrappingU32 {
value: u32,
}
impl From<u32> for WrappingU32 {
fn from(value: u32) -> Self {
Self { value }
}
}
fn example() {
let wrapping: WrappingU32 = 42.into();
let wrapping = WrappingU32::from(42);

View File

@ -13,6 +13,28 @@
// You don't have to though: it's perfectly okay to write three separate
// implementations manually. Venture further only if you're curious.
trait Power<T> {
fn power(&self, t: T) -> Self;
}
impl Power<u16> for u32 {
fn power(&self, t: u16) -> Self {
self.pow(t as u32)
}
}
impl Power<u32> for u32 {
fn power(&self, t: u32) -> Self {
self.pow(t)
}
}
impl Power<&u32> for u32 {
fn power(&self, t: &u32) -> Self {
self.pow(*t)
}
}
#[cfg(test)]
mod tests {
use super::Power;

View File

@ -2,9 +2,10 @@
// to get the code to compile.
pub fn summary(ticket: Ticket) -> (Ticket, Summary) {
(ticket, ticket.summary())
(ticket.clone(), ticket.summary())
}
#[derive(Clone)]
pub struct Ticket {
pub title: String,
pub description: String,

View File

@ -1,10 +1,21 @@
// TODO: implement the necessary traits to make the test compile and pass.
// You *can't* modify the test.
use std::ops::Add;
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct WrappingU32 {
value: u32,
}
impl Add for WrappingU32 {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
Self::new(self.value.wrapping_add(rhs.value))
}
}
impl WrappingU32 {
pub fn new(value: u32) -> Self {
Self { value }

View File

@ -2,6 +2,26 @@
// unless a certain operation has been performed on it.
// You can see the expected API in the tests below.
struct DropBomb(bool);
impl DropBomb {
pub fn new() -> Self {
Self(false)
}
pub fn defuse(&mut self) {
self.0 = true
}
}
impl Drop for DropBomb {
fn drop(&mut self) {
if !self.0 {
panic!("not defuse")
}
}
}
#[cfg(test)]
mod tests {
use super::*;

View File

@ -8,3 +8,67 @@
// 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.
use std::ops::Add;
#[derive(Debug, Copy, Clone)]
pub struct SaturatingU16(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 as 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 as u16)
}
}
impl Add<SaturatingU16> for SaturatingU16 {
type Output = u16;
fn add(self, rhs: SaturatingU16) -> Self::Output {
self.0.saturating_add(rhs.0)
}
}
impl Add<&SaturatingU16> for SaturatingU16 {
type Output = u16;
fn add(self, rhs: &SaturatingU16) -> Self::Output {
self.0.saturating_add(rhs.0)
}
}
impl Add<u16> for SaturatingU16 {
type Output = u16;
fn add(self, rhs: u16) -> Self::Output {
rhs.saturating_add(self.0)
}
}
impl PartialEq<u16> for SaturatingU16 {
fn eq(&self, other: &u16) -> bool {
self.0 == *other
}
}
impl PartialEq<SaturatingU16> for u16 {
fn eq(&self, other: &SaturatingU16) -> bool {
other.0 == *self
}
}

View File

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

View File

@ -7,15 +7,19 @@
struct Ticket {
title: String,
description: String,
status: String,
status: Status,
}
#[derive(Debug, PartialEq, Clone)]
enum Status {
// TODO: add the missing variants
ToDo,
InProgress,
Done,
}
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() {
panic!("Title cannot be empty");
}
@ -28,7 +32,7 @@ impl Ticket {
if description.len() > 500 {
panic!("Description cannot be longer than 500 bytes");
}
if status != "To-Do" && status != "In Progress" && status != "Done" {
if status != Status::ToDo && status != Status::InProgress && status != Status::Done {
panic!("Only `To-Do`, `In Progress`, and `Done` statuses are allowed");
}
@ -47,7 +51,7 @@ impl Ticket {
&self.description
}
pub fn status(&self) -> &String {
pub fn status(&self) -> &Status {
&self.status
}
}
@ -81,7 +85,7 @@ mod tests {
let ticket1 = Ticket {
title: title.clone(),
description: "description".to_string(),
status,
status: status.clone(),
};
let ticket2 = Ticket {
title: title.clone(),
@ -98,7 +102,7 @@ mod tests {
let ticket1 = Ticket {
title: "title".to_string(),
description: description.clone(),
status,
status: status.clone(),
};
let ticket2 = Ticket {
title: "title2".to_string(),

View File

@ -9,7 +9,13 @@ enum Shape {
impl Shape {
// TODO: Implement the `n_sides` method using a `match`.
pub fn n_sides(&self) -> u8 {
todo!()
match *self {
Shape::Circle => 0,
Shape::Square => 4,
Shape::Rectangle => 4,
Shape::Triangle => 3,
Shape::Pentagon => 5,
}
}
}

View File

@ -38,7 +38,11 @@ impl Ticket {
}
}
pub fn assigned_to(&self) -> &str {
todo!()
if let Status::InProgress { assigned_to } = &self.status {
assigned_to
} else {
panic!("Only `In-Progress` tickets can be assigned to someone")
}
}
}

View File

@ -8,7 +8,11 @@ impl Shape {
// TODO: Implement the `radius` method using
// either an `if let` or a `let/else`.
pub fn radius(&self) -> f64 {
todo!()
if let Shape::Circle { radius } = &self {
*radius
} else {
panic!("wrong");
}
}
}

View File

@ -36,7 +36,11 @@ impl Ticket {
}
}
pub fn assigned_to(&self) -> Option<&String> {
todo!()
if let Status::InProgress { assigned_to } = &self.status {
Some(assigned_to)
} else {
None
}
}
}

View File

@ -16,25 +16,25 @@ enum Status {
}
impl Ticket {
pub fn new(title: String, description: String, status: Status) -> Ticket {
pub fn new(title: String, description: String, status: Status) -> Result<Self, &'static str> {
if title.is_empty() {
panic!("Title cannot be empty");
Err("Title cannot be empty")?;
}
if title.len() > 50 {
panic!("Title cannot be longer than 50 bytes");
Err("Title cannot be longer than 50 bytes")?;
}
if description.is_empty() {
panic!("Description cannot be empty");
Err("Description cannot be empty")?;
}
if description.len() > 500 {
panic!("Description cannot be longer than 500 bytes");
Err("Description cannot be longer than 500 bytes")?;
}
Ticket {
Ok(Ticket {
title,
description,
status,
}
})
}
}

View File

@ -2,7 +2,7 @@
// When the description is invalid, instead, it should use a default description:
// "Description not provided".
fn easy_ticket(title: String, description: String, status: Status) -> Ticket {
todo!()
Ticket::new(title, description, status).unwrap()
}
#[derive(Debug, PartialEq, Clone)]
@ -22,17 +22,18 @@ enum Status {
impl Ticket {
pub fn new(title: String, description: String, status: Status) -> Result<Ticket, String> {
if title.is_empty() {
return Err("Title cannot be empty".to_string());
panic!("Title cannot be empty");
}
if title.len() > 50 {
return Err("Title cannot be longer than 50 bytes".to_string());
}
if description.is_empty() {
return Err("Description cannot be empty".to_string());
}
if description.len() > 500 {
return Err("Description cannot be longer than 500 bytes".to_string());
panic!("Title cannot be longer than 50 bytes");
}
let description = {
if description.is_empty() || description.len() > 500 {
"Description not provided".to_string()
} else {
description
}
};
Ok(Ticket {
title,

View File

@ -1,14 +1,23 @@
// 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.
// You'll have to update the implementation of `Ticket::new` as well.
enum TicketNewError {}
enum TicketNewError {
TitleError(&'static str),
DescriptionError(&'static str),
}
// TODO: `easy_ticket` should panic when the title is invalid, using the error message
// stored inside the relevant variant of the `TicketNewError` enum.
// When the description is invalid, instead, it should use a default description:
// "Description not provided".
fn easy_ticket(title: String, description: String, status: Status) -> Ticket {
todo!()
Ticket::new(title.clone(), description, status.clone()).unwrap_or_else(|t| match t {
TicketNewError::TitleError(e) => panic!("{}", e),
TicketNewError::DescriptionError(_) => {
Ticket::new(title, "Description not provided".to_string(), status)
.unwrap_or_else(|_| panic!(""))
}
})
}
#[derive(Debug, PartialEq)]
@ -32,16 +41,22 @@ impl Ticket {
status: Status,
) -> Result<Ticket, TicketNewError> {
if title.is_empty() {
return Err("Title cannot be empty".to_string());
Err(TicketNewError::TitleError("Title cannot be empty"))?;
}
if title.len() > 50 {
return Err("Title cannot be longer than 50 bytes".to_string());
Err(TicketNewError::TitleError(
"Title cannot be longer than 50 bytes",
))?;
}
if description.is_empty() {
return Err("Description cannot be empty".to_string());
Err(TicketNewError::DescriptionError(
"Description cannot be empty",
))?;
}
if description.len() > 500 {
return Err("Description cannot be longer than 500 bytes".to_string());
Err(TicketNewError::DescriptionError(
"Description cannot be longer than 500 bytes",
))?;
}
Ok(Ticket {