Rust模式探索:写出更优雅的Rust代码

2024-05-08 15:24:17 浏览数 (2)

模式定义

在Rust中,模式匹配是一种强大的编程工具,它允许你根据数据的结构来选择不同的执行路径。模式可以用在 match 表达式、if let 表达式、while let 表达式、函数参数、let 语句等地方

一个示例

来看个上一篇文章 Rust 枚举 简单入门 中例子

代码语言:javascript复制
enum Color {
    Red,
    Orange,
    Yellow
}

let c1 = Color::Red;

match c1 {
    Color::Red => println!("Red"),
    Color::Orange => println!("Orange"),
    Color::Yellow => println!("Yellow")
}

match 会执行模式匹配,在此示例中,模式就是出现在 => 符号前面的部分,模式匹配可以和枚举协同工作,甚至可以测试它们包含的数据

模式类型

上面的例子是匹配枚举值的模式。模式的类型不止于此,Rust 模式还有它们自己的小型语言,如下表

模式类型

例子

注意事项

字面量

100 "name"

匹配一个确切的值;也允许匹配常量名称

范围

0 ..= 100 'a' ..= 'k' 256..

匹配范围内的任何值,包括可能给定的结束值

通配符

_

匹配任何值并忽略它

变量

name mut count

类似于 _,但会把值移动或复制到新的局部变量中

引用变量

ref field ref mut field

借用对匹配值的引用,而不是移动或复制它

与子模式绑定

val @ 0 ..= 99 ref circle @ Shape::Circle { .. }

使用 @ 左边的变量名,匹配其右边的模式

枚举型模式

Some(value) None Pet::Orca

元组型模式

(key, value) (r, g, b)

数组型模式

[a, b, c, d, e, f, g] [heading, carom, correction]

切片型模式

[first, second] [first, _, third] [first, .., nth] [ ]

结构体型模式

Color(r, g, b) Point { x, y } Card { suit: Clubs, rank: n } Account { id, name, .. }

引用

&value &(k, v)

仅匹配引用值

或多个模式

'a' 竖线 'A' Some("left" 竖线 "right")

守卫表达式

x if x * x <= r2

只用在 match 表达式中(不能用在 let 语句等处)

注意! 由于当前页面的 Markdown 格式转换问题,竖线 | 会导致排版异常,因此上面表格使用中文 竖线 代替 |

字面量、变量、通配符

字面量可以是整数、浮点数、字符、字符串、布尔值等。下面是整数字面量的一个例子

代码语言:javascript复制
let x = 5;

match x {
    1 => println!("one"),
    2 => println!("two"),
    3 => println!("three"),
    _ => println!("something else"),
}

用单个下划线 _ 作为模式,这就是通配符模式,这里的通配符模式能匹配任意值,但不会将其存储到任何地方

即使你非常确定其他情况不会发生,也必须至少添加一个后备分支,也许是 panic 的分支

代码语言:javascript复制
let s = "hello";

match s {
    "hello" => println!("found hello"),
    other => println!("no match"),
}

这里面的 other 是一个变量名,它可以匹配任何值,匹配的值会移动或复制到一个新的局部变量中,这些模式类似 switch 语句中的 default 分支,用于匹配与任何其他模式都无法匹配的值

元组型和结构体型

元组模式是一种模式,用于匹配元组的结构。元组模式由一对圆括号和一组模式组成,模式之间用逗号分隔

代码语言:javascript复制
let x = (1, 2, 3);

match x {
    (1, 2, 3) => println!("one, two, three"),
    (a, b, c) => println!("({}, {}, {})", a, b, c),
}

结构体模式用于匹配结构体的结构。结构体模式由结构体的名称和一组模式组成,模式之间用逗号分隔

代码语言:javascript复制
struct Point {
    x: i32,
    y: i32,
}

let p = Point { x: 0, y: 7 };

match p {
    Point { x, y: 0 } => println!("x is {}, y is on the x axis", x),
    Point { x: 0, y } => println!("x is on the y axis, y is {}", y),
    Point { x, y } => println!("x is {}, y is {}", x, y),
}

当想要匹配一个大型结构体的一部分字段,而不是全部字段时,可以使用 .. 来表示剩余的字段。这被称为结构体模式的 .. 简写

代码语言:javascript复制
struct Point {
    x: i32,
    y: i32,
    z: i32,
    // ... 其他字段
}

let p = Point { x: 0, y: 7, z: 0 };

match p {
    Point { x, .. } => println!("x is {}", x),
}

match 表达式只关心 px 字段,而不关心 yz 字段。.. 表示剩余的字段,所以Point { x, .. }匹配任何 Point 结构体,只要它的 x 字段匹配

数组型和切片型

数组型模式匹配数组。数组型模式通常用于过滤一些特殊情况的值,并且在处理那些不同位置的值具有不同含义的数组时也非常有用

代码语言:javascript复制
let arr = [1, 2, 3];

match arr {
    [1, 2, 3] => println!("found 1, 2, 3"),
    _ => println!("no match"),
}

