< 返回版块

洋芋 发表于 2020-01-23 14:26

Tags:rust, 每周一知

对于Rust v1.42.0中的高级切片模式(advanced slice patterns),开发者Thomas Hartmann写了一篇博客文章,总结了我们会从中得到什么以及为什么他认为这很重要。本文是我对原文的翻译,同时增加了一些示例。

关于切片模式(slice patterns)

一直以来,在稳定版Rust上我们已使用了某些形式的切片匹配,但是如果没有高级切片模式这个功能,可以进行切片匹配的形式相当有限。使用已知长度的数组,可以根据需要进行解构和匹配,但是对于未知长度的切片,必须提供一个备选项,因为无法覆盖匹配表达式中所有可能的情况。同样,非常重要的是:没有办法将变量绑定到子切片(subslice)。高级切片模式功能最终打开了子切片和子数组(subarray)匹配的大门,从而减轻了上述两个问题,并使切片模式变得更加强大。

例如,我们想要接受一个名单列表并回复问候语。使用切片模式,对于未知长度的切片,必须提供一个备选项(_):

fn main() {
    greet(&[]);
    // output: Bummer, there's no one here :(
    greet(&["Alan"]);
    // output: Hey, there Alan! You seem to be alone.
    greet(&["Joan", "Hugh"]);
    // output: Hello, Joan and Hugh. Nice to see you are at least 2!
    greet(&["John", "Peter", "Stewart"]);
    // output: Hey everyone, we seem to be 3 here today.
}

fn greet(people: &[&str]) {
    match people {
        [] => println!("Bummer, there's no one here :("),
        [only_one] => println!("Hey, there {}! You seem to be alone.", only_one),
        [first, second] => println!(
            "Hello, {} and {}. Nice to see you are at least 2!",
            first, second
        ),
        _ => println!("Hey everyone, we seem to be {} here today.", people.len()),
    }
}

匹配数组如下:

let arr = [1, 2, 3];

assert_eq!("ends with 3", match arr {
    [_, _, 3] => "ends with 3",
    [a, b, c] => "ends with something else",
});

以上示例来自《Rust版本指南》

两种风格

新的子切片模式有两种语法风格:一种用于当要将子切片绑定到变量时,另一种用于当只想表示存在省略的元素时。两种风格都使用..模式(称为rest pattern)来匹配可变数量的元素。匹配的元素数取决于数组或切片的长度以及匹配之前和之后的匹配元素数。

非绑定匹配

看第一种风格,非绑定匹配,我们直接介绍..模式:

fn f<T>(xs: &[T])
where
    T: std::fmt::Debug,
{
    match xs {
        // 切片至少具有两个变量。
        // 我们将切片的第一项和最后一项分别绑定到x和y
        [x, .., y] => {
            println!("First and last: {:?} and {:?}.", x, y)
        }

        // 切片仅有单项:x
        [x] => {
          println!("the slice has a single item: {:?}.", x)
        }

        // 切片是空的
        [] => {
            println!("Got an empty slice.")
        }
    }
}

记住..可以匹配任意数量的元素,包括0。这意味着示例中第一个模式匹配具有至少两项的任何数量的元素。还可以在两端不“定界”的情况下使用模式,例如,如果要实现获取切片的第一个和最后一个元素这两个功能,则:

fn first<T>(xs: &[T]) -> Option<&T> {
    match xs {
        [x, ..] => Some(x),
        [] => None,
    }
}

fn last<T>(xs: &[T]) -> Option<&T> {
    match xs {
        [.., x] => Some(x),
        [] => None,
    }
}

注意这两个函数如何挑选切片的单个元素(分别为第一个和最后一个),而忽略其余元素。因为..匹配0个或多个元素,所以两个函数中的第一个模式都将匹配具有一个或多个元素的切片。

匹配并绑定子切片

另一种风格可以将子切片绑定到一个值,该值采用切片模式。绑定是通过@运算符完成的。

例如,想象一下我们要编写一个求和函数sum。可以这样做:

fn sum(xs: &[i32]) -> i32 {
    match xs {
        [] => 0,
        [x, xs @ ..] => x + sum(xs),
    }
}

在上面的示例中,如果切片不为空,则采用第一个元素x,并将其添加到与列表xs其余部分相加的结果中。由于Rust在迭代器(iterators)上已经具有sum方法,因此此函数是非常多余的,但它是如何绑定和使用子切片的一个很好的示例。

另一个示例是,如果切片的元素数量为奇数,则获取切片的中间元素。如果切片为空或元素数为偶数,则返回None

fn middle<T>(xs: &[T]) -> Option<&T> {
    match xs {
        // 忽略第一个和最后一个元素。
        // 递归它们之间的元素。
        [_, inner @ .., _] => middle(inner),

        // 一个元素则返回它!
        [x] => Some(x),

        // 什么都没有。
        [] => None,
    }
}

在上面的示例中,我们从两侧迭代遍历切片,持续地忽略起点处和终点处元素,中间剩下的任何元素(如果至少有两个元素)都分配给xs,并用作该函数另一步的输入。一旦我们剩下一个或零个元素,我们就会得到答案。

为什么这很重要

我对这个看似很小的功能很感兴趣,可能有点奇怪,但这是我自己一直认可的生活品质之一。习惯了Haskell及其模式匹配行为后,我经常忘记在Rust中对任意切片进行匹配有多么麻烦。到目前为止,我们在切片上使用了split_first方法(和split_at),我经常不记得它的名称,它返回一个Option,并且不允许进行任意匹配(例如使用匹配保护)。在这方面,新的slice_patterns功能是重要的一步。

我非常着迷的另一件事是?能够在切片结尾匹配。不仅可以从切片的任一端获取元素,还可以确保切片以某个值或一系列值结尾。

简而言之,我认为这是稳定Rust的绝佳补充。向所有使之成为可能的人们致敬。现在,请阅读RFC并查看他们正在谈论的所有其他有趣的内容(任意嵌套的OR模式?)。

原博客文章链接:https://thomashartmann.dev/blog/feature(slice_patterns)/

评论区

写评论

还没有评论

1 共 0 条评论, 1 页