Right, let’s get our hands dirty. You’ve defined a beautiful struct, a pristine blueprint for some data. It’s a work of art, but it’s useless until you actually build one. That’s what we’re doing here: construction. And like any good construction project, you can do it elegantly, you can do it messily, and you can definitely smash your thumb with the hammer if you’re not paying attention.

The Straightforward Way: Field Init Shorthand

The most common way to bring a struct to life is by specifying a value for every field. It’s straightforward, it’s explicit, and the compiler loves it.

struct User {
    active: bool,
    username: String,
    email: String,
    sign_in_count: u64,
}

fn main() {
    let user1 = User {
        email: String::from("someone@example.com"),
        username: String::from("someusername123"),
        active: true,
        sign_in_count: 1,
    };
}

Notice anything? The order doesn’t matter. The compiler isn’t pedantic about sequence; it just matches the field names you provide with the names in the struct definition. This is a huge win for readability—you list the fields in the order that makes the most sense for your code, not the order some bureaucrat (or past you) defined them in.

Now, let’s say you have variables lying around with the exact same names as your struct fields. Writing email: email feels… redundant. Because it is. Rust agrees, and gives us the field init shorthand.

fn build_user(email: String, username: String) -> User {
    User {
        email,    // This is shorthand for `email: email`
        username, // And this for `username: username`
        active: true,
        sign_in_count: 1,
    }
}

This isn’t just syntactic sugar; it’s a readability feature. It visually tightens the connection between the function’s parameters and the struct’s data, making the code’s intent clearer.

The “I Want a Copy, But With a Few Tweaks” Way

Here’s a classic scenario: you need a second User that’s mostly the same as user1, but with a different email. You could do this:

let user2 = User {
    email: String::from("another@example.com"),
    username: user1.username,
    active: user1.active,
    sign_in_count: user1.sign_in_count,
};

Yikes. That’s boilerplate city. It’s tedious, and it’s a fantastic way to introduce bugs if you add a field to the struct later and forget to update every.. single.. place.. where you did this.

Rust’s solution is the .. syntax, officially called “struct update syntax.” It’s a lifesaver.

let user2 = User {
    email: String::from("another@example.com"),
    ..user1
};

This tells the compiler: “For any field I haven’t explicitly set, just use the value from user1.” It’s brilliantly concise. But—and this is a big but—it involves a move. In this example, user1.username is a String, which doesn’t have the Copy trait. So when we use ..user1, the username field is moved into user2. Trying to use user1 after this line, except for fields that were Copy (like active and sign_in_count), will cause a compile-time error.

println!("{}", user1.active); // This is fine, it's a Copy type.
println!("{}", user1.username); // ERROR: borrow of moved value: `user1.username`

This is the compiler saving you from a use-after-move bug. The update syntax is powerful, but you have to be mindful of ownership. If you need to keep the original struct intact, you’ll need to .clone() the fields that are moved or restructure your logic.

Accessing Fields: It’s Not Magic

Once you have an instance, accessing its fields is delightfully boring and predictable: use a dot.

println!("User's email: {}", user2.email);
if user2.active {
    user2.sign_in_count += 1; // Modify a field if the struct is mutable
}

Ah, yes, mutability. Note the keyword in that last line: user2.sign_in_count += 1. This only works if user2 itself is declared as mutable. Mutability in Rust is a property of the binding, not the struct itself.

let mut user2 = User { ... }; // `mut` here allows modification of any field.
user2.email = String::from("newemail@example.com"); // This is allowed.

You can’t make individual fields mutable or immutable; the entire instance is. This keeps things simple and consistent. If you need interior mutability—where the struct itself is immutable but you need to change a field—that’s what types like Cell and RefCell are for, but that’s a story for another chapter. For now, just remember: if you want to change any part of it, the whole thing needs to be mut.