Start learning free

LESSON 210 OF 414 · FUNCTIONS & CLASSES

Class Fields in Rust: Struct Fields and Mutation

Lesson Read the full explanation

TL ; DR:

  • A member variable (or field) is a variable that lives inside a struct. Each value of that struct has its own copy.


  • Declare fields inside the struct. Add pub if code in other modules should read and write them:

    struct Dog {
        pub name: String,
        pub age: i32,
    }
    

  • Access them on an object with the dot operator: dog.name = String::from("Rex"); (the binding must be mut)



Full lesson:

Right now your Dog struct is just an empty shell — every Dog value you create is identical. We need a way to attach data to each object.

That's what member variables are for. A member variable is a variable that belongs to a struct — and each value created from that struct gets its own copy of every member.


Declaring member variables

struct Dog {
    pub name: String,
    pub age: i32,
}
  • pub is a visibility modifier — a field declared with it is accessible from other modules. Without it, a field is private to the module where the struct is defined; code in the same file and module, like mainFunction here, can still use it.

  • name: String is a regular field declaration.


Reading and writing

let mut dog = Dog { name: String::new(), age: 0 };
dog.name = String::from("Rex");        // write
dog.age = 4;
println!("{}", dog.name);  // read

Each object has its own slot for every member, so two dogs can have different names without interfering.


Your task

Update the Dog struct to have two member variables: name (a String) and age (an i32); pub is optional here. Then in mainFunction, set them on the dog value (use "Rex".to_string() for the name) so the pre-filled println! prints:

Rex is 4 years old
STEP 1 OF 2
    Ctrl + Enter to check

    RUST · FUNCTIONS & CLASSES

    Class Fields in Rust, explained

    What a field is

    An empty Dog struct is an empty shell: every instance is identical and carries no information. A field, which other languages call a member variable, is a variable that belongs to a struct. Each instance gets its own copy of every field, so two dogs can have different names without interfering with each other.

    Declaring fields

    Fields go between the braces of the struct, each written as name: Type and separated by commas:

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

    This struct has two fields. name is a String, an owned, growable piece of text, and age is an i32. A comma after the last field is allowed and is the usual style.

    Reading and writing fields

    A field is reached with the dot operator. The same syntax reads it and, on the left of =, writes it:

    let mut dog = Dog { name: String::new(), age: 0 };
    dog.name = "Rex".to_string(); // write
    dog.age = 4;
    println!("{}", dog.name);     // read: Rex
    

    Creating the instance requires a value for every field. Here the placeholders are an empty string and 0, and the next two lines overwrite them. Rust has no automatic default value for a missing field: leaving one out is a compile error ("missing field").

    Each instance keeps its own data:

    let mut a = Dog { name: "A".to_string(), age: 1 };
    let b = Dog { name: "B".to_string(), age: 2 };
    a.age += 10;
    a.name.push_str("bc");
    println!("{} {} {} {}", a.name, a.age, b.name, b.age); // Abc 11 B 2
    

    Changing a left b alone. The expression a.name.push_str("bc") shows that a field can itself be an object with methods: a.name is a String, and push_str works on it.

    The lesson's exercise, step by step

    1. Declare the fields. Inside struct Dog, add a String field called name and an i32 field called age, separated by commas. Every Dog then owns its own copy of this data.
    2. Set them. In mainFunction, assign "Rex".to_string() to dog.name and 4 to dog.age. The starter code already declares dog with let mut, and that mut is what allows the assignments.

    The string needs to_string() because a literal such as "Rex" is a borrowed &str, while the field holds an owned String.

    The solution in Rust

    struct Dog {
        name: String,
        age: i32,
    }
    
    fn mainFunction() {
        let mut dog = Dog {
            name: String::new(),
            age: 0,
        };
        dog.name = "Rex".to_string();
        dog.age = 4;
        println!("{} is {} years old", dog.name, dog.age);
    }
    

    Running mainFunction prints:

    Rex is 4 years old
    

    Rust details that matter here:

    • Mutability belongs to the binding. Without mut on dog, dog.age = 4 is rejected: "cannot assign to dog.age, as dog is not declared as mutable". There is no per-field mut: the whole value is either changeable or not.
    • Types are checked. dog.age = "4" and dog.name = "Rex" are both mismatched-type errors, because age is an i32 and name a String. A misspelt field, like dog.nam, is reported as no such field on the type Dog.
    • Visibility. Fields are private by default, which means private to the module that defines the struct. Code in the same file and module, like this exercise, can use them freely, so no pub is needed. To let code in other modules read or write a field, put pub before it: pub name: String.
    • Fields own their data. The String inside dog is moved or dropped together with dog. Pulling a field out with a destructuring pattern, let Dog { name, age } = dog;, takes the owned values out of the struct.
    • Formatting. Each {} in println! takes the next argument. Both String and i32 can be printed with {}.

    Common mistakes

    • Leaving out mut. The binding must be declared let mut to change a field.
    • Assigning a literal to a String field. Use to_string(), String::from("Rex") or .into().
    • Missing commas between fields. Each field needs a comma after it, except that the last comma is optional.
    • Using a field name without the instance. Writing name = ... by itself refers to a variable called name. Fields are reached as dog.name.
    • Forgetting a field in the struct expression. Every field needs a value when the instance is created.

    Key takeaways

    • A field is declared as name: Type inside the struct, and every instance has its own copy.
    • The dot operator reads and writes fields.
    • Changing a field needs a let mut binding.
    • A struct that owns text stores a String, so convert literals with to_string().
    • Fields are private to their module unless marked pub.
    Keep going with RustAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free