深入理解 Rust 正则表达式:使用 regex
crate
正则表达式(Regular Expression,简称 Regex 或 Regexp)是一种强大的文本处理工具,它用简洁的模式来匹配、查找、替换或提取字符串中的特定文本。在许多编程语言中,正则表达式都是不可或缺的一部分。对于 Rust 这样注重性能和安全性的语言来说,一个高效且可靠的正则表达式库尤为重要。
Rust 生态中最常用、功能最强大且性能卓越的正则表达式库是 regex
crate。它由 Rust 的主要贡献者之一 BurntSushi 开发和维护,以其高质量的代码和优异的性能而闻名。与许多其他语言的正则表达式引擎不同,regex
crate 默认使用的是有限自动机(Finite Automata),这意味着它的匹配时间与输入字符串的长度呈线性关系,且不提供传统的、可能导致灾难性回溯的特性(如look-around断言等)。这种设计决策在牺牲一些高级特性换取了极高的性能和可预测性。
本文将带你深入了解如何在 Rust 项目中使用 regex
crate,涵盖从基础匹配到高级操作、性能优化以及错误处理等各个方面。
一、准备工作:添加 regex
依赖
首先,你需要在你的 Rust 项目中添加 regex
crate 作为依赖。打开项目的 Cargo.toml
文件,并在 [dependencies]
部分加入以下行:
toml
[dependencies]
regex = "1.10.4" # 或者使用最新版本
保存文件后,Cargo 会自动下载并构建 regex
crate。现在,你就可以在你的 Rust 代码中使用它了。
二、核心概念:Regex 对象
在 regex
crate 中,表示一个编译后的正则表达式模式的核心类型是 regex::Regex
。在使用任何匹配或搜索方法之前,你必须先将你的正则表达式模式字符串编译成一个 Regex
对象。
编译通过 Regex::new()
函数完成。这个函数接收一个字符串切片作为正则表达式模式,并返回一个 Result<Regex, regex::Error>
。这意味着编译过程可能会失败(例如,如果你的模式字符串语法不正确),你需要处理这个潜在的错误。
一个简单的编译示例:
“`rust
use regex::Regex;
fn main() {
// 编译一个简单的正则表达式:匹配一个或多个数字
match Regex::new(r”\d+”) {
Ok(re) => {
println!(“正则表达式编译成功!”);
// 可以在这里使用 re 对象进行匹配操作
}
Err(err) => {
println!(“正则表达式编译失败: {}”, err);
}
}
// 通常,如果模式是硬编码且确定是有效的,可以使用 .unwrap() 或 .expect()
// 但在处理用户输入的模式时,应始终使用 match 或 if let 处理 Result
let re = Regex::new(r"\s+").unwrap(); // 匹配一个或多个空白字符
println!("另一个正则表达式也编译成功。");
}
“`
重要提示: 正则表达式的编译是一个相对耗时的操作。如果你在程序中多次使用同一个正则表达式,应该只编译它一次,并在需要的地方重用编译好的 Regex
对象。后续我们将讨论如何有效地管理 Regex
对象,特别是在函数或方法内部避免重复编译。
三、基本匹配操作
Regex
对象提供了多种方法来检查或查找匹配项。
3.1 检查是否匹配 (is_match
)
最简单的操作是检查整个输入字符串是否包含与正则表达式匹配的子串。is_match
方法返回一个布尔值。
“`rust
use regex::Regex;
fn main() {
let re = Regex::new(r”\d{3}-\d{3}-\d{4}”).unwrap(); // 匹配电话号码模式
let text1 = "My phone number is 123-456-7890.";
let text2 = "No number here.";
println!("'{}' contains phone number: {}", text1, re.is_match(text1)); // 输出: true
println!("'{}' contains phone number: {}", text2, re.is_match(text2)); // 输出: false
}
“`
需要注意的是,is_match
只需要找到一个匹配项就会返回 true
,它并不关心匹配项的位置或内容。
3.2 查找第一个匹配项 (find
)
如果你想找到第一个匹配的子字符串及其位置,可以使用 find
方法。它返回一个 Option<Match>
。如果找到匹配项,返回 Some(Match)
;否则返回 None
。
Match
结构体包含匹配子串的开始和结束字节索引,以及一个获取匹配子串切片的方法 as_str()
。
“`rust
use regex::Regex;
fn main() {
let re = Regex::new(r”\d+”).unwrap(); // 匹配一个或多个数字
let text = “There are 123 apples and 45 bananas.”;
match re.find(text) {
Some(match_) => {
println!("找到第一个数字: {}", match_.as_str());
println!("起始索引: {}, 结束索引: {}", match_.start(), match_.end());
}
None => {
println!("没有找到数字。");
}
}
// 输出:
// 找到第一个数字: 123
// 起始索引: 10, 结束索引: 13
}
“`
3.3 查找所有匹配项 (find_iter
)
如果你需要查找输入字符串中所有不重叠的匹配项,可以使用 find_iter
方法。它返回一个迭代器,迭代器产生的每个元素都是一个 Match
结构体。
“`rust
use regex::Regex;
fn main() {
let re = Regex::new(r”\w+”).unwrap(); // 匹配一个或多个字母数字字符
let text = “Hello world, this is a test.”;
println!("找到所有单词:");
for match_ in re.find_iter(text) {
println!("- {} ({}..{})", match_.as_str(), match_.start(), match_.end());
}
// 输出:
// 找到所有单词:
// - Hello (0..5)
// - world (6..11)
// - this (13..17)
// - is (18..20)
// - a (21..22)
// - test (23..27)
}
“`
find_iter
非常方便,可以与其他迭代器适配器结合使用,例如 map
、filter
等。
四、使用捕获组 (captures
)
正则表达式的强大之处之一在于捕获组。通过在模式中使用括号 ()
,你可以“捕获”正则表达式匹配的子串中的特定部分。
Regex
对象提供了 captures
和 captures_iter
方法来处理捕获组。
4.1 查找第一个捕获组集合 (captures
)
captures
方法查找输入字符串中第一个与整个正则表达式匹配的子串,并提取所有捕获组的内容。它返回 Option<Captures>
。
Captures
结构体代表一次完整的匹配以及其中包含的所有捕获组。索引 0 始终对应于整个匹配的文本,索引 1 对应第一个捕获组 ()
,索引 2 对应第二个,依此类推。
“`rust
use regex::Regex;
fn main() {
// 匹配日期格式 YYYY-MM-DD,并捕获年、月、日
let re = Regex::new(r”(\d{4})-(\d{2})-(\d{2})”).unwrap();
let text = “Today’s date is 2023-10-27.”;
match re.captures(text) {
Some(caps) => {
// caps[0] 是整个匹配项
println!("整个匹配项: {}", &caps[0]);
// caps[1] 是第一个捕获组 (年)
println!("年份: {}", &caps[1]);
// caps[2] 是第二个捕获组 (月)
println!("月份: {}", &caps[2]);
// caps[3] 是第三个捕获组 (日)
println!("日期: {}", &caps[3]);
// 使用 get() 方法更安全,它返回 Option<&Match>
// 这对于可选的捕获组特别有用
if let Some(year_match) = caps.get(1) {
println!("年份 (使用 get): {}", year_match.as_str());
}
}
None => {
println!("没有找到匹配的日期。");
}
}
// 输出:
// 整个匹配项: 2023-10-27
// 年份: 2023
// 月份: 10
// 日期: 27
// 年份 (使用 get): 2023
}
“`
使用索引访问 Captures
(如 &caps[1]
)如果索引超出范围会 panic。使用 caps.get(index)
则返回 Option<&Match>
,更加安全,尤其适用于可选捕获组(使用 ?
量词修饰的捕获组)。
4.2 查找所有捕获组集合 (captures_iter
)
captures_iter
方法返回一个迭代器,生成输入字符串中所有不重叠的匹配项及其对应的捕获组集合。
“`rust
use regex::Regex;
fn main() {
// 匹配 HTML 标签,并捕获标签名和内容
let re = Regex::new(r”<(\w+)>([^<]+)</\1>”).unwrap();
let text = “
Title
Some text.
“;
println!("找到所有标签及其内容:");
for caps in re.captures_iter(text) {
let tag_name = &caps[1];
let content = &caps[2];
println!("- 标签: {}, 内容: {}", tag_name, content);
}
// 输出:
// 找到所有标签及其内容:
// - 标签: h1, 内容: Title
// - 标签: p, 内容: Some text.
}
“`
4.3 命名捕获组
除了使用数字索引,你还可以给捕获组命名,使其更易读。使用 (?<name>...)
的语法来创建命名捕获组。在 Captures
中,你可以使用 caps.name("name")
来访问命名捕获组,它也返回一个 Option<&Match>
。
“`rust
use regex::Regex;
fn main() {
// 匹配日期,使用命名捕获组
let re = Regex::new(r”(?
let text = “The event is on 2024-01-15.”;
if let Some(caps) = re.captures(text) {
if let Some(year_match) = caps.name("year") {
println!("年份: {}", year_match.as_str());
}
if let Some(month_match) = caps.name("month") {
println!("月份: {}", month_match.as_str());
}
if let Some(day_match) = caps.name("day") {
println!("日期: {}", day_match.as_str());
}
} else {
println!("没有找到匹配的日期。");
}
// 输出:
// 年份: 2024
// 月份: 01
// 日期: 15
}
“`
使用命名捕获组可以提高代码的可读性和健壮性,尤其是在捕获组数量较多或模式可能发生变化时。
五、文本替换
正则表达式常用于查找并替换文本。regex
crate 提供了 replace
和 replace_all
方法。
5.1 替换第一个匹配项 (replace
)
replace
方法查找第一个匹配项,并将其替换为指定的字符串。它返回一个新的 Cow<str>
。Cow
是 “Clone-on-Write” 的缩写,表示如果不需要修改,则返回原字符串的引用;如果需要修改(发生替换),则返回一个拥有所有权的新 String
。
“`rust
use regex::Regex;
fn main() {
let re = Regex::new(r”Rust”).unwrap();
let text = “Rust is cool. Rust is fun.”;
let replaced_text = re.replace(text, "Go");
println!("替换第一个: {}", replaced_text);
// 输出: 替换第一个: Go is cool. Rust is fun.
}
“`
5.2 替换所有匹配项 (replace_all
)
replace_all
方法查找所有不重叠的匹配项,并将它们全部替换为指定的字符串。它也返回一个 Cow<str>
。
“`rust
use regex::Regex;
fn main() {
let re = Regex::new(r”Rust”).unwrap();
let text = “Rust is cool. Rust is fun.”;
let replaced_text = re.replace_all(text, "Go");
println!("替换所有: {}", replaced_text);
// 输出: 替换所有: Go is cool. Go is fun.
}
“`
5.3 在替换字符串中使用捕获组
你可以在替换字符串中使用 $N
或 ${name}
的语法来引用捕获组的内容,其中 N
是捕获组的数字索引,name
是命名捕获组的名称。
“`rust
use regex::Regex;
fn main() {
// 匹配日期 YYYY-MM-DD 并转换为 DD/MM/YYYY 格式
let re = Regex::new(r”(\d{4})-(\d{2})-(\d{2})”).unwrap();
let text = “Birth date: 1990-05-10, Join date: 2022-11-20.”;
// 使用 $N 引用捕获组
let formatted_text = re.replace_all(text, "$3/\/\");
println!("格式化日期 (数字索引): {}", formatted_text);
// 输出: 格式化日期 (数字索引): Birth date: 10/05/1990, Join date: 20/11/2022.
// 如果使用了命名捕获组
let re_named = Regex::new(r"(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})").unwrap();
let text_named = "Next meeting: 2025-03-01.";
let formatted_text_named = re_named.replace_all(text_named, "${day}/${month}/${year}");
println!("格式化日期 (命名索引): {}", formatted_text_named);
// 输出: 格式化日期 (命名索引): Next meeting: 01/03/2025.
}
“`
5.4 使用闭包进行复杂替换
对于更复杂的替换逻辑,你可以向 replace
或 replace_all
方法传递一个闭包,而不是一个简单的字符串。这个闭包会接收一个 Captures
结构体作为参数,你需要返回一个 AsRef<str>
类型的值(通常是 String
或 &str
)作为该匹配项的替换文本。
“`rust
use regex::Regex;
fn main() {
// 匹配数字,并将其值乘以 2 后作为替换文本
let re = Regex::new(r”\d+”).unwrap();
let text = “Numbers: 10, 25, 5.”;
let replaced_text = re.replace_all(text, |caps: ®ex::Captures| {
let num_str = caps.get(0).unwrap().as_str(); // 获取整个匹配的数字字符串
let num = num_str.parse::<i32>().unwrap_or(0); // 解析为整数
(num * 2).to_string() // 计算双倍并转换为 String 返回
});
println!("数字翻倍: {}", replaced_text);
// 输出: 数字翻倍: Numbers: 20, 50, 10.
}
“`
使用闭包提供了极大的灵活性,你可以根据匹配到的内容执行任意逻辑来生成替换文本。
六、正则表达式选项和标志
正则表达式引擎支持各种选项来修改匹配行为,例如忽略大小写、多行模式等。在 regex
crate 中,你可以通过两种主要方式设置这些选项:
- 在模式字符串中使用内联标志: 使用
(?flags:...)
或直接在模式开头使用(?flags)
。例如(?i)
表示忽略大小写。 - 使用
RegexBuilder
: 对于更复杂的配置或多个选项,可以使用RegexBuilder
。
6.1 内联标志
常用的内联标志包括:
i
: 忽略大小写(Case-insensitive)m
: 多行模式(Multiline mode)。^
匹配行首,$
匹配行尾,而不是整个字符串的开头/结尾。s
: 点号匹配所有字符(Dotall mode)。.
匹配包括换行符在内的任何字符。U
: 非贪婪匹配(Ungreedy mode)。量词(如*
,+
,?
)默认是非贪婪的。x
: 忽略模式中的空白和#
后面的注释(Extended mode)。
示例:忽略大小写匹配 “rust”
“`rust
use regex::Regex;
fn main() {
// 使用内联标志 (?i)
let re = Regex::new(r”(?i)rust”).unwrap();
let text = “Rust is cool. learn RUST now!”;
println!("忽略大小写匹配 'rust':");
for match_ in re.find_iter(text) {
println!("- {}", match_.as_str());
}
// 输出:
// 忽略大小写匹配 'rust':
// - Rust
// - RUST
}
“`
6.2 使用 RegexBuilder
RegexBuilder
提供了更详细的控制选项,并允许你设置多个选项。
“`rust
use regex::RegexBuilder;
fn main() {
let text = “First line.\nSecond line.”;
// 默认模式,^匹配字符串开头,$匹配字符串结尾
let re_default = Regex::new(r"^Second").unwrap();
println!("默认模式匹配 '^Second': {}", re_default.is_match(text)); // 输出: false
// 启用多行模式 (m),^和$匹配行首/行尾
let re_multiline = RegexBuilder::new(r"^Second")
.multi_line(true)
.build()
.unwrap();
println!("多行模式匹配 '^Second': {}", re_multiline.is_match(text)); // 输出: true
// 组合多个选项
let re_combined = RegexBuilder::new(r"start.*end")
.case_insensitive(true) // 忽略大小写
.dot_matches_new_line(true) // 点号匹配换行
.build()
.unwrap();
let text_combined = "This is the START\n...some text...\nEND here.";
println!("组合选项匹配: {}", re_combined.is_match(text_combined)); // 输出: true
}
“`
RegexBuilder
提供的其他方法包括 size_limit
(限制自动机的内存使用)、dfa_size_limit
(限制 DFA 的内存使用)等,这些对于处理非常复杂或用户提供的模式以防止资源耗尽非常有用。
七、性能优化:避免重复编译
前面提到,编译正则表达式是一个相对昂贵的操作。在 Rust 中,我们应该尽可能地避免在热点代码路径(如循环内部、频繁调用的函数)中重复编译同一个正则表达式。
最佳实践是将 Regex
对象存储起来,并在需要时重用。对于全局或静态的正则表达式,可以使用 lazy_static!
或 once_cell
crate 来实现只编译一次并在整个程序生命周期内共享。
7.1 使用 lazy_static!
(或 once_cell::lazy::Lazy
)
lazy_static!
宏允许你定义一个静态变量,其初始化会在第一次访问时进行,且是线程安全的。这非常适合用于存储编译后的 Regex
对象。once_cell::lazy::Lazy
提供了类似的功能,是 Rust 官方推荐的替代方案。
首先,你需要添加 lazy_static
或 once_cell
依赖:
toml
[dependencies]
regex = "1.10.4"
lazy_static = "1.4.0" # 或者 once_cell = "1.18.0"
然后,在你的代码中使用 lazy_static!
宏:
“`rust
[macro_use] // 导入 lazy_static 宏
extern crate lazy_static;
use regex::Regex;
// 定义一个全局的、只在首次使用时编译的 Regex 对象
lazy_static! {
static ref EMAIL_RE: Regex = Regex::new(r”^\S+@\S+.\S+$”).unwrap();
static ref URL_RE: Regex = Regex::new(r”^https?://”).unwrap();
}
fn is_valid_email(email: &str) -> bool {
EMAIL_RE.is_match(email) // 直接使用静态变量
}
fn is_url(url: &str) -> bool {
URL_RE.is_match(url) // 直接使用静态变量
}
fn main() {
println!(“Is ‘[email protected]’ a valid email? {}”, is_valid_email(“[email protected]”));
println!(“Is ‘invalid-email’ a valid email? {}”, is_valid_email(“invalid-email”));
println!(“Is ‘https://rust-lang.org’ a URL? {}”, is_url(“https://rust-lang.org”));
println!(“Is ‘ftp://example.com’ a URL? {}”, is_url(“ftp://example.com”));
}
“`
在这个例子中,EMAIL_RE
和 URL_RE
只会在它们各自的函数 (is_valid_email
, is_url
) 第一次被调用时进行编译,之后的调用会直接使用已编译好的 Regex
对象,大大提高了效率。
如果你的项目是 Edition 2021 或更高,并且不想引入额外的 lazy_static
crate,你可以使用 once_cell
:
“`rust
use regex::Regex;
use once_cell::sync::Lazy; // 导入线程安全的 Lazy
// 定义一个全局的、只在首次使用时编译的 Regex 对象
static EMAIL_RE: Lazy
Regex::new(r”^\S+@\S+.\S+$”).unwrap()
});
static URL_RE: Lazy
Regex::new(r”^https?://”).unwrap()
});
fn is_valid_email(email: &str) -> bool {
EMAIL_RE.is_match(email) // 直接使用静态变量
}
fn is_url(url: &str) -> bool {
URL_RE.is_match(url) // 直接使用静态变量
}
fn main() {
println!(“Is ‘[email protected]’ a valid email? {}”, is_valid_email(“[email protected]”));
println!(“Is ‘invalid-email’ a valid email? {}”, is_valid_email(“invalid-email”));
println!(“Is ‘https://rust-lang.org’ a URL? {}”, is_url(“https://rust-lang.org”));
println!(“Is ‘ftp://example.com’ a URL? {}”, is_url(“ftp://example.com”));
}
“`
这种方式在概念上与 lazy_static!
相同,只是语法略有不同。
7.2 传递 Regex 对象作为参数
另一种重用 Regex
对象的方法是在函数或结构体中存储它,并作为参数传递。
“`rust
use regex::Regex;
struct TextProcessor {
// 存储编译好的 Regex 对象
number_re: Regex,
word_re: Regex,
}
impl TextProcessor {
fn new() -> Result
let number_re = Regex::new(r”\d+”)?; // 使用 ? 处理 Result
let word_re = Regex::new(r”\w+”)?;
Ok(Self { number_re, word_re })
}
fn process_text(&self, text: &str) {
println!("Processing: '{}'", text);
println!(" Numbers found:");
for num_match in self.number_re.find_iter(text) {
println!(" - {}", num_match.as_str());
}
println!(" Words found:");
for word_match in self.word_re.find_iter(text) {
println!(" - {}", word_match.as_str());
}
}
}
fn main() {
match TextProcessor::new() {
Ok(processor) => {
processor.process_text(“Item 1 costs $25, Item 2 costs $10.”);
processor.process_text(“Just words here.”);
}
Err(err) => {
eprintln!(“Failed to initialize TextProcessor: {}”, err);
}
}
}
“`
这种方式将 Regex
对象的生命周期与 TextProcessor
结构体绑定,适用于需要在某个特定上下文中重用多个 Regex
对象的场景。
八、错误处理
正如之前提到的,Regex::new()
返回 Result<Regex, regex::Error>
。处理这个错误至关重要,尤其当正则表达式模式来自外部输入(如用户配置或文件)时。
“`rust
use regex::Regex;
fn compile_regex(pattern: &str) -> Result
Regex::new(pattern)
}
fn main() {
let pattern1 = r”^\d+$”; // 有效模式
match compile_regex(pattern1) {
Ok(re) => println!(“模式 ‘{}’ 编译成功。”, pattern1),
Err(err) => eprintln!(“模式 ‘{}’ 编译失败: {}”, pattern1, err),
}
let pattern2 = r"["; // 无效模式:未闭合的方括号
match compile_regex(pattern2) {
Ok(re) => println!("模式 '{}' 编译成功。", pattern2),
Err(err) => eprintln!("模式 '{}' 编译失败: {}", pattern2, err),
}
let pattern3 = r"(abc"; // 无效模式:未闭合的捕获组
match compile_regex(pattern3) {
Ok(re) => println!("模式 '{}' 编译成功。", pattern3),
Err(err) => eprintln!("模式 '{}' 编译失败: {}", pattern3, err),
}
}
“`
当模式是硬编码且你确定它是有效的时,使用 .unwrap()
或 .expect()
可以简化代码,但这应仅限于这种确定性场景,以避免程序意外 panic。
九、其他有用的方法和概念
replace_with
: 类似于replace_all
但使用闭包,并且返回一个String
。split
和splitn
: 使用正则表达式作为分隔符来分割字符串,返回一个迭代器。shortest_match
: 查找最短的匹配项(如果模式允许不同长度的匹配)。regex
默认是优先匹配最左边的结果,然后是符合模式的最长结果。- 锚点 (
^
,$
,\b
,\B
,\A
,\Z
,\z
): 用于匹配特定位置而不是字符序列。^
: 行首(多行模式下)或字符串开头(默认)。$
: 行尾(多行模式下)或字符串结尾(默认)。\b
: 单词边界。\B
: 非单词边界。\A
: 字符串绝对开头。\Z
: 字符串结尾,忽略最后的换行符。\z
: 字符串绝对结尾。
- 字符类 (
\d
,\w
,\s
,\D
,\W
,\S
): 匹配特定类型的字符。\d
: 任意数字。\D
: 任意非数字。\w
: 任意字母、数字或下划线(单词字符)。\W
: 任意非单词字符。\s
: 任意空白字符(空格、制表符、换行符等)。\S
: 任意非空白字符。
- 重复 (
*
,+
,?
,{n}
,{n,}
,{n,m}
): 控制前一个元素出现的次数。*
: 0 次或多次。+
: 1 次或多次。?
: 0 次或 1 次(可选)。{n}
: 恰好 n 次。{n,}
: 至少 n 次。{n,m}
: 至少 n 次,至多 m 次。- 默认是贪婪匹配,在其后加上
?
可变为非贪婪匹配(如*?
,+?
)。
十、与 fancy-regex
库的对比(简要)
虽然 regex
crate 功能强大且性能优异,但它不支持一些高级特性,如:
- 后向引用(backreferences)在某些复杂场景下的使用受限。
- 任意复杂性(arbitrary lookaround)的环视断言(lookarounds)。
- 递归匹配。
这些特性在某些其他语言的 regex 引擎(如 Perl、Python、Java 的标准库)中是支持的,但它们常常依赖于回溯,可能导致所谓的“正则表达式拒绝服务”(ReDoS)攻击或性能急剧下降。
如果你确实需要这些高级但可能影响性能的特性,可以考虑使用 fancy-regex
crate。fancy-regex
在很大程度上兼容 regex
的 API,但它通过集成一个支持回溯的引擎来实现这些高级功能。使用时需要权衡功能需求与潜在的性能风险。对于大多数常见的文本处理任务,regex
crate 通常是更好的选择。
十一、总结
Rust 的 regex
crate 是处理字符串模式匹配和操作的强大工具。它以其高性能、安全性和 Rust 语言的良好集成而脱颖而出。
本文详细介绍了:
- 如何添加
regex
依赖。 - 编译正则表达式模式为
Regex
对象。 - 使用
is_match
检查匹配。 - 使用
find
和find_iter
查找匹配项。 - 使用
captures
和captures_iter
处理捕获组(包括命名捕获组)。 - 使用
replace
和replace_all
进行文本替换,并展示了如何使用捕获组和闭包。 - 通过内联标志和
RegexBuilder
设置正则表达式选项。 - 通过
lazy_static!
或once_cell
优化性能,避免重复编译。 - 处理
Regex::new()
可能返回的错误。 - 简要提及了一些其他有用方法和概念。
掌握 regex
crate 将极大地提升你在 Rust 中进行文本处理的能力。记住在实践中优先编译一次、重用 Regex
对象,并根据需要灵活运用捕获组和替换功能。对于绝大多数应用场景,regex
crate 提供的功能和性能已经足够强大。
开始尝试在你的 Rust 项目中使用 regex
吧!