When you need a copy with one change
You are writing a configuration loader. You have a ServerConfig struct with ten fields: host, port, timeout, retries, log level, and so on. The user passes a JSON file that only specifies the port. You need to merge that partial data with your defaults without manually typing out every single field. Doing it by hand is error-prone. If you add a field to the struct tomorrow, your merge code breaks silently or requires a tedious update. Struct update syntax solves this. It lets you take an existing instance and override specific fields in one clean expression.
The syntax in plain words
Think of struct update syntax like filling out a form based on a previous submission. You have a completed form sitting on your desk. You want a new version that is identical except for the address. You do not rewrite the whole form. You copy the old one and change the address. In Rust, the .. operator does exactly that. It tells the compiler: "Create a new struct. Fill in the fields I list here. For everything else, grab the values from this other instance."
A struct literal is the syntax { field: value } used to create a struct. The update syntax is .. placed inside that literal, followed by the source struct. The source struct provides the fallback values for any field you did not explicitly set.
Minimal example
struct Point {
x: i32,
y: i32,
}
fn main() {
let original = Point { x: 10, y: 20 };
// Create a new Point. Override x with 30.
// The ..original part copies y from original.
// i32 implements Copy, so no move happens.
let updated = Point { x: 30, ..original };
println!("Original: ({}, {})", original.x, original.y);
println!("Updated: ({}, {})", updated.x, updated.y);
}
The output shows Original: (10, 20) and Updated: (30, 20). The updated struct has the new x value and the y value from original.
How the compiler processes it
The compiler parses Point { x: 30, ..original } in a specific order. It checks the struct definition to see which fields exist. It sees x is provided in the literal. It sees ..original. It looks at original and extracts the value for y. It constructs a new Point using the provided x and the extracted y.
The .. must be the last element in the struct literal. If you write Point { ..original, x: 30 }, the compiler rejects it. This rule prevents ambiguity. If you could override a field after the .., the compiler could not know which value wins. Rust forces you to be explicit. Explicit overrides come first. The rest comes from the source.
Put .. last. The compiler enforces this strictly.
Realistic example: Configuration defaults
Real code often involves types that do not implement Copy, like String. This introduces move semantics.
struct ServerConfig {
host: String,
port: u16,
timeout_secs: u32,
retries: u8,
}
fn default_config() -> ServerConfig {
ServerConfig {
host: String::from("localhost"),
port: 8080,
timeout_secs: 30,
retries: 3,
}
}
fn main() {
let defaults = default_config();
// User wants to change only the port.
// We create a new config based on defaults.
// host moves into user_config.
let user_config = ServerConfig {
port: 9090,
..defaults
};
// host, timeout_secs, and retries are moved from defaults.
println!("Host: {}", user_config.host);
println!("Port: {}", user_config.port);
// defaults is now partially moved.
// You cannot use defaults.host anymore.
}
The host field is a String. String does not implement Copy. When ..defaults runs, the host value moves from defaults to user_config. The defaults variable is now partially moved. You cannot access defaults.host after this line. If you try, the compiler rejects the code with E0382 (use of moved value).
If you need to keep defaults alive, you must clone it.
// Clone defaults so the original stays usable.
let user_config = ServerConfig {
port: 9090,
..defaults.clone()
};
The community convention is to use ..source.clone() when the source is a local variable you still need. When the source is a temporary value or you are done with it, use ..source to move the fields. Moving is cheaper than cloning. Name the source variable clearly, like ..defaults or ..base, so readers know where the values come from.
Trust the move semantics. If you need the source, clone it explicitly.
Move semantics and the Copy trait
Struct update syntax interacts directly with ownership. Every field you copy via .. follows the rules of its type. If the type implements Copy, the value is duplicated. If the type does not implement Copy, the value moves.
The Copy trait is a marker trait. It tells the compiler that the type can be duplicated bit-for-bit without side effects. Primitive integers, floats, booleans, and references implement Copy. String, Vec, and custom structs without #[derive(Copy)] do not.
When you use ..other, the compiler checks each field. If a field is Copy, it copies. If a field is not Copy, it moves. This means the source struct becomes unusable for any non-Copy fields. This is a common trap for beginners. You write let b = Struct { field: new_val, ..a } and then try to use a later. The compiler stops you.
If you want to avoid moves, derive Copy and Clone on your struct.
#[derive(Copy, Clone)]
struct Point {
x: i32,
y: i32,
}
Now ..original copies everything. original remains fully usable.
Private fields block the update
Struct update syntax respects visibility. You can only copy fields that are accessible from your current scope. If the source struct has private fields, ..other fails.
mod config {
pub struct ServerConfig {
pub host: String,
pub port: u16,
secret_key: String, // Private field
}
pub fn default() -> ServerConfig {
ServerConfig {
host: String::from("localhost"),
port: 8080,
secret_key: String::from("hidden"),
}
}
}
fn main() {
let defaults = config::default();
// This fails. secret_key is private.
// let updated = config::ServerConfig {
// port: 9090,
// ..defaults
// };
}
The compiler rejects this with error[E0616]: field 'secret_key' of struct 'ServerConfig' is private. The update syntax requires access to every field it copies. You cannot bypass encapsulation with ...
If you need to update a struct with private fields, you must use a public method provided by the module, or the struct must implement a builder pattern that exposes the necessary changes.
Private fields stay private. Update syntax respects visibility.
Builder patterns and ..self
Struct update syntax is idiomatic in builder patterns. A builder pattern allows you to construct a complex object step by step. Each step returns a new instance with one field set.
struct Config {
port: u16,
host: String,
timeout: u32,
}
impl Config {
fn new() -> Self {
Self {
port: 80,
host: String::from("localhost"),
timeout: 30,
}
}
fn with_port(self, port: u16) -> Self {
// Return a new Config with the port updated.
// The ..self copies all other fields.
Self { port, ..self }
}
fn with_host(self, host: String) -> Self {
Self { host, ..self }
}
}
fn main() {
let config = Config::new()
.with_port(8080)
.with_host(String::from("example.com"));
println!("Port: {}", config.port);
println!("Host: {}", config.host);
}
The method with_port takes self by value. It returns a new Self. The expression Self { port, ..self } creates the new instance. The port field is set to the argument. The ..self copies host and timeout from the input self. This pattern is clean and efficient. It avoids mutating state and makes the data flow explicit.
Builder methods return Self. Use ..self to carry forward the state.
Pitfalls and compiler errors
Struct update syntax has a few sharp edges. Knowing them saves debugging time.
The .. must be last. If you place it in the middle or at the start, the compiler rejects the code. The error message is direct. It tells you the struct update syntax must end with a base struct. Fix the order.
Field types must match. You cannot override a field with a value of the wrong type. If x is i32, you cannot pass 30.0. The compiler rejects this with E0308 (mismatched types). The type of the override must match the struct definition exactly.
You cannot omit fields unless you use ... If you write Point { x: 30 } without .., the compiler complains that y is missing. The update syntax is the only way to skip fields in a struct literal.
You cannot use .. with a value that is not a struct of the same type. You cannot write Point { x: 30, ..some_tuple }. The source must be an instance of the same struct.
Watch the order. Match the types. Use the right source.
Decision: when to use struct update syntax
Use struct update syntax when you need a modified copy of an existing instance and want to avoid repeating field names. Use struct update syntax when implementing a builder pattern where you return a new instance with one field changed. Use struct update syntax when merging partial user input with defaults. Reach for manual construction when you are creating a struct from scratch and have no base instance to reference. Reach for manual construction when the fields come from completely different sources and no single struct represents the default state. Use clone() on the whole struct when you need an exact duplicate and the struct implements Clone. Use clone() when you want to avoid move semantics without changing any fields.