Method Syntax

About This Module

This module introduces method syntax in Rust, which brings aspects of object-oriented programming to the language by combining properties and methods in one object. You'll learn how methods are functions defined within the context of a struct and how to use impl blocks to define methods.

Prework

Prework Readings

Read the following sections from "The Rust Programming Language" book:

Pre-lecture Reflections

Before class, consider these questions:

  1. How do methods differ from regular functions in Rust?
  2. What is the significance of the self parameter in method definitions?
  3. When would you choose to use associated functions vs. methods?
  4. How do methods help with code organization and encapsulation?
  5. What are the benefits of the impl block approach compared to other languages?

Learning Objectives

By the end of this module, you should be able to:

  • Define methods within impl blocks for structs
  • Understand the role of self in method definitions
  • Create associated functions that don't take self
  • Use methods to encapsulate behavior with data
  • Apply method syntax for cleaner, more readable code

Method Syntax Overview

Brings aspects of object-oriented programming to Rust: combine properties and methods in one object.

Methods are functions that are defined within the context of a struct.

The first parameter is always self, which refers to the instance of the struct the method is being called on.

Use and impl (implementation) block on the struct to define methods.

struct Point {  // stores x and y coordinates
    x: f64,
    y: f64,
}

struct Rectangle {  // store upper left and lower right points
    p1: Point,
    p2: Point,
}

impl Rectangle {
    // This is a method
    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())
    }
}

fn main() {
    let rectangle = Rectangle {
        p1: Point{x:0.0, y:0.0},
        p2: Point{x:3.0, y:4.0},
    };

    println!("Rectangle perimeter: {}", rectangle.perimeter());
    println!("Rectangle area: {}", rectangle.area());
}

Associated Functions without self parameter

Useful as constructors.

You can have more than one impl block on the same struct.

struct Point {  // stores x and y coordinates
    x: f64,
    y: f64,
}

struct Rectangle {  // store upper left and lower right points
    p1: Point,
    p2: Point,
}

impl Rectangle {
    // This is a method
    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())
    }
}

impl Rectangle {
    fn new(p1: Point, p2: Point) -> Rectangle {
        Rectangle { p1, p2 }  // instantiate a Rectangle struct and return it
    }
}

fn main() {
    // instantiate a Rectangle struct and return it
    let rect = Rectangle::new(Point{x:0.0, y:0.0}, Point{x:3.0, y:4.0});  
    println!("Rectangle area: {}", rect.area());
}

Common Patterns

Builder Pattern with Structs:

struct Config {
    host: String,
    port: u16,
    debug: bool,
    timeout: u32,
}

impl Config {
    fn new() -> Self {
        Config {
            host: String::from("localhost"),
            port: 8080,
            debug: false,
            timeout: 30,
        }
    }
    
    fn with_host(mut self, host: &str) -> Self {
        self.host = String::from(host);
        self
    }
    
    fn with_debug(mut self, debug: bool) -> Self {
        self.debug = debug;
        self
    }
}

fn main() {
    // Usage
    let config = Config::new()
            .with_host("api.example.com")
            .with_debug(true);
}