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.
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 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);
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.