🎓 A1Learner
🔍
0 of 142 lessons completed

Rust Patterns

A pattern is a shape that a value is compared against. A fitting value can have its pieces pulled out and named.

Pull a struct apart in one let

In a struct pattern, the fields may be listed in any order, and each field may be renamed as it comes out. Leaving a field out without a trailing .. is an error, because the pattern must account for every field:

struct Point { x: i32, y: i32 }
let p = Point { x: 3, y: 7 };
let Point { y: up, x } = p;
println!("x = {} up = {}", x, up);
// let Point { x } = p; // error: the pattern leaves out y — name it, or end with ..

Ignore pieces with _ and ..

Use _ to ignore a piece you do not need, and .. to ignore all the remaining pieces of a struct or tuple:

let triple = (10, 20, 30);
let (first, _, third) = triple;
println!("first = {} third = {}", first, third);
let p = Point { x: 3, y: 7 };
let Point { x, .. } = p;
println!("only x = {}", x);

Ranges, choices, and @ bindings

d @ 1..=9 means: match any value from 1 to 9, and also save it in d. Use | between choices in one arm, and a range pattern works only for numbers and single characters:

let digit = 7;
let label = match digit { d @ 1..=9 => format!("digit {}", d), _ => String::from("not a digit") };
println!("{}", label);
let pick = match 2 { 1 | 2 => "one or two", _ => "other" };
println!("{}", pick);
let group = match 'e' { 'a'..='j' => "early letter", _ => "later or other" };
println!("{}", group);

An arm can run a block

An arm may hold a whole block. The block can test a value the pattern alone cannot:

let reading = Some(-3);
let report = match reading { Some(x) => { if x > 0 { "positive" } else { "zero or negative" } }, None => "nothing" };
println!("{}", report);

ref borrows a piece instead of moving it

Inside a pattern, ref borrows a piece instead of moving it out. So the value the piece came from still works afterwards:

let name = Some(String::from("Ada"));
match name {
    Some(ref s) => println!("hello {}", s),
    None => println!("nobody"),
}
println!("name still here: {:?}", name);

Patterns nest — and test literals

Patterns nest: Some(Some(n)) matches an enum inside an enum. A field can be tested with a literal inside the pattern, so Point { x, y: 0 } matches only points whose y is 0:

let wrapped = Some(Some(5));
let found = match wrapped { Some(Some(n)) => n, _ => 0 };
println!("found = {}", found);
let p = Point { x: 3, y: 0 };
let label = match p { Point { x, y: 0 } => format!("on the ground at x = {}", x), Point { x, y } => format!("in the air at ({}, {})", x, y) };
println!("{}", label);

Example

struct Point { x: i32, y: i32 }
fn main() {
    let p = Point { x: 3, y: 7 };
    let Point { x, y } = p;
    let label = match (x, y) {
        (0, 0) => "origin",
        (0, _) => "on y-axis",
        (_, 0) => "on x-axis",
        _ => "somewhere",
    };
    println!("({}, {}) is {}", x, y, label);
}

Key points

  • Patterns appear in match, let, if let, function inputs, and closures
  • Every pattern does the same job: test the shape, then name the pieces that fit.

In this example, p is a Point with x = 3 and y = 7. The let line uses a pattern to pull the struct apart into plain x and y variables. Then a match on the tuple (x, y) sorts the point into kinds. (0, 0) is the origin. (0, _) lies on the y-axis. (_, 0) lies on the x-axis. And _ catches everything else. (3, 7) only fits the last arm, so the label is "somewhere", and the println! shows (3, 7) is somewhere.

Note

Patterns are one small language inside Rust.

The same shapes work everywhere: learn them once, and use them in every lesson from here on.

Try it Yourself

Edit the code, then press Run.

Running is simulated · Rust Playground

Exercise

What does n @ 1..=9 do in a pattern?