1 个不稳定版本
0.1.0 | 2022年2月28日 |
---|
#52 在 #bytes
用于 lighter
13KB
195 行
lighter
- 略优于 match
🔥
假设我们有某些使用字符串匹配语句的 Rust 代码
pub fn greeting_id(greeting: &str) -> usize {
match greeting {
"hi" => 0,
"hey" => 1,
"hello" => 2,
_ => 3,
}
}
结果,字符串作为模式的匹配语句被编译成一系列的 if
语句,上述代码本质上等同于以下代码
pub fn greeting_id(greeting: &str) -> usize {
if greeting == "hi" {
0
} else if greeting == "hey" {
1
} else if greeting == "hello" {
2
} else {
3
}
}
rustc
和 LLVM 有许多优化匹配语句的技巧,其中参数是整数或枚举等原始类型,但 Rust 似乎并没有像对待数字或枚举那样将字符串视为“特殊”。相反,它会回退到调用相关的 PartialEq
实现方式。这可能不是很高效,尤其是如果我们有大量字符串,并且其中一些字符串具有共同的子串。下面是使用 lighter
而不是普通 match
修改后的 greeting_id
use lighter::lighter;
pub fn greeting_id(greeting: &str) -> usize {
lighter! {
match greeting {
"hi" => 0,
"hey" => 1,
"hello" => 2,
_ => 3,
}
}
}
在编译过程中,lighter
将扁平的匹配语句转换成静态的 字典树,其中每个匹配语句考虑一次一个字符
pub fn greeting_id(greeting: &str) -> usize {
let mut bytes = greeting.bytes();
match bytes.next() {
Some(b'h') => match bytes.next() {
Some(b'i') => match bytes.next() {
None => 0,
_ => 3,
},
Some(b'e') => match bytes.next() {
Some(b'y') => match bytes.next() {
None => 1,
_ => 3,
},
Some(b'l') => match bytes.next() {
Some(b'l') => match bytes.next() {
Some(b'o') => match bytes.next() {
None => 2,
_ => 3,
},
_ => 3,
},
_ => 3,
},
_ => 3,
},
_ => 3,
},
_ => 3,
}
}
与没有 lighter
的原始 match
相比,这可能会显得有些复杂,但通过使用字节字面量(实际上只是 u8
),我们允许 Rust 和 LLVM 使用他们全部的优化工具来映射数字到数字的开关,从而产生 更好的代码。嵌套的 match
结构还意味着我们只需要比较每个字符一次:使用普通的 match
,greeting_id
会将其输入与 "hi"
中的 h
和 "hello"
中的 h
进行比较,而使用 lighter
,greeting_id
只匹配一次 h
,并知道它正在寻找的后缀是 i
或 ello
。
更重要的是,lighter
不仅与字符串或切片一起工作:您还可以对任何 u8
迭代器的输出进行匹配,或者任何可以转换为迭代器的东西。 lighter
还可以匹配与整个字符串匹配的字符串前缀,或者除了整个字符串外还匹配字符串;参见 lighter/examples/is_whitespace_2.rs
。
依赖项
~3.5MB
~75K SLoC