匹配表达式 match
match 表达式与 C/C++ 中的 switch/case 语句类似, 用于匹配多个分支条件.
它的语法如下所示:
match value {
pattern1 => expr1,
pattern2 => expr2,
...
}
注意上面每个分支匹配表达式是以逗号 ,
结尾的, 如果该表达式是块表达式 (block expression), 这个逗号就可以省略不写.
match 语句中的匹配优先级是根据分支顺序来确定的, 即优先检查第一条分支条件 pattern1
是否匹配, 如果匹配则执行 expr1
表达式, 并跳过剩下的所有分支. Rust 要求这些分支中必须有一条被匹配成功, 比如, 如果枚举中的条目没有被完全匹配到的话,
编译器就会报错:
#![allow(dead_code)] #[derive(Debug, Clone, Copy, Eq, PartialEq)] enum Weekday { Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday, } fn main() { let date = Weekday::Friday; let to_weekend: i32 = match date { Weekday::Monday => 5, Weekday::Tuesday => 4, Weekday::Wednesday => 3, Weekday::Thursday => 2, Weekday::Friday => 1, Weekday::Saturday => 0, // 把下面这一行代码注释掉并重新编译, 查看编译器报错信息. // 它编译失败, 并提示: // ^^^^ pattern `Weekday::Sunday` not covered Weekday::Sunday => 0, }; assert_eq!(to_weekend, 1); }
上面的代码中, 最后两条可以合并在一起, 作为一个分支; 或者使用通配符匹配.
match 只能在最后一个分支使用通配符匹配 (wildcard pattern), 表示无条件匹配这个分支,
类似于 C 语言 switch/case 中的 default
. 先看一个简单的用例:
use std::env; fn main() { use std::env; fn main() { let num = env::args().len(); let accum = match num { 1 => 100, 2 => 200, 3 => 300, _ => 0, }; assert_eq!(accum, 100); }
用 C 语言来描述相同的功能, 大概如下:
#include <assert.h>
int main(int argc, char** argv) {
int accum;
switch (argc) {
case 1: {
accum = 100;
break;
}
case 2: {
accum = 200;
break;
}
case 3: {
accum = 300;
break;
}
default: {
accum = 0;
}
}
assert(accum == 100);
return 0;
}
后文有单独的章节介绍 模式匹配 更多功能和写法.
跳转表 Jump Table
跳转表 Jump Table, 又称作 分支表 Branch Table, 是对分支语句的一种优化手段.
下面的代码用于本次验证:
use std::env; fn with_if_else() { let num = env::args().len(); let accum = if num == 1 { 100 } else if num == 2 { 200 } else if num == 3 { 300 } else if num == 4 { 400 } else if num == 5 { 500 } else if num == 6 { 600 } else { 0 }; assert_eq!(accum, 100); } fn with_match_short() { let num = env::args().len(); let accum = match num { 1 => 100, 2 => 200, 3 => 300, _ => 0, }; assert_eq!(accum, 100); } // 在 x86_64 (AMD Ryzen 5) 上超过 4 个分支后, 才会构造 jump table. // 在 aarch64 (Rock 3A) 上面也可以发现, 当超过 4 个分支后, 会构造 jump table. fn with_match_long() { let num = env::args().len(); let accum = match num { 1 => 100, 2 => 200, 3 => 300, 4 => 400, _ => 0, }; assert_eq!(accum, 100); } fn main() { with_if_else(); with_match_short(); with_match_long(); }
是上面的是 with_if_else()
函数, 它里面的分支语句比较多,
使用 rustc --emit asm jump-table.rs
命令生成汇编代码, 生成的部分 x86_64 汇编代码如下:
// with_if_else()
.section .text._ZN10jump_table12with_if_else17hc2cb507cd4512507E,"ax",@progbits
.p2align 4, 0x90
.type _ZN10jump_table12with_if_else17hc2cb507cd4512507E,@function
_ZN10jump_table12with_if_else17hc2cb507cd4512507E:
.Lfunc_begin4:
.cfi_startproc
.cfi_personality 155, DW.ref.rust_eh_personality
.cfi_lsda 27, .Lexception4
subq $168, %rsp
.cfi_def_cfa_offset 176
movq _ZN3std3env4args17h377a659c89f76567E@GOTPCREL(%rip), %rax
leaq 40(%rsp), %rdi
movq %rdi, 24(%rsp)
callq *%rax
movq 24(%rsp), %rdi
.Ltmp23:
movq _ZN84_$LT$std..env..Args$u20$as$u20$core..iter..traits..exact_size..ExactSizeIterator$GT$3len17hfc397728d7a27a41E@GOTPCREL(%rip), %rax
callq *%rax
.Ltmp24:
movq %rax, 32(%rsp)
jmp .LBB47_3
.LBB47_1:
.Ltmp26:
leaq 40(%rsp), %rdi
callq _ZN4core3ptr35drop_in_place$LT$std..env..Args$GT$17h27567071cfad13afE
.Ltmp27:
jmp .LBB47_20
.LBB47_2:
.Ltmp25:
movq %rax, %rcx
movl %edx, %eax
movq %rcx, 152(%rsp)
movl %eax, 160(%rsp)
jmp .LBB47_1
.LBB47_3:
leaq 40(%rsp), %rdi
callq _ZN4core3ptr35drop_in_place$LT$std..env..Args$GT$17h27567071cfad13afE
movq 32(%rsp), %rax
cmpq $1, %rax
jne .LBB47_5
movl $100, 76(%rsp)
jmp .LBB47_6
.LBB47_5:
movq 32(%rsp), %rax
cmpq $2, %rax
je .LBB47_7
jmp .LBB47_8
.LBB47_6:
leaq 76(%rsp), %rax
movq %rax, 80(%rsp)
leaq .L__unnamed_10(%rip), %rax
movq %rax, 88(%rsp)
movq 80(%rsp), %rax
movq %rax, 8(%rsp)
movq 88(%rsp), %rcx
movq %rcx, 16(%rsp)
movl (%rax), %eax
cmpl (%rcx), %eax
je .LBB47_18
jmp .LBB47_17
.LBB47_7:
movl $200, 76(%rsp)
jmp .LBB47_6
.LBB47_8:
movq 32(%rsp), %rax
cmpq $3, %rax
jne .LBB47_10
movl $300, 76(%rsp)
jmp .LBB47_6
.LBB47_10:
movq 32(%rsp), %rax
cmpq $4, %rax
jne .LBB47_12
movl $400, 76(%rsp)
jmp .LBB47_6
.LBB47_12:
movq 32(%rsp), %rax
cmpq $5, %rax
jne .LBB47_14
movl $500, 76(%rsp)
jmp .LBB47_6
.LBB47_14:
movq 32(%rsp), %rax
cmpq $6, %rax
jne .LBB47_16
movl $600, 76(%rsp)
jmp .LBB47_6
.LBB47_16:
movl $0, 76(%rsp)
jmp .LBB47_6
.LBB47_17:
movq 16(%rsp), %rdx
movq 8(%rsp), %rsi
movb $0, 103(%rsp)
movq $0, 104(%rsp)
movzbl 103(%rsp), %edi
leaq .L__unnamed_11(%rip), %r8
leaq 104(%rsp), %rcx
callq _ZN4core9panicking13assert_failed17h884f0f31899bb549E
.LBB47_18:
addq $168, %rsp
.cfi_def_cfa_offset 8
retq
.LBB47_19:
.cfi_def_cfa_offset 176
.Ltmp28:
movq _ZN4core9panicking16panic_in_cleanup17hc8e2b17e1b6d1381E@GOTPCREL(%rip), %rax
callq *%rax
.LBB47_20:
movq 152(%rsp), %rdi
callq _Unwind_Resume@PLT
.Lfunc_end47:
.size _ZN10jump_table12with_if_else17hc2cb507cd4512507E, .Lfunc_end47-_ZN10jump_table12with_if_else17hc2cb507cd4512507E
.cfi_endproc
.section .gcc_except_table._ZN10jump_table12with_if_else17hc2cb507cd4512507E,"a",@progbits
.p2align 2, 0x0
可以看到, with_if_else()
函数, 使用 if/else
语句判断 num
变量时, 使用多次跳转才能匹配到
else
分支, 跳转次数越多, CPU 执行指令的效率越低.
接下来看 with_match_short()
函数, 它内部使用了 match 表达式来匹配 num
的值,
生成的汇编代码片段如下:
// with_match_short()
.section .text._ZN10jump_table16with_match_short17h5f66f4b7024a913fE,"ax",@progbits
.p2align 4, 0x90
.type _ZN10jump_table16with_match_short17h5f66f4b7024a913fE,@function
_ZN10jump_table16with_match_short17h5f66f4b7024a913fE:
.Lfunc_begin5:
.cfi_startproc
.cfi_personality 155, DW.ref.rust_eh_personality
.cfi_lsda 27, .Lexception5
subq $168, %rsp
.cfi_def_cfa_offset 176
movq _ZN3std3env4args17h377a659c89f76567E@GOTPCREL(%rip), %rax
leaq 40(%rsp), %rdi
movq %rdi, 24(%rsp)
callq *%rax
movq 24(%rsp), %rdi
.Ltmp29:
movq _ZN84_$LT$std..env..Args$u20$as$u20$core..iter..traits..exact_size..ExactSizeIterator$GT$3len17hfc397728d7a27a41E@GOTPCREL(%rip), %rax
callq *%rax
.Ltmp30:
movq %rax, 32(%rsp)
jmp .LBB48_3
.LBB48_1:
.Ltmp32:
leaq 40(%rsp), %rdi
callq _ZN4core3ptr35drop_in_place$LT$std..env..Args$GT$17h27567071cfad13afE
.Ltmp33:
jmp .LBB48_12
.LBB48_2:
.Ltmp31:
movq %rax, %rcx
movl %edx, %eax
movq %rcx, 152(%rsp)
movl %eax, 160(%rsp)
jmp .LBB48_1
.LBB48_3:
leaq 40(%rsp), %rdi
callq _ZN4core3ptr35drop_in_place$LT$std..env..Args$GT$17h27567071cfad13afE
movq 32(%rsp), %rax
subq $1, %rax
je .LBB48_5
jmp .LBB48_13
.LBB48_13:
movq 32(%rsp), %rax
subq $2, %rax
je .LBB48_6
jmp .LBB48_14
.LBB48_14:
movq 32(%rsp), %rax
subq $3, %rax
je .LBB48_7
jmp .LBB48_4
.LBB48_4:
movl $0, 76(%rsp)
jmp .LBB48_8
.LBB48_5:
movl $100, 76(%rsp)
jmp .LBB48_8
.LBB48_6:
movl $200, 76(%rsp)
jmp .LBB48_8
.LBB48_7:
movl $300, 76(%rsp)
.LBB48_8:
leaq 76(%rsp), %rax
movq %rax, 80(%rsp)
leaq .L__unnamed_10(%rip), %rax
movq %rax, 88(%rsp)
movq 80(%rsp), %rax
movq %rax, 8(%rsp)
movq 88(%rsp), %rcx
movq %rcx, 16(%rsp)
movl (%rax), %eax
cmpl (%rcx), %eax
je .LBB48_10
movq 16(%rsp), %rdx
movq 8(%rsp), %rsi
movb $0, 103(%rsp)
movq $0, 104(%rsp)
movzbl 103(%rsp), %edi
leaq .L__unnamed_12(%rip), %r8
leaq 104(%rsp), %rcx
callq _ZN4core9panicking13assert_failed17h884f0f31899bb549E
.LBB48_10:
addq $168, %rsp
.cfi_def_cfa_offset 8
retq
.LBB48_11:
.cfi_def_cfa_offset 176
.Ltmp34:
movq _ZN4core9panicking16panic_in_cleanup17hc8e2b17e1b6d1381E@GOTPCREL(%rip), %rax
callq *%rax
.LBB48_12:
movq 152(%rsp), %rdi
callq _Unwind_Resume@PLT
.Lfunc_end48:
.size _ZN10jump_table16with_match_short17h5f66f4b7024a913fE, .Lfunc_end48-_ZN10jump_table16with_match_short17h5f66f4b7024a913fE
.cfi_endproc
.section .gcc_except_table._ZN10jump_table16with_match_short17h5f66f4b7024a913fE,"a",@progbits
.p2align 2, 0x0
从上面的汇编代码可以看到, 汇编器并没有生成跳转表, 也都只是一些条件判断语句,
需要多次判断用跳转才能到达最后一个分支.
但这部分代码要比 with_if_else()
的汇编代码更简洁, 执行效率也会更高.
只有分支语句达到某个限制时, 汇编器才会生成跳转表; 在 x86_64 上, 这个分支个数是4.
接下来看 with_match_long()
函数的汇编代码, 它就被构造出了跳转表:
// with_match_long()
.section .text._ZN10jump_table15with_match_long17hf93574d4242b2d97E,"ax",@progbits
.p2align 4, 0x90
.type _ZN10jump_table15with_match_long17hf93574d4242b2d97E,@function
_ZN10jump_table15with_match_long17hf93574d4242b2d97E:
.Lfunc_begin6:
.cfi_startproc
.cfi_personality 155, DW.ref.rust_eh_personality
.cfi_lsda 27, .Lexception6
subq $168, %rsp
.cfi_def_cfa_offset 176
movq _ZN3std3env4args17h377a659c89f76567E@GOTPCREL(%rip), %rax
leaq 40(%rsp), %rdi
movq %rdi, 24(%rsp)
callq *%rax
movq 24(%rsp), %rdi
.Ltmp35:
movq _ZN84_$LT$std..env..Args$u20$as$u20$core..iter..traits..exact_size..ExactSizeIterator$GT$3len17hfc397728d7a27a41E@GOTPCREL(%rip), %rax
callq *%rax
.Ltmp36:
movq %rax, 32(%rsp)
jmp .LBB49_3
.LBB49_1:
.Ltmp38:
leaq 40(%rsp), %rdi
callq _ZN4core3ptr35drop_in_place$LT$std..env..Args$GT$17h27567071cfad13afE
.Ltmp39:
jmp .LBB49_13
.LBB49_2:
.Ltmp37:
movq %rax, %rcx
movl %edx, %eax
movq %rcx, 152(%rsp)
movl %eax, 160(%rsp)
jmp .LBB49_1
.LBB49_3:
leaq 40(%rsp), %rdi
callq _ZN4core3ptr35drop_in_place$LT$std..env..Args$GT$17h27567071cfad13afE
movq 32(%rsp), %rax
decq %rax
movq %rax, 16(%rsp)
subq $3, %rax
ja .LBB49_4
movq 16(%rsp), %rax
leaq .LJTI49_0(%rip), %rcx
movslq (%rcx,%rax,4), %rax
addq %rcx, %rax
jmpq *%rax
.LBB49_4:
movl $0, 76(%rsp)
jmp .LBB49_9
.LBB49_5:
movl $100, 76(%rsp)
jmp .LBB49_9
.LBB49_6:
movl $200, 76(%rsp)
jmp .LBB49_9
.LBB49_7:
movl $300, 76(%rsp)
jmp .LBB49_9
.LBB49_8:
movl $400, 76(%rsp)
.LBB49_9:
leaq 76(%rsp), %rax
movq %rax, 80(%rsp)
leaq .L__unnamed_10(%rip), %rax
movq %rax, 88(%rsp)
movq 80(%rsp), %rax
movq %rax, (%rsp)
movq 88(%rsp), %rcx
movq %rcx, 8(%rsp)
movl (%rax), %eax
cmpl (%rcx), %eax
je .LBB49_11
movq 8(%rsp), %rdx
movq (%rsp), %rsi
movb $0, 103(%rsp)
movq $0, 104(%rsp)
movzbl 103(%rsp), %edi
leaq .L__unnamed_13(%rip), %r8
leaq 104(%rsp), %rcx
callq _ZN4core9panicking13assert_failed17h884f0f31899bb549E
.LBB49_11:
addq $168, %rsp
.cfi_def_cfa_offset 8
retq
.LBB49_12:
.cfi_def_cfa_offset 176
.Ltmp40:
movq _ZN4core9panicking16panic_in_cleanup17hc8e2b17e1b6d1381E@GOTPCREL(%rip), %rax
callq *%rax
.LBB49_13:
movq 152(%rsp), %rdi
callq _Unwind_Resume@PLT
.Lfunc_end49:
.size _ZN10jump_table15with_match_long17hf93574d4242b2d97E, .Lfunc_end49-_ZN10jump_table15with_match_long17hf93574d4242b2d97E
.cfi_endproc
// 定义的跳转表
.section .rodata._ZN10jump_table15with_match_long17hf93574d4242b2d97E,"a",@progbits
.p2align 2, 0x0
.LJTI49_0:
.long .LBB49_5-.LJTI49_0
.long .LBB49_6-.LJTI49_0
.long .LBB49_7-.LJTI49_0
.long .LBB49_8-.LJTI49_0
.section .gcc_except_table._ZN10jump_table15with_match_long17hf93574d4242b2d97E,"a",@progbits
.p2align 2, 0x0
下图展示了跳转表的基本结构, 与 if/else 语句相比, 分支越多, match 表达式的执行效率相对越高.
另外, 在 aarch64 平台编译器也有类似的行为, 下面的汇编代码片段展示了 with_match_long()
函数:
.section .text._ZN10jump_table15with_match_long17h4ee0bf410c273a3cE,"ax",@progbits
.p2align 2
.type _ZN10jump_table15with_match_long17h4ee0bf410c273a3cE,@function
_ZN10jump_table15with_match_long17h4ee0bf410c273a3cE:
.Lfunc_begin7:
.cfi_startproc
.cfi_personality 156, DW.ref.rust_eh_personality
.cfi_lsda 28, .Lexception7
sub sp, sp, #192
.cfi_def_cfa_offset 192
str x30, [sp, #176]
.cfi_offset w30, -16
.cfi_remember_state
add x8, sp, #48
str x8, [sp, #32]
bl _ZN3std3env4args17hf417fc576c685b45E
ldr x0, [sp, #32]
.Ltmp41:
bl _ZN84_$LT$std..env..Args$u20$as$u20$core..iter..traits..exact_size..ExactSizeIterator$GT$3len17ha56aa4d3a3bd219eE
str x0, [sp, #40]
.Ltmp42:
b .LBB43_3
.LBB43_1:
.Ltmp44:
add x0, sp, #48
bl _ZN4core3ptr35drop_in_place$LT$std..env..Args$GT$17hdc3a512020133b55E
.Ltmp45:
b .LBB43_14
.LBB43_2:
.Ltmp43:
str x0, [sp, #160]
mov w8, w1
str w8, [sp, #168]
b .LBB43_1
.LBB43_3:
add x0, sp, #48
bl _ZN4core3ptr35drop_in_place$LT$std..env..Args$GT$17hdc3a512020133b55E
ldr x8, [sp, #40]
subs x8, x8, #1
str x8, [sp, #24]
subs x8, x8, #3
cset w8, hi
tbnz w8, #0, .LBB43_5
ldr x11, [sp, #24]
adrp x10, .LJTI43_0
add x10, x10, :lo12:.LJTI43_0
.Ltmp47:
adr x8, .Ltmp47
ldrsw x9, [x10, x11, lsl #2]
add x8, x8, x9
br x8
.LBB43_5:
str wzr, [sp, #84]
b .LBB43_10
.LBB43_6:
mov w8, #100
str w8, [sp, #84]
b .LBB43_10
.LBB43_7:
mov w8, #200
str w8, [sp, #84]
b .LBB43_10
.LBB43_8:
mov w8, #300
str w8, [sp, #84]
b .LBB43_10
.LBB43_9:
mov w8, #400
str w8, [sp, #84]
b .LBB43_10
.LBB43_10:
add x8, sp, #84
str x8, [sp, #88]
adrp x8, .L__unnamed_10
add x8, x8, :lo12:.L__unnamed_10
str x8, [sp, #96]
ldr x8, [sp, #88]
str x8, [sp, #8]
ldr x9, [sp, #96]
str x9, [sp, #16]
ldr w8, [x8]
ldr w9, [x9]
subs w8, w8, w9
cset w8, ne
tbnz w8, #0, .LBB43_12
b .LBB43_11
.LBB43_11:
ldr x30, [sp, #176]
add sp, sp, #192
.cfi_def_cfa_offset 0
.cfi_restore w30
ret
.LBB43_12:
.cfi_restore_state
ldr x2, [sp, #16]
ldr x1, [sp, #8]
strb wzr, [sp, #111]
add x3, sp, #112
str xzr, [sp, #112]
ldrb w0, [sp, #111]
adrp x4, .L__unnamed_13
add x4, x4, :lo12:.L__unnamed_13
bl _ZN4core9panicking13assert_failed17h10918a4a4d6d5d6fE
brk #0x1
.LBB43_13:
.Ltmp46:
bl _ZN4core9panicking19panic_cannot_unwind17hf82fd8d1e9cc4d07E
brk #0x1
.LBB43_14:
ldr x0, [sp, #160]
bl _Unwind_Resume
brk #0x1
.Lfunc_end43:
.size _ZN10jump_table15with_match_long17h4ee0bf410c273a3cE, .Lfunc_end43-_ZN10jump_table15with_match_long17h4ee0bf410c273a3cE
.cfi_endproc
// 定义的跳转表
.section .rodata._ZN10jump_table15with_match_long17h4ee0bf410c273a3cE,"a",@progbits
.p2align 2, 0x0
.LJTI43_0:
.word .LBB43_6-.Ltmp47
.word .LBB43_7-.Ltmp47
.word .LBB43_8-.Ltmp47
.word .LBB43_9-.Ltmp47
.section .gcc_except_table._ZN10jump_table15with_match_long17h4ee0bf410c273a3cE,"a",@progbits
.p2align 2, 0x0