2014-04-17

2014-04-17: Updated for Rust v0.11-pre

Pattern matching is one of the features I like most about modern / functional style languages, also one I sincerely enjoy in Rust.

It works in a lot of different scenarios, the most basic is in a local scope using let.

let tuple = (1, 2);
let (a, b) = tuple; // => a =  1; b = 2

Structs

Should you have the need to capture a nested tuple or something, you can do that with the Haskell @ syntax:

struct Foo { x: (uint, uint), y: uint }

let foo = Foo { x: (1, 2), y: 3 };
let Foo { x: tuple @ (a, b), .. } = foo; // => a == 1; b == 2; tuple == (1, 2)

You can destructure structs and rename the variables:

struct Point { x: uint, y: uint }

let  p = Point { x: 1, y: 2 };
let  Point { x: new_x, y: new_y } = p; // => new_x == 1, new_y == 2

The order is not important:

let Point { y, x } = p;        // => y  == 2, x  == 1
let Point { y: y2, x: x2} = p; // => y2 == 2, x2 == 1

and you can also ignore some variables:

let Point { y: y3, .. } = p; // => y3 == 2
let Point { y } = p;         // -> error: pattern does not mention field `x`

you can match on ranges

let b = match 5 { 0..5 => true, _ => false}; // => true

match range and capture the value:

let age = 10;
let pax = match age {
  0..2  => ~"infant",
  a @ 2..12 => format!("child ({} yrs)", a),
  _         => ~"adult"
};

assert_eq!(pax, ~"child (10 yrs)");

Struct Variants

It also can be used to destructure struct variants:

#![feature(struct_variant)]

enum Fagazi {
  Fob { a: int },
  Foo { b: int, c: int }
}

#[test]
fn test_enum() {
  let foo = Foo { b: 1, c: 2 };

  match foo {
    Foo { b, c } if b > 2 => assert!((b,c) == (1, 2)),
    Foo { b, c } => assert!((b,c) == (1, 2)),
    _            => fail!("This will never happen, but the compiler doesn't know")
  };
}

You cannot just destructure an enum with multiple variants using let:

let Foo { b, c } = foo; // -> error: refutable pattern in local binding

You need to use a match instead of a simple let, because let can never fail using the second condition in match, the compiler knows, all possible paths have been exhausted.

One more cool feature of match are guard clauses:

let foo = Foo { b: 3, c: 2 };

match foo {
  Foo { b, c } if b <= 2 => assert!(b <= 2 && c == 2),
  Foo { b, c }           => assert!((b, c) == (3, 2)),
  _                      => unreachable!()
};

See the if b <= 2 in the first line? This is called a guard, it will match only if the pattern matches and the guard clause is true.

Take also notice of the unreachable!() expression. As mentioned before all match clauses need to be exhaustive. unreachable!() expands to fail!("internal error: entered unreachable code").

match allows to match on concrete values:

let foo = Some(1);

match foo {
  Some(3) => println!("three"),
  Some(2) => println!("two"),
  Some(v) => println!("not two, {}", v),
  None    => unreachable!()
}

Remember, that a match must contain all possibilities, otherwise you'll get an error, saying that you haven't covered all patterns. In this case if you'd leave out the last None, the compiler would tell you: non-exhaustive patterns: None not covered.

Vectors

You can destructure vectors, too:

let v = vec!(1, 2, 3, 4, 5);

match v.as_slice() {
    []                       => println!("empty"),
    [elem]                   => println!("{}", elem),   // => 1
    [first, second, ..rest]  => println!("{:?}", rest)  // => &[3, 4, 5]
  }

If you only care about the first or last values you can do this:

let v = vec!(1, 2, 3);

match v.as_slice() {
  [first, ..] => assert_eq!(first, 1),
  [.., last]  => assert_eq!(last, 3),
  _           => unreachable!()
}

or if you want the first, last, but also the middle:

let v = vec!(1, 2, 3, 4, 5);

match v.as_slice() {
  [first, .. middle, last] => println!("{:?} {:?} {:?}", first, middle, last),
  _                        => unreachable!()
}

matching on a ~[str] works just like matching any other vector

match ["foo", "bar"] {
  ["foo"] => 1,
  _ => 2,
}

Function Arguments

It works in a function's arguments:

fn my_function((a, b) : (uint, uint)) -> uint {
  a + b
}

fn main() {
  let pair = (1, 2);
  my_function(pair); // => 3
}

Loops

You can also use destructuring in for loops:

struct Pair { x: int, y: int }

let pairs = ~[Pair {x: 10, y: 20}, Pair {x: 30, y: 0}];

for &Pair {x, y} in pairs.iter() {
  assert_eq!(x + y, 30);
}

For comments head over to Reddit



blog comments powered by Disqus