Rust: Methods

Home · Blog

14 February 2023

A method is a function that’s associated with a type and that you can call “on” a value of that type. Rust actually has three kinds of associated items that you can define on a type: constants, functions, and methods.

Associated constants

Let’s start with associated constants. You define associated items in an impl block, like this:

#[derive(Debug)]
struct RGBColor(u8, u8, u8);

impl RGBColor {
    // an associated constant
    const BLACK: RGBColor = RGBColor(0, 0, 0);
}

An impl block like that is called an inherent implementation to distinguish it from trait implementations. You access the constant with the :: syntax:

let c = RGBColor::BLACK;

Associated functions

Associated functions are defined in a similar way:

impl RGBColor {
    // an associated function
    fn grey(value: u8) -> RGBColor {
        RGBColor(value, value, value)
    }
}

Associated functions are often used as constructors like this. They’re called the way you’d expect:

let dark_grey = RGBColor::grey(0x40);

Methods

A method looks like an associated function except its first parameter is &self, &mut self, or self. For example:

impl RGBColor {
    // a method
    fn is_black(&self) -> bool {
        let &RGBColor(r, g, b) = self;
        r == 0 && g == 0 && b == 0
    }
}

When it’s called on a value, the self will be a reference to that value. You call the method like this:

let c = RGBColor(0x10, 0x20, 0x30);
println!("{}", c.is_black());   // prints false

This last line of code takes advantage of automatic referencing, which turns c into &c if needed. Method calls are one of the few cases where this happens in Rust. Without it, the code would look like this:

println!("{}", (&c).is_black());

If you use &mut self, you get a mutating method:

impl RGBColor {
    // a mutating method
    fn make_it_black(&mut self) {
        let RGBColor(ref mut r, ref mut g, ref mut b) = self;
        *r = 0;
        *g = 0;
        *b = 0;
    }
}

If c is declared mutable, we can call it like this:

c.make_it_black();
println!("Is it black? {}", c.is_black());   // prints true

The third option is to use just self to get a consuming method:

impl RGBColor {
    // a consuming method
    fn destroy(self) {
        println!("consuming color: {:?}", self);
    }
}

Because the oject is moved into the method, the caller won’t be able to use it afterwards:

c.destroy();
//println!("{:?}", c); // not allowed

Having this destroy method on RGBColor doesn’t really make sense, but you might have a similar method that, for example, closes a network connection. After calling the method you can’t use the connection anymore so it makes sense for the object to be moved.