Wildcard Patterns
Rust provides three ways to match values without naming every component: the single-value wildcard (_), which matches any value without binding or moving it; a catch-all variable binding (other or _unused), which matches any value and binds it; and the rest pattern (..), which ignores multiple fields or elements.
Crucial Difference Between _ and _name
Unlike a variable prefixed with an underscore (_x), the bare underscore pattern _ never binds, moves, or borrows the matched value:
| Pattern | Binds the Value? | Moves Non-Copy Data? |
When Dropped? |
|---|---|---|---|
_ (Wildcard) |
No | No (the matched value stays untouched in place!) | At the end of the enclosing scope as normal |
_x (Unused variable) |
Yes (suppresses unused-variable warning) | Yes (moves ownership into _x) |
At the end of _x's scope |
let _ = expr; |
No | Evaluates expr and immediately drops the temporary result at the semicolon |
Immediately at ; |
.. (Rest pattern) |
No | No (ignores remaining struct fields, tuple items, or slice elements) | Unchanged |
fn main() {
let maybe_text = Some(String::from("Rust 2024"));
// Because `Some(_)` uses `_`, the inner `String` is NOT moved out of `maybe_text`!
if let Some(_) = maybe_text {
println!("maybe_text contains a value.");
}
// `maybe_text` is still completely valid and owns the `String`:
println!("Still owned: {maybe_text:?}");
}
maybe_text contains a value.
Still owned: Some("Rust 2024")
By contrast, writing if let Some(_text) = maybe_text moves the String into _text, consuming maybe_text.
Using _ and Catch-All Variable Bindings in match
When the final arm of a match needs to inspect or log the unmatched value, use a descriptive variable name (such as other); when the unmatched value is not needed, use _:
fn classify_port(port: u16) -> String {
match port {
22 => String::from("SSH"),
80 | 8080 => String::from("HTTP"),
443 => String::from("HTTPS"),
other => format!("Custom port ({other})"),
}
}
fn main() {
println!("{}", classify_port(443));
println!("{}", classify_port(5432));
}
HTTPS
Custom port (5432)
Ignoring Specific Positions vs. Ignoring All Remaining Fields (..)
You can place _ at specific positions inside a tuple or enum variant ((x, _, z)), or use .. to ignore all remaining fields of a struct or elements of a tuple/slice:
struct Telemetry {
timestamp: u64,
cpu_load: f32,
mem_used_mb: u32,
disk_io_mb: u32,
}
fn main() {
let reading = Telemetry {
timestamp: 1700000000,
cpu_load: 0.75,
mem_used_mb: 4096,
disk_io_mb: 12,
};
// Ignore all fields except `cpu_load` using `..`:
let Telemetry { cpu_load, .. } = reading;
println!("CPU Load: {:.0}%", cpu_load * 100.0);
// Ignore middle tuple elements using `..` or specific positions using `_`:
let coords = (10, 20, 30, 40);
let (first, .., last) = coords;
let (_, second, _, _) = coords;
println!("first = {first}, second = {second}, last = {last}");
}
CPU Load: 75%
first = 10, second = 20, last = 40
Unreachable Arms After a Wildcard (unreachable_patterns Warning)
Because match evaluates arms top-to-bottom and _ matches every possible value, any arm placed after a bare _ (without a match guard) can never be reached and triggers a compiler warning:
fn describe(n: u8) -> &'static str {
match n {
0 => "zero",
_ => "nonzero", // Always place the catch-all arm LAST!
}
}
fn main() {
println!("{}", describe(5));
}
nonzero