[Rust] Patterns

고승우·2024년 7월 24일
0

Rust

목록 보기
15/16
post-thumbnail

Patterns

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.

Literals, Variables, and Wildcards in Patterns

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 and Struct Patterns

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),

Array and Slice Patterns

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)
        }
    }
}

Reference Patterns

Rust patterns support two features for working with references. ref patterns borrow parts of a matched value. & patterns match references.

ref Pattern

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);
    }
}

& Pattern

Matching 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

Match Guards

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)
}

Matching Multiple Possibilities

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,
};

Binding with @ Patterns

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!")
    }
}

Where Patterns Are Allowed

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.

Conclusion

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.

profile
٩( ᐛ )و 

0개의 댓글