Associated functions & Methods
Examples
struct Point { x: f64, y: f64, } // Implementation block, all `Point` associated functions & methods go in here. impl Point { // This is an "associated function" because this function is associated with // a particular type, that is, Point. // // Associated functions don't need to be called with an instance. // These functions are generally used like constructors. fn origin() -> Point { Point { x: 0.0, y: 0.0 } } // Another associated function, taking two arguments: fn new(x: f64, y: f64) -> Point { Point { x: x, y: y } } } struct Rectangle { p1: Point, p2: Point, } impl Rectangle { // This is a method. // `&self` is sugar for `self: &Self`, where `Self` is the type of the // caller object. In this case `Self` = `Rectangle` fn area(&self) -> f64 { // `self` gives access to the struct fields via the dot operator. let Point { x: x1, y: y1 } = self.p1; let Point { x: x2, y: y2 } = self.p2; // `abs` is a `f64` method that returns the absolute value of the // caller ((x1 - x2) * (y1 - y2)).abs() } fn perimeter(&self) -> f64 { let Point { x: x1, y: y1 } = self.p1; let Point { x: x2, y: y2 } = self.p2; 2.0 * ((x1 - x2).abs() + (y1 - y2).abs()) } // This method requires the caller object to be mutable // `&mut self` desugars to `self: &mut Self` fn translate(&mut self, x: f64, y: f64) { self.p1.x += x; self.p2.x += x; self.p1.y += y; self.p2.y += y; } } // `Pair` owns resources: two heap allocated integers. struct Pair(Box<i32>, Box<i32>); impl Pair { // This method "consumes" the resources of the caller object // `self` desugars to `self: Self` fn destroy(self) { // Destructure `self` let Pair(first, second) = self; println!("Destroying Pair({}, {})", first, second); // `first` and `second` go out of scope and get freed. } } fn main() { let rectangle = Rectangle { // Associated functions are called using double colons p1: Point::origin(), p2: Point::new(3.0, 4.0), }; // Methods are called using the dot operator. // Note that the first argument `&self` is implicitly passed, i.e. // `rectangle.perimeter()` === `Rectangle::perimeter(&rectangle)` println!("Rectangle perimeter: {}", rectangle.perimeter()); println!("Rectangle area: {}", rectangle.area()); let mut square = Rectangle { p1: Point::origin(), p2: Point::new(1.0, 1.0), }; // Error! `rectangle` is immutable, but this method requires a mutable // object. //rectangle.translate(1.0, 0.0); // TODO ^ Try uncommenting this line // Okay! Mutable objects can call mutable methods square.translate(1.0, 1.0); let pair = Pair(Box::new(1), Box::new(2)); pair.destroy(); // Error! Previous `destroy` call "consumed" `pair` //pair.destroy(); // TODO ^ Try uncommenting this line }
Exercises
Method
- ππ Methods are similar to functions: Declare with
fn, have parameters and a return value. Unlike functions, methods are defined within the context of a struct (or an enum or a trait object), and their first parameter is alwaysself, which represents the instance of the struct the method is being called on.
struct Rectangle { width: u32, height: u32, } impl Rectangle { // Complete the area method which return the area of a Rectangle. fn area } fn main() { let rect1 = Rectangle { width: 30, height: 50 }; assert_eq!(rect1.area(), 1500); println!("Success!"); }
- ππ
selfwill take the ownership of current struct instance, however,&selfwill only borrow a reference from the instance.
// Only fill in the blanks, DON'T remove any line! #[derive(Debug)] struct TrafficLight { color: String, } impl TrafficLight { pub fn show_state(__) { println!("the current state is {}", __.color); } } fn main() { let light = TrafficLight{ color: "red".to_owned(), }; // Don't take the ownership of `light` here. light.show_state(); // ... Otherwise, there will be an error below println!("{:?}", light); }
- ππ The
&selfis actually short forself: &Self. Within animplblock, the typeSelfis an alias for the type that theimplblock is for. Methods must have a parameter namedselfof typeSelffor their first parameter, so Rust lets you abbreviate this with only the nameselfin the first parameter spot.
struct TrafficLight { color: String, } impl TrafficLight { // Using `Self` to fill in the blank. pub fn show_state(__) { println!("the current state is {}", self.color); } // Fill in the blank, DON'T use any variants of `Self`. pub fn change_state(__) { self.color = "green".to_string() } } fn main() { println!("Success!"); }
Associated functions
- ππ All functions defined within an
implblock are called associated functions because theyβre associated with the type named after theimpl. We can define associated functions that donβt haveselfas their first parameter (and thus are not methods) because they donβt need an instance of the type to work with.
#[derive(Debug)] struct TrafficLight { color: String, } impl TrafficLight { // 1. Implement an associated function `new`, // 2. It will return a TrafficLight contains color "red" // 3. Must use `Self`, DONT use `TrafficLight` in fn signatures or body pub fn new() pub fn get_state(&self) -> &str { &self.color } } fn main() { let light = TrafficLight::new(); assert_eq!(light.get_state(), "red"); println!("Success!"); }
Multiple impl blocks
- π Each struct is allowed to have multiple impl blocks.
struct Rectangle { width: u32, height: u32, } // Using multiple `impl` blocks to rewrite the code below. impl Rectangle { fn area(&self) -> u32 { self.width * self.height } fn can_hold(&self, other: &Rectangle) -> bool { self.width > other.width && self.height > other.height } } fn main() { println!("Success!"); }
Enums
- πππ We can also implement methods for enums.
#[derive(Debug)] enum TrafficLightColor { Red, Yellow, Green, } // Implement TrafficLightColor with a method. impl TrafficLightColor { } fn main() { let c = TrafficLightColor::Yellow; assert_eq!(c.color(), "yellow"); println!("{:?}",c); }
Practice
@todo
You can find the solutions here(under the solutions path), but only use it when you need it