【译】Rust中的切片模式

1,876 阅读3分钟

原文标题:Daily Rust: Slice Patterns
原文链接:adventures.michaelfbryan.com/posts/daily…
公众号: Rust 碎碎念
翻译 by: Praying

Rust 1.26中引入了一个很好的小特性——基本切片模式(Basic Slice Patterns) ,通过它能够在已知长度的切片(slice)上进行模式匹配(pattern match)。之后在Rust 1.42中,这个特性得到扩展,进而可以使用..来匹配“一切其他”。

从功能特性上来看,这似乎只是一个小小的补充,但是它让开发者能够写出更具有表达力的代码。

提醒

本文中的代码可以在对应的 playground 链接中获取,代码可以自由获取及修改\n 如果你觉得本文还不错,或者发现了文中的 bug,请在博客的issue tracker中告知于我。

处理多样性

切片模式最简单的应用之一就是通过匹配固定长度的切片来提供用户友好的信息。

一般而言,能够根据一句话中有 0 个、1 个或者更多的词语来定制语言是比较好的,例如下面这个例子:

fn print_words(sentence: &str) {
    let words: Vec<_> = sentence.split_whitespace().collect();

    match words.as_slice() {
        [] => println!("There were no words"),
        [word] => println!("Found 1 word: {}", word),
        _ => println!("Found {} words: {:?}", words.len(), words),
    }
}

fn main() {
    print_words("");
    print_words("Hello");
    print_words("Hello World!");
}

代码链接:playground

上面的代码会输出下面的内容:

There were no words
Found 1 word: Hello
Found 2 words: ["Hello", "World!"]

匹配一个切片的开头

..被称为“rest”模式,它让你能够匹配到切片剩余的部分。

根据[ELF 格式](en.wikipedia.org/wiki/Execut… ELF,据此,我们可以使用rest模式实现我们的is_elf()`函数。

use std::error::Error;

fn is_elf(binary: &[u8]) -> bool {
    match binary {
        [0x7f, b'E', b'L', b'F', ..] => true,
        _ => false,
    }
}

fn main() -> Result<(), Box<dyn Error>> {
    let current_exe = std::env::current_exe()?;
    let binary = std::fs::read(&current_exe)?;

    if is_elf(&binary) {
        print!("{} is an ELF binary", current_exe.display());
    } else {
        print!("{} is NOT an ELF binary", current_exe.display());
    }

    Ok(())
}

代码链接: playground

检测回文(Checking for Palindromes)

编程入门中一个常见的问题就是检测回文(palindromes)。我们知道,@符号会把一个新变量绑定到它所有匹配的内容,所以我们可以在切片的头部和尾部匹配从而实现一个比较优雅的is_palindrome()函数。

fn is_palindrome(items: &[char]) -> bool {
    match items {
        [first, middle @ .., last] => first == last && is_palindrome(middle),
        [] | [_] => true,
    }
}

代码链接:playground

简陋的参数解析器

你可能还会想用切片模式来“剥去(peeling off)”期望的前缀和后缀。

尽管已经存在像clapstructopt这样更强大的 crate,但是我们还是使用切片模式来实现我们自己的参数解析器。

fn parse_args(mut args: &[&str]) -> Args {
    let mut input = String::from("input.txt");
    let mut count = 0;

    loop {
        match args {
            ["-h" | "--help", ..] => {
                eprintln!("Usage: main [--input <filename>] [--count <count>] <args>...");
                std::process::exit(1);
            }
            ["-i" | "--input", filename, rest @ ..] => {
                input = filename.to_string();
                args = rest;
            }
            ["-c" | "--count", c, rest @ ..] => {
                count = c.parse().unwrap();
                args = rest;
            }
            [..] => break,
        }
    }

    let positional_args = args.iter().map(|s| s.to_string()).collect();

    Args {
        input,
        count,
        positional_args,
    }
}

struct Args {
    input: String,
    count: usize,
    positional_args: Vec<String>,
}

代码链接:playground

无可辩驳的模式匹配

尽管这不是切片模式特性的技术部分,你仍然可以使用模式匹配在matchif let状态之外,解构固定长度的数组。这对于索引总是有效的序列,能够很大程度地避免繁琐。

fn format_coordinates([x, y]: [f32; 2]) -> String {
    format!("{}|{}", x, y)
}

fn main() {
    let point = [3.14, -42.0];

    println!("{}", format_coordinates(point));

    let [x, y] = point;
    println!("x: {}, y: {}", x, y);
    // Much more ergonomic than writing this!
    // let x = point[0];
    // let y = point[1];
}

代码链接:playground

总结

就 Rust 中的诸多特性而言,切片模式在使用得当的情况下,并不算十分复杂,它们可以有效改善代码的表达力。