Patterns are the parts that appear before the =>
symbol.
fn rough_time_to_english(rt: RoughTime) -> String{
match rt{
RoughTime::InThePast(units, count) => format!("{} {} ago", count, units),
RoughTime::JustNow => format!("just now"),
RoughTime::InTheFuture(units, count) => format!("{} {} from now", count, units),
}
}
Pattern matching an enum, struct, or tuple works as though Rust is doing a simple left-to-right scan, checking each component of the pattern to see if the value matches it. If it doesn’t, Rust moves on to the next pattern.
When a pattern contains simple identifiers like units and count, those become local variables in the code following the pattern.
ou can use a single underscore _ as a pattern, the wildcard pattern:
fn match_string(string: String){
match string.as_str(){
"something" => {},
"blahblah" => {},
_ => {}
}
}
The wildcard pattern(_
) matches any value, but without storing it anywhere
Tuple patterns match tuples
fn describe_point(x: i32, y: i32) -> &'static str {
use std::cmp::Ordering::*;
match (x.cmp(&0), y.cmp(&0)) {
(Equal, Equal) => "at the origin",
(_, Equal) => "on the x axis",
(Equal, _) => "on the y axis",
(Greater, Greater) => "in the first quadrant",
(Less, Greater) => "in the second quadrant",
_ => "somewhere else",
}
}
Struct patterns use curly braces, just like struct expressions. They contain a subpat‐ tern for each field:
match balloon.location {
Point { x: 0, y: height } => println!("straight up {} meters", height),
Point { x: x, y: y } => println!("at ({}m, {}m)", x, y),
}
Even with the shorthand, it is cumbersome to match a large struct when we only care about a few fields. Too avoid this, use ..
to tell Rust you don't care about any of the other fields:
Some(Account { name, language, .. }) => language.show_custom_greeting(name),
They’re often used to filter out some special-case values and are useful any time you’re working with arrays whose values have a different meaning based on position.
fn greet_people(names: &[&str]) {
match names {
[] => {
println!("Hello, nobody.")
}
["Seung Woo"] => {
println!("Hello, Seung Woo.")
}
[a] => {
println!("Hello, {}.", a)
}
[a, b] => {
println!("Hello, {} and {}.", a, b)
}
[a, .., b] => {
println!("Hello, everyone from {} to {}.", a, b)
}
}
}
Rust patterns support two features for working with references. ref patterns borrow parts of a matched value. &
patterns match references.
Matching a noncopyable value moves the value. If we need a kind of pattern that borrows matched values instead of moving them, we can match ref pattern
:
match line_result {
Err(ref err) => log_error(err), // `err` is &Error (shared ref)
Ok(ref mut line) => { // `line` is &mut String (mut ref)
trim_comments(line); // modify the String in place
handle(line);
}
}
&
PatternMatching a reference follows all the rules we’ve come to expect. Lifetimes are enforced. So, if we try to match & pattern
with noncopyable fields, We'll get an error:
match friend.borrow_car() {
Some(&Car { engine, .. }) => {}// error: can't move out of borrow
None => {}
}
We can use a ref pattern
to borrow a refernce to a part.
Some(&Car { ref engine, .. }) => // ok, engine is a reference
Rust also provides match guards
, extra conditions that must be true in order for a match arm to apply, written as if CONDITION, between the pattern and the arm’s => token:
match point_to_hex(click) {
None => Err("That's not a game space."),
Some(hex) if hex == current_hex => Err("You are already there! You must click somewhere else"),
Some(hex) => Ok(hex)
}
The vertical bar |
can be used to combine several patterns in a single match arm:
let at_end = match chars.peek() {
Some(&'\r') | Some(&'\n') | None => true,
_ => false,
};
Finally, x @ pattern
matches exactly like the given pattern, but on success, instead of creating variables for parts of the matched value, it creates a single variable x
and moves or copies the whole value into it
fn binding_pattern(arr: &[i32]) {
match &arr{
target @ [1, ..] => println!("First value is one!: {:?}", target),
_ => println!("First value isn't one!")
}
}
they are allowed in place of an identifier. The meaning is always the same: instead of just storing a value in a single variable, Rust uses pattern matching to take the value apart.
This means patterns can be used to...
// ...unpack a struct into three new local variables
let Track { album, track_number, title, .. } = song; // ...unpack a function argument that's a tuple
fn distance_to((x, y): (f64, f64)) -> f64 { ... }
// ...iterate over keys and values of a HashMap
for (id, document) in &cache_map {
println!("Document #{}: {}", id, document.title);
}
// ...automatically dereference an argument to a closure
// (handy because sometimes other code passes you a reference // when you'd rather have a copy)
let sum = numbers.fold(0, |a, &num| a + num);
Those patterns that are guaranteed to match is called irrefutable patterns
, but there is a pattern which can't be guaranteed to match.
// ...handle just one enum variant specially
if let RoughTime::InTheFuture(_, _) = user.date_of_birth() {
user.set_time_traveler(true);
}
// ...run some code only if a table lookup succeeds
if let Some(document) = cache_map.get(&id) {
return send_cached_response(document);
}
// ...repeatedly try something until it succeeds
while let Err(err) = present_cheesy_anti_robot_task() {
log_robot_attempt(err); // let the user try again (it might still be a human)
}
// ...manually loop over an iterator
while let Some(_) = lines.peek() {
read_paragraph(&mut lines);
}
Those patterns are called refutable pattern
.
Patterns in Rust provide a powerful way to destructure and match data, simplifying code that deals with complex data structures. They allow for elegant and readable handling of various cases, such as enums, structs, and tuples. By using patterns, you can efficiently match and bind variables, handle special cases, and control program flow. Understanding and using patterns effectively can greatly enhance your Rust programming skills.