10.3Field init shorthand and update syntax
Building instances by hand gets repetitive fast. Rust has two small pieces of syntax that remove the most common repetitions. Neither adds a new idea; both just save typing and, more importantly, save you from a class of copy-paste mistakes.
Field init shorthand
A struct is often built inside a function whose parameters already have the field names. That's no accident: when you name a function parameter email, it's because it holds an email. Written out the long way, the pattern looks silly:
struct User {
name: String,
email: String,
active: bool,
}
fn build_user(name: String, email: String) -> User {
User {
name: name,
email: email,
active: true,
}
}
name: name and email: email say the same word twice: the field name gets the value of the variable name. Rust lets you write the name once when the field and the variable match. This is field init shorthand:
fn build_user(name: String, email: String) -> User {
User {
name,
email,
active: true,
}
}
name on its own means name: name. Fields whose value comes from somewhere else (active: true) are written out as usual. The shorthand only applies when an in-scope variable has exactly the field's name; otherwise you spell out the value. Clippy will nudge you toward the shorthand if you write the redundant form, so this is the version you'll see in real code.
Best practice
Use field init shorthand whenever a variable already carries the field's name. Beyond saving keystrokes, it removes the mismatched-pair bug: with name: name, email: email written out, a tired copy-paste produces name: name, email: name, and both are String so the compiler can't object. The shorthand simply can't express that mistake.
Struct update syntax
The other repetition is "give me one just like this, but change a field or two." Suppose you have a user and want a copy with active flipped:
fn main() {
let user1 = User {
name: String::from("Ada"),
email: String::from("ada@example.com"),
active: true,
};
let user2 = User {
name: user1.name,
email: user1.email,
active: false,
};
}
Copying every unchanged field by hand is exactly the noise structs were supposed to remove. Struct update syntax says "the rest comes from that one" with ..:
let user2 = User {
active: false,
..user1
};
The fields you list (active: false) are set explicitly; ..user1 fills in every remaining field from user1. The ..base part comes last and has no trailing comma. Read it as "and the rest, from user1."
What moves when you update
The .. is convenient, but it is not free, and chapter 8 already taught you the reason to look twice. Struct update moves the fields it copies, unless they're Copy. After the user2 line above, user1.name and user1.email have been moved into user2, so user1 is partly gutted. Using it triggers a familiar refusal:
let user2 = User { active: false, ..user1 };
println!("{}", user1.name);error[E0382]: borrow of moved value: `user1.name`
--> src/main.rs:16:20
|
14 | let user2 = User { active: false, ..user1 };
| ------- value moved here
15 |
16 | println!("{}", user1.name);
| ^^^^^^^^^^ value borrowed here after move
This is lesson 8.4's move and lesson 8.6's "this function took it" beat, now applied field by field. String isn't Copy (it owns heap text, lesson 8.5), so ..user1 moves the strings out, and the moved-from user1 can't be read. Fields that are Copy, like the bool active, would be copied instead and leave the source intact. If user2 had reused only active and supplied its own name and email, user1 would survive whole.
Key insight
..base isn't a snapshot; it's a per-field move (or copy, for Copy fields). The same rule you learned for let s2 = s1; applies inside struct update, one field at a time. If you need both instances to keep using a heap-owning field, .clone() it explicitly (lesson 8.7) rather than reaching for ...
Quiz time
Question #1
Rewrite this using field init shorthand:
fn make_point(x: f64, y: f64) -> Point {
Point { x: x, y: y }
}Show solution
fn make_point(x: f64, y: f64) -> Point {
Point { x, y }
}
Both fields match an in-scope variable name, so both collapse to the bare name.
Question #2
Given user1 from this lesson, what does struct update produce, and what happens to user1 afterward?
let user2 = User {
email: String::from("new@example.com"),
..user1
};Show solution
user2 gets the supplied email and takes name and active from user1. name is a String, so it's moved out of user1; active is a bool (Copy) and is copied. Afterward user1 can't be used as a whole, and user1.name specifically is moved-out, so reading it is E0382. Reading user1.active would still be fine, since only the moved fields are off-limits.
Question #3
Why does field init shorthand prevent a bug that the written-out form allows?
Show solution
The written-out form name: name, email: email repeats each name, so a copy-paste slip can produce name: name, email: name, which compiles whenever the two fields share a type. The shorthand writes each name once; there's no second copy to get wrong, so the mismatch is simply unwritable.
So far structs have had named fields. The next lesson covers two leaner variants: tuple structs, whose fields are numbered, and unit structs, which have no fields at all but still earn their keep.