第N次入门Rust - 15.再谈模式匹配


前言

在前面的文章 第N次入门Rust - 5.枚举和模式匹配 中,已简单介绍了模式匹配的一些简单用法。这一篇将继续讨论模式匹配在Rust中的使用,有一部分提到的语法还没用过,先记下来~


15.1 模式匹配简介

模式匹配(Pattern Match)通常指的是通过特殊的语法或者规则从一堆数据中心找出符合规则的目标数据。

在Rust中模式是Rust中的一种特殊语法,用于匹配复杂和简单类型的结构。

在前面的文章中最常用模式匹配的地方是match表达式,通过匹配值的类型执行不同的分支逻辑,这在Java(这里指Java1.8,后续版本暂时没了解,毕竟版本任你发,我用Java8 @の@)中是不容易实现的。Java8中的match表达式只能根据取值来选择分支逻辑,要实现基于类型选择分支,只能借助instanceof运算符,且具体实现效果没有Rust那么强大。

模式通常由以下项组成:

  • 字面值
  • 解构的数组、枚举、结构体或者元组
  • 变量
  • 通配符
  • 占位符

在下列的说明中:

  • VALUE是参与模式匹配的值;
  • PATTERN可以是下列的语法:
    • pattern:只匹配模式pattern,不提取值VALUE中的信息;
    • pattern(val):当值VALUE匹配模式pattern,就将VALUE中的信息提取出来放入val中,且在之后的上下文中可以使用val。注意,这个pattern除了可以是一般类型以外,也可以是结构体或元组。如果是结构体或元组,则可以精确到每个位置或者某些位置的字段是什么类型或者取值,并提取出某些字段的取值。
  • EXPRESSION是匹配成功后执行的表达式;

Rust模式匹配流程伪代码如下:

def match_and_get(PATTERN, VALUE):
	if PATTERN is pattern形式语法:
		if VALUE 匹配 PATTERN模式:
			return (true, None)
		else:
			return (false, None)
	else if PATTERN is pattern(val)形式语法:
		if VALUE 匹配 PATTERN模式:
			val = 从VALUE中获取到的值
			return (true, val)
		else:
			return (false, None)

def execute(PATTERN, VALUE, EXPRESSION):
	is_match, val = match_and_get(PATTERN, VALUE)
	if is_match:
		EXPRESSION(val)
		return true
	else:
		return false

15.2 使用模式匹配的场景

15.2.1 match表达式

match VALUE {
    PATTERN => EXPRESSION,
    PATTERN => EXPRESSION,
    PATTERN => EXPRESSION,
    _ => EXPRESSION,
}
  • match 表达式必须是 **穷尽(exhaustive)**的,意为 match 表达式所有可能的值都必须被考虑到。
  • 一个特殊的模式:_(下划线),会匹配任何东西:
    • 它不会绑定任何变量;
    • 通常用于match的最后一个arm,或用于忽略某些值;

15.2.2 if let条件表达式

  • if let 表达式等同于只关心一种情况的 match 语句的简写形式。
  • if let 可以对应一个可选的带有代码的 elseif let 中的模式不匹配时运行。
if let PATTERN = VALUE {
    /* ... */
} else {
	/* ... */
}

15.2.3 while let 条件循环

while let PATTERN = VALUE {
	/* ... */    
}
  • 只要VALUE满足模式PATTERN,就一直执行循环。
  • 通常会将VALUE中的值提取出来到val中,在循环体内使用。

15.1.4 for 循环

for PATTERN in VALUE {
    /* ... */
}

15.1.5 let 语句

let语句也可以使用模式匹配。

let PATTERN = EXPRESSION;

例子:

let x = 10;
let (x, y, z) = (1, 2, 3);
let Point{x, y} = Point::new(10, 20);

15.1.6 函数参数

fn foo(PATTERN) {
    /* ... */
}

foo(VALUE)

例子:

// x实际上就是一个模式
fn foo(x: i32) { /* ... */ }

fn foo(&(x, y): &(i32, i32)) { /* ... */ }
foo(&(3, 5))

15.2 可反驳性模式和不可反驳性模式

  • 模式有两种形式:refutable(可反驳的)和 irrefutable(不可反驳的)。
  • 可反驳的模式:代码逻辑允许模式不匹配,如果模式不匹配则不执行相关逻辑。 可反驳的模式匹配通常涉及条件。检查一个已有的值是否匹配模式,如果匹配,则直接执行一段逻辑,或者先从中抽取想要的值,然后执行一段逻辑。
  • 不可反驳的模式:代码逻辑必须匹配模式,如果模式不匹配则编译错误。 不可反驳的模式通常这涉及赋值,即从一个已有的值中抽取出想要的值。
