Start learning free

LESSON 211 OF 414 · FUNCTIONS & CLASSES

Class Methods in Rust: Methods in an impl Block

Lesson Read the full explanation

TL ; DR:

  • A member function (or method) is a function defined for a struct. It can read and write the object's data members.


  • Define methods in an impl block; the &self parameter is what lets you call them with a dot (add pub if other modules need to call them):

    struct Dog {
        pub name: String,
    }
    
    impl Dog {
        fn new(name: &str) -> Dog {
            Dog { name: name.to_string() }
        }
    
        pub fn bark(&self) {
            println!("Woof! I am {}", self.name);
        }
    }
    

  • Call it on an object with the dot operator: dog.bark();



Full lesson:

So far you've added data to a struct with member variables and a constructor. The other half of OOP is behavior — actions an object can perform. That's what member functions are for.


Your task

Add a bark() member function that prints "Woof! I am " followed by the dog's name.

Woof! I am Rex
STEP 1 OF 2
    Ctrl + Enter to check

    RUST · FUNCTIONS & CLASSES

    Class Methods in Rust, explained

    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, like Dog::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 declared mut.
    • 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:

    1. Declare bark inside the impl Dog block, below new, with &self as its only parameter. The method only reads the dog's fields, so a shared borrow is enough, and the dog is not consumed.
    2. Print the message. In the body, use println! with "Woof! I am {}" and pass self.name as 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 at self.name, so bark takes &self. The call rex.bark() is shorthand for Dog::bark(&rex), which also compiles and does the same thing.
    • Calling twice. Since bark only borrows, rex is still usable afterwards and the method can be called as often as you like.
    • You must write self.. Inside a method, a bare name doesn't refer to the field. The compiler says it cannot find the value name and suggests self.name.
    • Forgetting the method. Calling rex.bark() when no such method exists gives "no method named bark found for struct Dog".
    • Visibility. Methods are private to their module unless declared pub fn. In this exercise everything is in one file, so plain fn is enough. Code in another module can call only the pub methods.

    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, so rex.bark() fails to compile. It would have to be called as Dog::bark(), and it couldn't read self.name.
    • Using self instead of &self. It compiles, but the first call moves the dog, and a second rex.bark() fails.
    • Writing bark outside the impl block. 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 &self to modify fields. Assigning to self.age through a shared borrow is an error. Use &mut self.

    Key takeaways

    • Methods live in an impl block and take self, &self or &mut self as their first parameter.
    • &self reads, &mut self changes, and plain self takes ownership.
    • Call a method on a value with a dot, and an associated function on the type with ::.
    • Fields are reached through self inside a method.
    Keep going with RustAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free