1个不稳定版本
0.1.0 | 2022年2月28日 |
---|
#302 在 解析工具
5KB
69 行
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
基本上将扁平的匹配语句转换为静态的Trie,其中每个匹配语句考虑一个字符
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使用它们全部的优化功能来处理数字到数字的映射,从而得到更好的代码。[链接](https://rust.godbolt.org/z/zcxKhdWfd "更好的代码")。嵌套的match
结构也意味着我们只需要比较每个字符一次:使用普通的match
,greeting_id
会将输入与字符串""hi""和""hello""中的"h"进行比较,而使用lighter
时,greeting_id
只需匹配一次"h",并知道它要查找的后缀是"i"或""ello""。
更重要的是,lighter
不仅与字符串或切片一起工作:你可以匹配任何u8
迭代器的输出,或者任何可以被转换成迭代器的对象。lighter
还可以匹配与整个字符串匹配的字符串前缀,或者作为整个字符串的补充;请参阅lighter/examples/is_whitespace_2.rs
。
依赖项
~3.5MB
~75K SLoC