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) -> Circleto create a circle. - A method
area(&self) -> f64to 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
DebugandDefault. - Create a default
Configinstance and print it using debug format. - Create a new
Configinstance, overriding only thehostfield 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
}