注意! 数组模式只能用于固定大小的数组,不能用于动态大小的数组(也就是切片)。如果你想要匹配一个切片的结构,你应该使用切片模式

切片型模式与数组型相似,但与数组不同,切片具有可变长度,因此切片型模式不仅匹配值,还匹配长度。.. 在切片型模式中能匹配任意数量的元素

代码语言:javascript复制
let arr = ["a"];

match &arr[..] {
    [] => println!("no body"),
    [a] => println!("a"),
    [a, b] => println!("a, b"),
    _ => println!("no match"),
}

引用型模式

引用型模式(Reference Patterns)允许你通过引用来匹配和解构数据,而不是通过值。这种模式在处理借用的数据时特别有用,因为它允许你在不获取所有权的情况下访问数据的部分或全部内容

基本用法

引用型模式通常与&符号一起使用,表示你正在匹配一个引用。当你想要在模式匹配中解构一个引用指向的值时,这非常有用,下面是个简单的例子

代码语言:javascript复制
let reference = &10;

match reference {
    &val => println!("val: {:?}", val)
}

reference是一个指向10的引用。在match表达式中,模式&val用于解构reference,允许直接访问它指向的值10

解构数据

引用型模式在解构复杂数据结构时尤其有用,比如元组或结构体

代码语言:javascript复制
let tuple = &(1, 2, 3);

match tuple {
    &(x, y, z) => println!("Matched: {}, {}, {}", x, y, z),
}
使用ref关键字

ref关键字用于创建一个引用指向模式匹配中的值,而不是通过值绑定

代码语言:javascript复制
let value = 5;
let ref reference = value;

match reference {
    &val => println!("val: {:?}", val),
}
ref和mut结合使用

ref mut可以用来匹配可变引用,并允许修改通过引用访问的数据

代码语言:javascript复制

let mut value = 5;

match value {
    ref mut r => {
        *r  = 10;
        println!("value: {}", r);
    }
}
注意!
  • 使用ref mut时,必须确保被引用的数据本身是可变的
  • 修改通过ref mut创建的引用所指向的数据时,需要使用解引用操作符*
  • 在模式匹配中使用ref和ref mut可以让你更灵活地处理数据,特别是在需要引用而不是所有权的场景中

匹配守卫

匹配守卫(match guards)是一种与模式匹配结合使用的条件表达式,它提供了额外的条件来决定是否应该选择某个分支。这使得模式匹配更加灵活,允许在模式本身无法表达的复杂情况下进行精细的控制

匹配守卫紧跟在模式之后,使用if关键字引入,如下例子

代码语言:javascript复制
let tuple = (5, 12);

match tuple {
    (5, y) if y > 10 => println!("第一个元素是5,且第二个元素大于10,y = {}", y),
    _ => println!("不匹配"),
}

在循环中使用匹配守卫

代码语言:javascript复制
let numbers = vec![Some(10), Some(11), Some(20), Some(30)];

for option in numbers.iter() {
    match option {
        Some(x) if *x > 10 => println!("大于10的数字为:{}", x),
        _ => (),
    }
}

匹配多种可能性

模式匹配(Pattern Matching)是一种强大的控制流工具,它不仅可以匹配单一的值,还可以同时匹配多种可能性。这通过使用|运算符来实现,|在这里表示“或”(or),允许在同一个match分支中指定多个模式

代码语言:javascript复制
let pair = (2, 3);
match pair {
    (x, y) if x == y => println!("数字一样"),
    (x, 0) | (0, x) => println!("x: {}", x),
    _ => println!("没有匹配")
}

使用@模式绑定

@模式绑定的基本语法是在模式中使用@后跟一个变量名,这样可以在模式匹配成功时,将匹配到的值绑定到这个变量

代码语言:javascript复制
enum Message {
    Move { x: i32, y: i32 },
    ChangeColor(i32, i32, i32)
}

let move_message = Message::Move { x: 3, y: 104 };

match  move_message {
    Message::Move { x: x_pos @ 0..=100, y: y_pos @ 101..=200 } => {
        println!("移出({}, {})", x_pos, y_pos)
    },
    _ => println!("other")
}

模式能用在哪里

尽管模式在 match 表达式中作用最为突出,但它们也可以出现在其他一些地方,通常用于代替标识符。但无论出现在哪里,其含义都是一样的:Rust 不是要将值存储到单个变量中,而是使用模式匹配来拆分值

代码语言:javascript复制
// 把结构体解包成3个局部变量……
let Track { album, track_number, title, .. } = song;

// ……解包某个作为函数参数传入的元组
fn distance_to((x, y): (f64, f64)) -> f64 { ... }

// ……迭代某个HashMap上的键和值
for (id, document) in &cache_map {
    println!("Document #{}: {}", id, document.title);
}

上述示例中的每一个都节省了两三行样板代码。同样的概念也存在于其他一些语言中:JavaScript 中叫作解构,而 Python 中叫作解包

看到这里,有没有越看越顺的感觉?

0 人点赞