用到模式的地方类型
if let可反驳的模式
while let可反驳的模式
match不可反驳的模式
函数参数不可反驳的模式
let 语句不可反驳的模式
for 循环不可反驳的模式

15.3 所有模式语法

15.3.1 匹配字面值

匹配字面量的意思是模式本身是一个字面量,这种语法类似于Java的Switch,起到的是分支作用。

let x = 1;

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

15.3.2 匹配命名变量

匹配命名变量,是不可反驳模式。在值匹配模式后,将值复制到另一个新的变量上,这个变量只能在匹配成功的逻辑分支中使用。

fn main() {
    let x = Some(5);
    let y = 10;

    match x {
        Some(50) => println!("Got 50"),                 // 匹配字面量
        Some(y) => println!("Matched, y = {:?}", y),    // 匹配命名变量,y的作用域是match内
        _ => println!("Default case, x = {:?}", x),
    }

    println!("at the end: x = {:?}, y = {:?}", x, y);
}

15.3.3 匹配多个模式

  • 与正则表达式的匹配多个模式中的一个类似,通常用于多个模式对应同一个逻辑分支。
  • 使用|语法匹配多个模式,or的意思。
let x = 1;

match x {
    1 | 2 => println!("one or two"),    // 匹配1或者2
    3 => println!("three"),
    _ => println!("anything"),
}

15.3.4 匹配区间范围内的值

  • 使用双目运算符..=
  • 模式 a..=b 意思为数学上的左闭右闭区间[a,b]
  • 也等价于 a | a+1 | a+2 | ... | b
  • 运算符接受的类型除了数字还可以是char值。
let x = 5;

match x {
    1..=5 => println!("one through five"),
    _ => println!("something else"),
}

15.3.5 解构

  • 解构含义为从匹配模式的给定值中提取出提取出对应的字段值。
  • 解构分为两步:
    1. 模式匹配
    2. 提取字段值
  • 可以解构的对象:结构体、枚举、元组、引用。

解构结构体

struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let p = Point { x: 0, y: 7 };

    let Point { x: a, y: b } = p;   // a = 0; b = 7;
    let Point { x: 0, y: c}  = p;   // c = 7;
    let Point { x: d, y: 7} = p;    // d = 0;
}

解构枚举

enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
}

fn main() {
    let msg = Message::ChangeColor(0, 160, 255);

    match msg {
        Message::Quit => {
            println!("The Quit variant has no data to destructure.")
        }
        Message::Move { x, y } => {
            println!(
                "Move in the x direction {} and in the y direction {}",
                x,
                y
            );
        }
        Message::Write(text) => println!("Text message: {}", text),
        Message::ChangeColor(r, g, b) => {
            println!(
                "Change the color to red {}, green {}, and blue {}",
                r,
                g,
                b
            )
        }
    }
}

解构嵌套的结构体和枚举

enum Color {
   Rgb(i32, i32, i32),
   Hsv(i32, i32, i32),
}

enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(Color),
}

fn main() {
    let msg = Message::ChangeColor(Color::Hsv(0, 160, 255));

    match msg {
        Message::ChangeColor(Color::Rgb(r, g, b)) => {
            println!(
                "Change the color to red {}, green {}, and blue {}",
                r,
                g,
                b
            )
        }
        Message::ChangeColor(Color::Hsv(h, s, v)) => {
            println!(
                "Change the color to hue {}, saturation {}, and value {}",
                h,
                s,
                v
            )
        }
        _ => ()
    }
}

解构结构体和元组

let ((feet, inches), Point {x, y}) = ((3, 10), Point { x: 3, y: -10 });

15.3.5 匹配时忽略模式中的值

使用 _ 忽略部分值或者整个值

下划线_用于模式中时,作用为忽略给定值中对应位置的值。

let a = (1, 2);

// 忽略部分值(第一个字段的值)
if let (_, x) = a {
    /* ... */
}
// 忽略整个值(第一个参数的值),虽然这种语法不太推荐使用,建议把第一个参数删掉
fn run(_: i32, b: i32) {
    /* ... */
}
let x = 10;
match x {
    10 => /* ... */
    _ = > /* 非10时的逻辑 */
}

通过在名字前以一个下划线开头来忽略未使用的变量

  • 通常存在未使用变量编译器会给出一个警告,认为这里是一个bug,但是有时创建一个还未使用的变量是有用的,比如会在后续才用到这个值等,这是可以在变量名前加一个下划线_可以忽略掉未使用警告,但是变量还是会正常声明。
  • 使用__变量名两种语法是不一样的:前者值没有被绑定,后者还是会发生值的绑定。
