Skip to main content

Defining Methods for Structs

Methods are very similar to functions - the difference here being that methods are applied to structs. They breathe life into structs by providing associated logic that often utilizes its inner fields and represents an instance of that struct.

The syntax is nearly identical to a function, with the exception that its part of the implementation of the struct:

struct Person {
age: i32,
name: String,
}

// This is an implementation of 'Person'
// We can add methods on each instance of the struct
impl Person {
fn print_person(&self) {
println!("This person's name is {} and is {} years old.", self.name, self.age);
}
}
let a_person = Person {
age: 22,
name: String::from("Bader"),
};

a_person.print_person();
// This person's name is Bader and is 22 years old

In the above implementation, we defined a method, print_person, which takes &self as a parameter. &self is an immutable reference to that specific instance, in this case, a_person. To access these methods, we must first have an instance of the struct defined, then use the dot operator (.) to access the method. The &self parameter is actually a shorthand way of saying:

self: &Self -> person: &Person

Self, with a capital S, is an alias for the struct's type, whereas self with a lowercase s, refers to the actual instance with its initialized fields.

info

We borrowed &self - can you think of why?

It's also possible to accept more parameters of the same type as part of the method, for example:

impl Person {
fn print_person(&self) {
println!("This person's name is {} and is {} years old.", self.name, self.age);
}

fn is_older(&self, other: &Person) -> bool {
// Return if the other person is older or not as an expression
other.age > self.age
}
}
let a_person = Person {
age: 22,
name: String::from("Bader"),
};

let another_person_who_is_older = Person {
age: 35,
name: String::from("Johnny"),
};

a_person.is_older(&another_person_who_is_older); // false

Associated Functions

All functions under the impl (implementation) block are considered "associated" because they effectively become part of the type. Until now, you've seen all methods take self as a parameter - but it is possible to have a function that doesn't require an existing instance of the struct. They are often used as constructors to create new instances of that struct, as seen below:

impl Person {
// Notice the return type is `Self`:
fn new(age: i32, name: String) -> Self {
// Shorthand syntax for struct fields from the previous lesson!
Person {
age,
name
}
}
}

// use the :: keyword/operator to access this method
let person = Person::new(22, String::from("Bader"));

Try it yourself!

What's happening here?

This example showcases how a Person can be printed, compared against other borrowed Person(s), and how an associated function can be used to create a new Person.