Destructuring and Pattern Matching
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