fn main() {
    let _x = 5;
    let y = 10;
}

.. 忽略剩余值

  • ..忽略剩余值的语法可以看成是_忽略值的一个语法糖,通常使用_忽略模式中的值往往是需要忽略的值比较少的使用才会用到,当需要忽略的值非常多,想要提取的值比较少时,使用..忽略会更方便。

  • 只提取结构体中少数字段的值:

    struct Point {
        x: i32,
        y: i32,
        z: i32,
    }
    
    let origin = Point { x: 0, y: 0, z: 0 };
    
    match origin {
        Point { x, .. } => println!("x is {}", x),
    }
    
  • 只提取元组中首尾的值:

    fn main() {
        let numbers = (2, 4, 8, 16, 32);
    
        match numbers {
            (first, .., last) => {
                println!("Some numbers: {}, {}", first, last);
            },
        }
    }
    
  • 注意:使用..忽略剩余值必须没有歧义,下面的例子就是有歧义的,无法通过编译:

    fn main() {
        let numbers = (2, 4, 8, 16, 32);
    
        match numbers {
            (.., second, ..) => {
                println!("Some numbers: {}", second)
            },
        }
    }
    

15.3.7 匹配守卫提供条件判断

  • 匹配守卫(match guard) 是一个指定于 match 分支模式之后的额外 if 条件,它也必须被满足才能选择此分支。匹配守卫用于表达比单独的模式所能允许的更为复杂的情况。
  • 带有匹配守卫的模式匹配逻辑如下:
    1. 匹配给定值是否满足模式A;
    2. 如果不满足模式A,开始匹配下一个模式;
    3. 如果满足模式A,从给定值的提取出值val
    4. 执行匹配守卫的条件判断语句,其中val可以在判断语句中使用;
    5. 如果没有通过匹配守卫的条件判断,开始匹配下一个模式;
    6. 如果通过匹配守卫的条件判断,进入对应的分支,分支内val可以被使用;
  • 语法:
    match VALUE {
        PATTERN1 if cond1 => /* ... */
        PATTERN2 if cond2 => /* ... */
        PATTERN3 => /* ... */
        _ => /* ... */
    }
    
let num = Some(4);

match num {
    Some(x) if x < 5 => println!("less than five: {}", x),
    Some(x) => println!("{}", x),
    None => (),
}

15.3.8 at 运算符(@) 绑定

  • at 运算符(@)允许开发者在创建一个存放值的变量的同时测试其值是否匹配模式。
enum Message {
    Hello { id: i32 },
}

let msg = Message::Hello { id: 5 };

match msg {
    // 匹配结构体,并将id字段的值提取出来到id_variable中,然后判断是否属于[3,7]
    Message::Hello { id: id_variable @ 3..=7 } => {
        println!("Found an id in range: {}", id_variable)
    },
    Message::Hello { id: 10..=12 } => {
        println!("Found an id in another range")
    },
    Message::Hello { id } => {
        println!("Found some other id: {}", id)
    },
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要配置rust-analyzer的代码提示,你可以按照以下步骤进行操作: 1. 确保已经在你的项目中安装了rust-analyzer插件。你可以在VSCode的插件市场中搜索并安装"rust-analyzer"插件。 2. 打开VSCode的设置(可以通过菜单栏的"文件" -> "首选项" -> "设置"或者使用快捷键Ctrl + ,打开设置)。 3. 在设置页面的搜索框中输入"rust-analyzer",找到相关的设置选项。 4. 根据你的需求,配置下列常用的代码提示相关的设置: - "rust-analyzer.enable": 设置为true以启用rust-analyzer插件。 - "rust-analyzer.completion.enable": 设置为true以启用代码补全功能。 - "rust-analyzer.completion.addCallArgumentSnippets": 设置为true以自动添加函数调用时的参数提示。 - "rust-analyzer.completion.addCallParenthesis": 设置为true以自动添加函数调用时的括号。 - "rust-analyzer.completion.postfix.enable": 设置为true以启用后缀代码补全功能,例如`.if`、`.let`等。 - "rust-analyzer.hover.enable": 设置为true以启用悬停提示功能。 - "rust-analyzer.inlayHints.enable": 设置为true以启用内联提示功能。 5. 根据你的需求,可以进一步自定义配置rust-analyzer的代码提示行为。你可以在设置中找到更多相关的选项,并根据注释进行配置。 6. 保存设置,并重启VSCode使更改生效。 通过以上步骤,你可以根据自己的喜好和需求来配置rust-analyzer的代码提示功能。请注意,具体的配置选项可能会因rust-analyzer插件版本的不同而有所差异,请参考插件的官方文档或参考其它资源获取更多定制化的配置信息。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值