Data and behaviour
Fields and a constructor give a struct its data. The other half is behaviour: things an instance can do. A method is a function that belongs to a type and works on one instance of it. In Rust, methods are written in an impl block, and the method call rex.bark() uses the dot you already know from reading fields.
What makes a function a method
Inside an impl Dog block, the first parameter decides how the function can be called:
- No
self: an associated function, called on the type, likeDog::new("Rex"). &self: a method that borrows the instance for reading. The caller keeps ownership and can use the instance afterwards.&mut self: a method that borrows the instance for changing it. The binding must be declaredmut.self: a method that takes ownership. After the call, the instance has been moved and the caller can't use it again.
Most methods use &self. self is a keyword, not a name you choose, and inside the method it stands for the instance the method was called on. Fields are always reached through it: self.name.
The lesson's exercise, step by step
The starter code already has a Dog struct with a name field, a constructor new and a mainFunction that calls rex.bark(). That call doesn't compile yet because no bark exists. The task adds it:
- Declare
barkinside theimpl Dogblock, belownew, with&selfas its only parameter. The method only reads the dog's fields, so a shared borrow is enough, and the dog is not consumed. - Print the message. In the body, use
println!with"Woof! I am {}"and passself.nameas the argument. The{}placeholder is replaced by the argument.
The solution in Rust
struct Dog {
name: String,
}
impl Dog {
fn new(name: &str) -> Dog {
Dog {
name: name.to_string(),
}
}
fn bark(&self) {
println!("Woof! I am {}", self.name);
}
}
fn mainFunction() {
let rex = Dog::new("Rex");
rex.bark();
}
Running mainFunction prints:
Woof! I am Rex
Rust details worth knowing:
- Reading through a borrow.
println!only needs to look atself.name, sobarktakes&self. The callrex.bark()is shorthand forDog::bark(&rex), which also compiles and does the same thing. - Calling twice. Since
barkonly borrows,rexis still usable afterwards and the method can be called as often as you like. - You must write
self.. Inside a method, a barenamedoesn't refer to the field. The compiler says it cannot find the valuenameand suggestsself.name. - Forgetting the method. Calling
rex.bark()when no such method exists gives "no method namedbarkfound for structDog". - Visibility. Methods are private to their module unless declared
pub fn. In this exercise everything is in one file, so plainfnis enough. Code in another module can call only thepubmethods.
Methods that change the instance
A method that modifies a field takes &mut self:
impl Dog {
fn birthday(&mut self) {
self.age += 1;
}
}
This assumes Dog also has an age: i32 field. For a dog created as let mut d = Dog::new("Luna", 7);, calling d.birthday() twice takes the age from 7 to 9. If the binding is a plain let, the call is rejected: "cannot borrow d as mutable, as it is not declared as mutable".
A method that takes plain self consumes the instance:
fn into_name(self) -> String {
self.name
}
After let n = d.into_name();, the dog's name belongs to n and d has been moved: using d afterwards is a compile error, "borrow of moved value". Methods like this are less common, and they are used when the value is being converted into something else.
Common mistakes
- Leaving out
&self.fn bark()is an associated function, sorex.bark()fails to compile. It would have to be called asDog::bark(), and it couldn't readself.name. - Using
selfinstead of&self. It compiles, but the first call moves the dog, and a secondrex.bark()fails. - Writing
barkoutside theimplblock. A function outside the block is an ordinary function, not a method. - Printing the name without a placeholder.
println!("Woof! I am ", self.name)doesn't compile, since the format string has no{}for the argument. - Using
&selfto modify fields. Assigning toself.agethrough a shared borrow is an error. Use&mut self.
Key takeaways
- Methods live in an
implblock and takeself,&selfor&mut selfas their first parameter. &selfreads,&mut selfchanges, and plainselftakes ownership.- Call a method on a value with a dot, and an associated function on the type with
::. - Fields are reached through
selfinside a method.
