9.14 Exercises
Practice applying the concepts learned in this chapter.
Click to see the list of suggested exercises
Exercise 1: Basic Struct and Methods
Define a Circle
struct with a radius
field (type f64
). Implement the following in an impl
block:
- An associated function
new(radius: f64) -> Circle
to create a circle. - A method
area(&self) -> f64
to calculate the area (π * r^2). Usestd::f64::consts::PI
. - A method
grow(&mut self, factor: f64)
that increases the radius byfactor
.
Instantiate a circle, calculate its area, grow it, and calculate the new area.
use std::f64::consts::PI; struct Circle { radius: f64, } impl Circle { // Associated function (constructor) fn new(radius: f64) -> Self { Circle { radius } } // Method to calculate area fn area(&self) -> f64 { PI * self.radius * self.radius } // Method to grow the circle fn grow(&mut self, factor: f64) { self.radius += factor; } } fn main() { let mut c = Circle::new(5.0); println!("Initial Area: {}", c.area()); c.grow(2.0); println!("Radius after growing: {}", c.radius); println!("New Area: {}", c.area()); }
Exercise 2: Tuple Struct and Newtype Pattern
Create a tuple struct Kilograms(f64)
to represent weight. Implement the Add
trait from std::ops::Add
for it, so you can add two Kilograms
values together. Demonstrate its usage.
use std::ops::Add; #[derive(Debug)] // Add Debug for printing struct Kilograms(f64); // Implement the Add trait for Kilograms impl Add for Kilograms { type Output = Self; // Result of adding two Kilograms is Kilograms fn add(self, other: Self) -> Self { Kilograms(self.0 + other.0) // Access inner f64 using .0 } } fn main() { let weight1 = Kilograms(10.5); let weight2 = Kilograms(5.2); let total_weight = weight1 + weight2; // Uses the implemented Add trait println!("Total weight: {:?}", total_weight); // e.g., Total weight: Kilograms(15.7) println!("Value: {}", total_weight.0); // Access the inner value }
Exercise 3: Struct with References and Lifetimes
Define a struct DataView<'a>
that holds an immutable reference (&'a [u8]
) to a slice of bytes. Implement a method len(&self) -> usize
that returns the length of the slice. Demonstrate creating an instance and calling the method.
struct DataView<'a> { data: &'a [u8], } impl<'a> DataView<'a> { fn len(&self) -> usize { self.data.len() } } fn main() { let my_data: Vec<u8> = vec![10, 20, 30, 40, 50]; // Create a view of part of the data (elements at index 1, 2, 3) let data_view = DataView { data: &my_data[1..4] }; println!("Data slice: {:?}", data_view.data); // e.g., Data slice: [20, 30, 40] println!("Length of view: {}", data_view.len()); // e.g., Length of view: 3 }
Exercise 4: Generic Struct with Trait Bounds
Create a generic struct MinMax<T>
that holds two values of type T
. Implement a method get_min(&self) -> &T
that returns a reference to the smaller of the two values. This method should only be available if T
implements the PartialOrd
trait. Demonstrate its usage with numbers and potentially strings.
use std::cmp::PartialOrd; struct MinMax<T> { val1: T, val2: T, } impl<T: PartialOrd> MinMax<T> { // This method only exists if T can be partially ordered fn get_min(&self) -> &T { if self.val1 <= self.val2 { &self.val1 } else { &self.val2 } } } // We can still have methods that don't require PartialOrd impl<T> MinMax<T> { fn new(v1: T, v2: T) -> Self { MinMax { val1: v1, val2: v2 } } } fn main() { let numbers = MinMax::new(15, 8); println!("Min number: {}", numbers.get_min()); // 8 let strings = MinMax::new("zebra", "ant"); println!("Min string: {}", strings.get_min()); // "ant" // struct Unorderable; // A struct that doesn't implement PartialOrd // let custom = MinMax::new(Unorderable, Unorderable); // // custom.get_min(); // Error! Unorderable does not implement PartialOrd }
Exercise 5: Destructuring, Update Syntax, and Printing
Define a Config
struct with fields host: String
, port: u16
, use_https: bool
.
- Derive
Debug
andDefault
. - Create a default
Config
instance and print it using debug format. - Create a new
Config
instance, overriding only thehost
field using struct update syntax and the default instance. Print this instance too. - Write a function
print_host_only(&Config { ref host, .. }: &Config)
that uses destructuring to print only the host. Call this function.
#[derive(Default, Debug)] // Derive Default and Debug struct Config { host: String, port: u16, use_https: bool, } // Function using destructuring in parameter fn print_host_only(&Config { ref host, .. }: &Config) { // Use 'ref' to borrow String println!("Host from function: {}", host); } fn main() { // 1. Create and print default config let default_config = Config::default(); println!("Default config: {:?}", default_config); // 2. Create and print custom config using struct update syntax let custom_config = Config { host: String::from("api.example.com"), ..default_config // Use default values for port and use_https }; println!("Custom config: {:?}", custom_config); // 3. Call function that destructures the parameter print_host_only(&custom_config); // Pass a reference }