类型理解
他们二者可以相互转换, 因为本质都是用于描述"字符串"这个概念的
- 字符串切片 &str 是最原始的字符串切片类型, 不可变引用, 固定大小(胖指针+长度)
- 可增长字符串 String 是标准库模块中的结构体, 拥有所有权, 用其他编程语言的话说他是一个类一个引用类型
关键差异说明
String 独有特点:
- 所有修改操作(push、insert、remove、pop等)
- 容量管理(capacity、reserve、shrink等)
- 所有权转移(into_bytes、into_boxed_str等)
- 原地修改(make_ascii_*、replace_range等)
共有但行为不同:
to_string()/to_owned(): String 克隆自身, &str 创建新 Stringas_str(): String 借用, &str 返回自身- 所有修改方法在 &str 上不可用
标准库操作方法
INFO
由于 String 和 &str 两种类型可以互相转换使用
所以笔记以 String 为主, 必要的时候也可以转换为 &str
创建
| 方法 | 作用 |
|---|---|
字符串字面量 "hello" | 直接创建 &str |
| String::new | 创建空 String |
| String::from | 从 &str 创建 String |
| format! 宏 | 格式化创建字符串 |
| push_str | 在末尾追加字符串 |
rust
use std::any::type_name;
// 打印变量的类型和值, 只取最后的类型名(方便阅读)
fn print_type<T: std::fmt::Debug>(var: &T) {
let name = type_name::<T>().rsplit("::").next().unwrap();
println!("类型: {name}, 值: {var:?}");
}
fn main() {
// 1.直接创建 &str(字面量就是字符串切片)
let str = "hello";
print_type(&str); // 类型: &str, 值: "hello"
// 2.直接创建空 String
let string = String::new();
print_type(&string); // 类型: String, 值: ""
// 3.创建可变 String 后再追加内容
let mut string = String::new();
string.push_str("hello");
print_type(&string); // 类型: String, 值: "hello"
// 4.使用宏创建字符串
let string = format!("{}-{}", 1, "2");
print_type(&string); // 类型: String, 值: "1-2"
}String 与 &str 互相转换
| 方法 | 作用 |
|---|---|
| as_str | String 借用为 &str |
| as_mut_str | String 借用为可变 &mut str |
| String::from | &str 转为 String |
| str::to_string | &str 转为 String |
| into | 通过 From trait 转换 |
rust
use std::any::type_name;
fn print_type<T: std::fmt::Debug>(var: &T) {
let name = type_name::<T>().rsplit("::").next().unwrap();
println!("类型: {name}, 值: {var:?}");
}
fn main() {
let mut string = String::from("hello world");
// as_str: 借用为 &str, 不转移所有权
let str1 = string.as_str();
print_type(&str1); // 类型: &str, 值: "hello world"
// as_mut_str: 借用为可变 &mut str
let str2 = string.as_mut_str();
print_type(&str2); // 类型: &mut str, 值: "hello world"
}rust
use std::any::type_name;
fn print_type<T: std::fmt::Debug>(var: &T) {
let name = type_name::<T>().rsplit("::").next().unwrap();
println!("类型: {name}, 值: {var:?}");
}
fn main() {
let str = "hello world";
// String::from: &str 转为 String
let string1 = String::from(str);
print_type(&string1); // 类型: String, 值: "hello world"
// clone: 复制一份 String
let string2 = string1.clone();
print_type(&string2); // 类型: String, 值: "hello world"
// to_string: 最常用的转换方式
let string3 = str.to_string();
print_type(&string3); // 类型: String, 值: "hello world"
// into: 通过 From trait 自动转换
let string4: String = string3.into();
print_type(&string4); // 类型: String, 值: "hello world"
}与非字符串类型转换
| 方法 | 作用 |
|---|---|
| str::parse | 字符串解析为数字等类型 |
| str::as_bytes | 转为字节切片 &[u8] |
| String::into_bytes | String 转为 Vec<u8>(转移所有权) |
| String::from_utf8 | Vec<u8> 转为 String(可能失败) |
| String::from_utf8_lossy | Vec<u8> 转为 String(非法字节替换为 �) |
rust
fn main() {
// 将 &str 解析为数字类型
let str = "25";
let num = str.parse::<u32>().unwrap();
println!("{num}"); // 25
// 将 String 解析为数字类型
let string = String::from(str);
let num = string.parse::<u32>().unwrap();
println!("{num}"); // 25
// 解析失败会返回 Err(不是 panic)
let result = "abc".parse::<u32>();
println!("{result:?}"); // Err(ParseIntError { kind: InvalidDigit })
}rust
fn main() {
// &str 和 String 都可以转字节切片
let str = "hello";
let u8s = str.as_bytes();
println!("{u8s:?}"); // [104, 101, 108, 108, 111]
let string = String::from(str);
let bytes = string.as_bytes();
println!("{bytes:?}"); // [104, 101, 108, 108, 111]
}rust
fn main() {
// Vec<u8> 与 &[u8] 的不同:
// Vec<u8> 是拥有所有权的, 是可变的, 可以动态扩容, &[u8] 是不可变的, 固定大小
// 所以: Vec<u8> 只能由 String 转换而来(转移所有权), 而 &str 不行
let s = String::from("hello");
let mut bytes = s.into_bytes();
println!("{:?}", bytes); // [104, 101, 108, 108, 111]
// Vec<u8> 是可变的, 可以继续修改
bytes.push(b'A');
println!("{:?}", bytes); // [104, 101, 108, 108, 111, 65]
}rust
fn main() {
// from_utf8: 合法的 UTF-8 字节转 String, 返回 Result
let bytes = vec![104, 101, 108, 108, 111];
let string = String::from_utf8(bytes).unwrap();
println!("{string}"); // hello
// from_utf8_lossy: 遇到非法的 UTF-8 字节, 替换为 � 字符
// 最后一个 255 不是合法的 UTF-8 字节
// https://rustwiki.org/zh-CN/std/char/constant.REPLACEMENT_CHARACTER.html
let bytes2 = vec![104, 101, 108, 108, 111, 255];
let string = String::from_utf8_lossy(&bytes2);
println!("{string}") // hello�
}格式化转换
| 方法 | 作用 |
|---|---|
| to_uppercase / to_lowercase | 转大写/小写(支持 Unicode) |
| to_ascii_uppercase / to_ascii_lowercase | 转大写/小写(仅 ASCII, 中文不受影响) |
rust
fn main() {
// to_uppercase: 转大写(Unicode 感知)
let str = "hello";
println!("{}", str.to_uppercase()); // HELLO
let string = String::from(str);
println!("{}", string.to_uppercase()); // HELLO
// to_lowercase: 转小写
let str2 = "Hello World";
println!("{}", str2.to_lowercase()); // hello world
let string = String::from(str2);
println!("{}", string.to_lowercase()); // hello world
// Unicode 感知: 中文不受影响
println!("{}", "你好".to_uppercase()); // 你好
}rust
fn main() {
// to_ascii_uppercase: 只处理 ASCII 字符
let str = "hello";
println!("{}", str.to_ascii_uppercase()); // HELLO
let string = String::from(str);
println!("{}", string.to_ascii_uppercase()); // HELLO
// to_ascii_lowercase
let str2 = "Hello World";
println!("{}", str2.to_ascii_lowercase()); // hello world
let string = String::from(str2);
println!("{}", string.to_ascii_lowercase()); // hello world
// 中文不是 ASCII 字符, 不会被转换
println!("{}", "你好ABC".to_ascii_lowercase()); // 你好abc
}长度信息获取
| 方法 | 作用 |
|---|---|
| str::len | 字节长度(UTF-8 字节数) |
| str::chars | 字符迭代器, 配合 count 得到字符数量 |
| String::capacity | String 的容量 |
rust
fn main() {
let str = "你好,Rust";
// len: 字节长度(UTF-8 中一个中文占 3 个字节)
println!("{}", str.len()); // 11
// chars().count(): 字符数量(Unicode 标量值数量)
println!("{}", str.chars().count()); // 7
// capacity: String 的容量(当前分配的内存大小)
let mut string = String::new();
println!("{}", string.capacity()); // 0
string.push_str(str);
println!("{}", string.capacity()); // 11
}注意点
str.len() 返回的是字节数而不是字符数, 一个中文占 3 个字节。 所以不能用 len() 直接当"字符个数"用, 需要字符数量用 chars().count()
判断
| 方法 | 作用 |
|---|---|
| is_empty | 是否为空字符串 |
| is_ascii | 是否所有字符都是 ASCII |
| is_char_boundary | 索引位置是否是字符边界 |
| contains | 是否包含某个子字符串 |
| starts_with / ends_with | 是否以某个前缀/后缀开头结尾 |
rust
fn main() {
let mut str = "";
println!("str is empty: {}", str.is_empty()); // true
str = "hello";
println!("str is empty: {}", str.is_empty()); // false
let mut string = String::new();
println!("string is empty: {}", string.is_empty()); // true
string.push_str("hello");
println!("string is empty: {}", string.is_empty()); // false
}rust
fn main() {
let mut str = "hello";
println!("str is ascii: {}", str.is_ascii()); // true
str = "hello世界";
println!("str is ascii: {}", str.is_ascii()); // false
let mut string = String::from("hello");
println!("string is ascii: {}", string.is_ascii()); // true
string.push_str("hello世界");
println!("string is ascii: {}", string.is_ascii()); // false
}rust
fn main() {
// "你" 这个汉字在 UTF-8 中占 3 个字节(索引 0-2)
// 索引 3 是下一个字符 "好" 的开始位置
let str = "你好Rust";
// 0 是字符串开头, 一定是边界
let start = str.is_char_boundary(0);
println!("str[0] is: {start:?}"); // true
// 1 和 2 在 "你" 的字节中间, 不是边界
let middle = str.is_char_boundary(1);
println!("str[1] is: {middle:?}"); // false
let middle = str.is_char_boundary(2);
println!("str[2] is: {middle:?}"); // false
// 3 是 "好" 的开始位置, 是边界
let end = str.is_char_boundary(3);
println!("str[3] is: {end:?}"); // true
}rust
fn main() {
// contains: 是否包含子字符串(区分大小写), 返回 bool
let str = "hello rust";
let is_contain = str.contains("rust");
println!("is_contain = {is_contain}"); // true
let is_contain = str.contains("Rust");
println!("is_contain = {is_contain}"); // false
let is_contain = str.contains("rust1");
println!("is_contain = {is_contain}"); // false
}rust
fn main() {
// starts_with: 是否以某个前缀开头(区分大小写), 返回 bool
let str = "hello rust";
let is_prefix = str.starts_with("hello");
println!("is_prefix = {is_prefix}"); // true
let is_prefix = str.starts_with("Hello");
println!("is_prefix = {is_prefix}"); // false
}rust
fn main() {
// ends_with: 是否以某个后缀结尾(区分大小写), 返回 bool
let str = "hello rust";
let is_suffix = str.ends_with("rust");
println!("is_suffix = {is_suffix}"); // true
let is_suffix = str.ends_with("Rust");
println!("is_suffix = {is_suffix}"); // false
}查找搜索
| 方法 | 作用 |
|---|---|
| find / rfind | 查找子串第一次出现的位置(从前往后/从后往前) |
| matches / rmatches | 查找所有子串, 返回迭代器 |
| match_indices / rmatch_indices | 查找所有子串及开始位置, 返回迭代器 |
这些方法, String 和 &str 都可以使用
rust
fn main() {
let str = "hello rust";
//=== 1.查找 子字符串 第一次出现的位置 ===//
// 1.1: find 从前往后找, 返回 Option<usize>
let index = str.find("rust").unwrap();
println!("index = {index}"); // 6
// 1.2: rfind 从后往前找, 返回 Option<usize>
let rindex = str.rfind("rust").unwrap();
println!("rindex = {rindex}"); // 6
//=== 2.查找所有子字符串 ===//
// 2.1: matches 返回迭代器, 元素是匹配到的 &str
let str = "abcXXXabcYYYabc";
let items = str.matches("abc").collect::<Vec<_>>();
println!("{items:?}"); // ["abc", "abc", "abc"]
// 2.2: rmatches 从右边开始找
let items = str.rmatches("abc").collect::<Vec<_>>();
println!("{items:?}"); // ["abc", "abc", "abc"]
//=== 3.查找所有子字符串及开始位置 ===//
// 3.1: match_indices 返回 (index, sub_str) 元组迭代器
let items: Vec<_> = str.match_indices("abc").collect();
println!("{items:?}"); // [(0, "abc"), (6, "abc"), (12, "abc")]
// 3.2: rmatch_indices 从右边开始找
let items: Vec<_> = str.rmatch_indices("abc").collect();
println!("{items:?}"); // [(12, "abc"), (6, "abc"), (0, "abc")]
}替换
| 方法 | 作用 |
|---|---|
| replace | 替换所有匹配项 |
| replacen | 只替换前 n 个匹配项 |
| replace_range | 替换指定索引范围(仅 String, 原地修改) |
rust
fn main() {
// 1.replace: 将所有匹配项全部替换, 返回新字符串
let str = "hello rust, rust is best programming language";
let new_str = str.replace("rust", "Rust");
println!("{new_str}"); // hello Rust, Rust is best programming language
// 2.replacen: 只替换指定次数
let new_str = str.replacen("rust", "Rust", 1);
println!("{new_str}"); // hello Rust, rust is best programming language
// 3.replace_range: 替换指定索引范围内的字符, 仅 String 可用
// 注意: 这个函数不会返回新字符串, 而是直接修改原字符串
let mut string = String::from("hello rust");
println!("修改前: {string:?}");
string.replace_range(0..5, "hi");
println!("修改后: {string:?}"); // "hi rust"
}追加插入/移除删除
| 方法 | 作用 |
|---|---|
| push / push_str | 追加一个字符/字符串 |
| insert / insert_str | 指定位置插入字符/字符串 |
| remove / pop | 删除指定位置/末尾的字符 |
| drain | 删除指定范围, 返回被删内容的迭代器 |
| clear | 清空字符串 |
| retain | 只保留符合条件的字符 |
| truncate | 截断到指定字节长度 |
这些方法都是修改原字符串, 所以仅 String 可用
rust
fn main() {
let mut string = String::from("hello");
// 1.push: 追加一个字符
string.push('-');
println!("{string}"); // hello-
// 2.push_str: 追加一个字符串
string.push_str("rust");
println!("{string}"); // hello-rust
// 3.insert: 指定位置插入一个字符
string.insert(0, '-');
println!("{string}"); // -hello-rust
// 4.insert_str: 指定位置插入一个字符串
string.insert_str(0, "test");
println!("{string}"); // test-hello-rust
// 5.extend: 追加一个字符集合(Vec<char> 或迭代器)
string.extend(vec!['-', 'a', 'b', 'c']);
println!("{string}"); // test-hello-rust-abc
}rust
fn main() {
// 1.remove: 根据索引删除一个字符, 返回被删除的字符
let mut string = String::from("hello rust");
let removed_char = string.remove(3);
println!("{removed_char}"); // l
println!("{string}"); // helo rust
// 2.pop: 删除最后一个字符, 返回 Option<char>
let mut string = String::from("hello rust");
let removed_char = string.pop().unwrap();
println!("{removed_char}"); // t
println!("{string}"); // hello rus
// 3.clear: 清空字符串
let mut string = String::from("hello rust");
string.clear();
println!("{string:?}"); // ""
// 4.drain: 删除指定范围(索引 3..=5), 返回被删除字符的迭代器
let mut string = String::from("hello rust");
let removed: Vec<_> = string.drain(3..=5).collect();
println!("{removed:?}"); // ['l', 'o', ' ']
println!("{string}"); // herust
// 5.retain: 只保留符合条件的字符, 其他全部删除
let mut string = String::from("你好rust");
string.retain(|c| c.is_ascii_alphabetic());
println!("{string}"); // rust ("你好" 不是 ASCII 字符被删掉)
// 6.truncate: 截断到指定字节长度(6 字节 = "你好" 两个字)
let mut string = String::from("你好rust");
string.truncate(6);
println!("{string}"); // 你好
}注意点
所有修改操作(push/insert/remove/clear 等)都是 String 独有的, &str 不可变, 不能使用这些方法
截取/修剪
| 方法 | 作用 |
|---|---|
切片语法 &str[..] | 按字节索引截取(越界会 panic) |
| get | 安全截取, 越界返回 None |
| split_at | 按索引分割成两半, 返回元组 |
| strip_prefix / strip_suffix | 移除前缀/后缀, 返回 Option |
| trim / trim_start / trim_end | 去除开头/结尾的空白字符 |
| trim_matches | 去除开头/结尾匹配到的字符 |
这些方法 String 和 &str 都可以使用
rust
fn main() {
// 0.通过切片语法截取, 返回截取的字符串, 越界会 panic
let str = "hello world";
let sub_str = &str[3..5];
// let sub_str2 = &str[3..100]; // 越界会 panic
println!("1: {sub_str}"); // lo
// 1.get: 安全截取, 返回 Option, 越界返回 None 不会 panic
let str = "hello world";
let sub_str = str.get(4..=6).unwrap();
println!("2: {str}"); // hello world (原字符串没变)
println!("3: {sub_str}"); // o w
// 2.split_at: 按索引位置分割(左闭右开), 返回 (前面部分, 后面部分)
let items = str.split_at(5);
println!("4: {items:?}"); // ("hello", " world")
// 3.strip_prefix: 移除前缀, 有前缀返回 Some(剩余部分), 没有返回 None
let str = "hello world";
let sub_str = str.strip_prefix("hello").unwrap();
let non_str = str.strip_prefix("abcd");
println!("5: {sub_str:?}"); // " world" (注意前面有空格)
println!("6: {non_str:?}"); // None
// 4.strip_suffix: 移除后缀, 有后缀返回 Some(剩余部分), 没有返回 None
let str = "hello world";
let sub_str = str.strip_suffix("world").unwrap();
let non_str = str.strip_suffix("abcd");
println!("7: {sub_str:?}"); // "hello "
println!("8: {non_str:?}"); // None
// 5.trim/trim_start/trim_end: 删除开头结尾的空白字符(包括换行和空格)
// 返回一个新的字符串, 不影响原来的字符串
let str = "\n hello world \n";
let trim_str1 = str.trim(); // 开头结尾都删除
let trim_str2 = str.trim_start(); // 只删开头
let trim_str3 = str.trim_end(); // 只删结尾
println!("9: {trim_str1:?}"); // "hello world"
println!("10: {trim_str2:?}"); // "hello world \n"
println!("11: {trim_str3:?}"); // "\n hello world"
// 6.trim_matches/trim_start_matches/trim_end_matches
// 删除开头结尾匹配到的字符, 可以传闭包(参数是 char) 或 &str
let str = "abcabc-hello rust-abcabc";
let trim_str1 = str.trim_matches(|c| c == 'a' || c == 'b' || c == 'c');
let trim_str2 = str.trim_start_matches("abc");
let trim_str3 = str.trim_end_matches("abc");
println!("12: {trim_str1:?}"); // "-hello rust-"
println!("13: {trim_str2:?}"); // "-hello rust-abcabc"
println!("14: {trim_str3:?}"); // "abcabc-hello rust-"
}注意点
切片语法 &str[..] 是按字节截取的, 索引必须落在字符边界上, 切到汉字中间或者越界都会 panic, 不确定时用 get()
分割字符串
| 方法 | 作用 |
|---|---|
| split / rsplit | 按分隔符分割(从左/从右) |
| split_inclusive | 分割并保留分隔符 |
| split_once / rsplit_once | 只分割成两半, 返回 Option |
| splitn / rsplitn | 最多分割成 n 份 |
| split_whitespace | 按空白字符分割(Unicode 空白) |
| lines | 按换行符分割 |
rust
fn main() {
// split/rsplit/split_inclusive
// split: 从左往右分割, 不保留分隔符
// rsplit: 从右往左分割, 不保留分隔符
// split_inclusive: 从左往右分割, 保留分隔符
// 返回一个迭代器
let str = "foo@bar@baz";
let iter = str.split("@");
for item in iter {
println!("{item}"); // foo bar baz
}
let iter2 = str.rsplit("@");
for item in iter2 {
println!("{item}"); // baz bar foo
}
let str = "foo@bar@baz";
let iter3 = str.split_inclusive("@");
for item in iter3 {
println!("{item}"); // foo@ bar@ baz
}
}rust
fn main() {
// split_once/rsplit_once: 只按分隔符分割成两半
// 返回 Option<(分隔符前的字符, 分隔符后的字符)>
// 没有找到分隔符则返回 None
let str = "@abc$def#";
let non_str = str.split_once("&");
println!("1: {non_str:?}"); // None
let (before, after) = str.split_once("$").unwrap();
println!("2: before: {before}, after: {after}"); // before: @abc, after: def#
let (before, after) = str.split_once("@").unwrap();
println!("3: before: {before}, after: {after}"); // before: "", after: abc$def#
let (before, after) = str.split_once("#").unwrap();
println!("4: before: {before}, after: {after}"); // before: @abc$def, after: ""
}rust
fn main() {
// split_at/split_at_mut: 按照索引位置将字符串切分为两份
// split_at: 返回不可变引用
let str = "hello-world";
let (before, after) = str.split_at(5);
println!("1: before: {before}"); // hello
println!("2: after: {after}"); // -world
// split_at_mut: 返回可变引用, 可以修改两半(需要 &mut str)
let str: &mut str = &mut String::from("hello-world");
let (before_mut, after_mut) = str.split_at_mut(5);
before_mut.make_ascii_uppercase();
after_mut.make_ascii_uppercase();
println!("3: before: {before_mut}"); // HELLO
println!("4: after: {after_mut}"); // -WORLD
}rust
fn main() {
// splitn/rsplitn: 最多分割成 n 份
// splitn: 从左至右分割
let str = "foo bar baz hello world";
let iter = str.splitn(3, ' ');
for item in iter {
println!("{item}"); // foo / bar / baz hello world (剩下的全部放最后一份)
}
// rsplitn: 从右至左分割
let iter2 = str.rsplitn(2, ' ');
for item in iter2 {
println!("{item}"); // world / foo bar baz hello
}
}rust
fn main() {
// split_whitespace: 按照所有 Unicode 空白字符分割
// rsplit_ascii_whitespace: 仅按照 ASCII 空白(空格/换行等)分割
let text = "Hello\u{3000}World"; // \u{3000} 是中文全角空格(Unicode 空白)
let unicode_parts: Vec<&str> = text.split_whitespace().collect();
let ascii_parts: Vec<&str> = text.split_ascii_whitespace().collect();
println!("split_whitespace: {:?}", unicode_parts); // ["Hello", "World"]
println!("split_ascii_whitespace: {:?}", ascii_parts); // ["Hello\u{3000}World"]
// lines: 按照换行符(\n)分割
let str = "Hello\nWorld";
let lines = str.lines().collect::<Vec<&str>>();
println!("lines: {:?}", lines); // ["Hello", "World"]
}链接字符串
所有返回迭代器的函数, 都可以通过 collect 方法收集为 Vec<&str>, 而这个类型可以使用 join 方法
rust
fn main() {
let str = "Hello\nWorld";
// lines 按行分割后, 再用 join 拼接回去(换成逗号)
let lines = str.lines().collect::<Vec<&str>>().join(",");
println!("{lines}"); // Hello,World
// 分割后的集合也可以用 join 拼回去
let parts = str.split("l").collect::<Vec<&str>>();
println!("{parts:?}"); // ["He", "", "o\nWor", "d"]
println!("{}", parts.join("L")); // HeLLo\nWorLd
}迭代器遍历
rust
// chars: 字符迭代器
// char_indices: 字符位置迭代器
// bytes: 字节迭代器
// split: 分割迭代器
// match_indices: 匹配迭代器
// lines: 行(\n分割)迭代器
fn main() {
let str = "hi 你好";
// chars: 遍历每个字符
for c in str.chars() {
println!("{c}"); // h i 你 好
}
// char_indices: 遍历每个字符及其字节位置
for (i, c) in str.char_indices() {
println!("[{i}] {c}"); // [0] h / [1] i / [2] (空格) / [3] 你 / [6] 好
}
// bytes: 遍历每个字节
for b in str.bytes() {
println!("{b}"); // 104 105 32 228 189 160 229 165 189
}
}原始字符串字面量 Raw String Literals
r"": 内容中不能包含双引号", 因为双引号会被识别为字符串的结束边界r#""#: 内容中可以包含双引号", 但是不能出现"#因为会识别为结束标记r##""##: 内容中可以包含双引号"和"#但是不能包含"##因为会识别为结束标记r###""###: 根据上面内容递推, 这个不能包含"###, 因为会识别为结束标记
rust
fn main() {
let raw_str = r#"helloa
"world" \n test \t example
haha"#;
// 原始字符串字面量: 就是不解析 \n \t 等转义字符, 直接原样输出
// helloa
// "world" \n test \t example
// haha
println!("{}", raw_str);
}正则表达式
前面学过的
contains/find/starts_with都只能匹配固定的字符串, 如果要做模糊匹配(比如: 是否是手机号、是否包含数字、提取日期), 就需要用到正则表达式
标准库中没有提供正则表达式相关内容, 可以使用 regex crate
添加依赖与编译
toml
# Cargo.toml
[dependencies]
regex = "1.13.0"| 方法 | 作用 |
|---|---|
| Regex::new | 编译正则表达式, 返回 Result |
| Regex::is_match | 判断字符串是否匹配, 返回 bool |
| Regex::find | 查找第一个匹配, 返回 Option<Match> |
| Regex::captures | 查找第一个匹配并提取分组 |
| Regex::find_iter | 迭代所有匹配 |
| Regex::replace | 替换匹配的内容 |
rust
use regex::Regex;
fn main() {
// Regex::new: 编译正则表达式
// 注意: 返回的是 Result, 因为正则写错了会编译失败
let re = Regex::new(r"^\d{11}$").expect("正则编译失败");
// is_match: 判断字符串是否匹配, 返回 bool
println!("{}", re.is_match("13800138000")); // true
println!("{}", re.is_match("12345")); // false
println!("{}", re.is_match("1380013800a")); // false (最后一位不是数字)
}小技巧
使用 原始字符串字面量(Raw String Literals)写正则, 这样 \d \w 等反斜杠不需要转义
基本语法
Rust regex crate 使用的正则语法(和其他语言的正则大致相同):
| 语法 | 作用 | 示例 |
|---|---|---|
| 普通字符 | 匹配字符本身 | a 匹配 "a" |
. | 匹配任意字符(除换行) | a.c 匹配 "abc" |
[abc] | 字符集合 | [a-z0-9] 匹配小写字母或数字 |
[^abc] | 取反字符集合 | [^0-9] 匹配非数字 |
\d \w \s | 数字/单词字符/空白字符 | \d+ 匹配数字 |
\D \W \S | 以上三种的取反 | \D 匹配非数字 |
* + ? | 0次或多次/1次或多次/0次或1次 | ab*c ab+c ab?c |
{n} {n,} {n,m} | 出现次数 | \d{11} 匹配 11 位数字 |
^ $ | 匹配开头/结尾 | ^\d+$ 全是数字 |
(...) | 分组(可以提取) | (ab)+ 匹配 "abab" |
(?:...) | 非捕获分组 | (?:ab)+ 不提取分组 |
| | 或 | cat|dog 匹配 cat 或 dog |
\b | 单词边界 | \bfoo\b 匹配单词 foo |
判断是否匹配 is_match
rust
use regex::Regex;
fn main() {
// 判断字符串是否全是数字
let re = Regex::new(r"^\d+$").expect("正则编译失败");
println!("{}", re.is_match("123456")); // true
println!("{}", re.is_match("12a456")); // false
println!("{}", re.is_match("")); // false (至少需要一个数字)
// 判断是否包含数字(不加 ^ $ 就是"包含"匹配)
let re = Regex::new(r"\d+").expect("正则编译失败");
println!("{}", re.is_match("我的电话是 10086")); // true
}查找匹配的内容 find
rust
use regex::Regex;
fn main() {
let re = Regex::new(r"\d+").expect("正则编译失败");
let text = "order: 10086, price: 99";
// find: 查找第一个匹配, 返回 Option<Match>
if let Some(m) = re.find(text) {
println!("匹配内容: {}", m.as_str()); // 匹配内容: 10086
println!("开始位置: {}", m.start()); // 开始位置: 7
println!("结束位置: {}", m.end()); // 结束位置: 12
}
}分组捕获 captures
用 (...) 分组后, captures 可以提取每一组的内容
rust
use regex::Regex;
fn main() {
// 三个分组: 年-月-日
let re = Regex::new(r"(\d{4})-(\d{2})-(\d{2})").expect("正则编译失败");
let text = "今天是 2026-06-01";
if let Some(caps) = re.captures(text) {
println!("caps: {:#?}", caps);
// caps: Captures(
// {
// 0: 10..20/"2026-06-01",
// 1: 10..14/"2026",
// 2: 15..17/"06",
// 3: 18..20/"01",
// },
// )
// caps[0] 是整个匹配, caps[1]/[2]/[3] 是各组
println!("完整匹配: {}", &caps[0]); // 完整匹配: 2026-06-01
println!("年: {}", &caps[1]); // 年: 2026
println!("月: {}", &caps[2]); // 月: 06
println!("日: {}", &caps[3]); // 日: 01
}
}迭代所有匹配项 find_iter
rust
use regex::Regex;
fn main() {
let re = Regex::new(r"\d+").expect("正则编译失败");
let text = "今年是2026年, 有12个月, 365天";
// find_iter: 迭代所有匹配
for m in re.find_iter(text) {
println!("{}", m.as_str()); // 2026 / 12 / 365
}
// captures_iter: 迭代所有匹配, 每个都带分组
let re = Regex::new(r"(\d+)-(\d+)").expect("正则编译失败");
for caps in re.captures_iter("a 1-2 b 3-4") {
println!("{} {}", &caps[1], &caps[2]); // 1 2 / 3 4
}
}正则替换 replace_all
rust
use regex::Regex;
fn main() {
// replace_all: 替换所有匹配的内容
let re = Regex::new(r"\d+").expect("正则编译失败");
let new = re.replace_all("订单 10086 号", "***");
println!("{new}"); // 订单 *** 号
// 替换时可以用 $1 $2 引用分组
let re = Regex::new(r"(\d{4})-(\d{2})").expect("正则编译失败");
let new = re.replace("2026-06", "$1年$2月");
println!("{new}"); // 2026年06月
}经典场景: 校验邮箱与手机号
rust
use regex::Regex;
fn main() {
// 校验邮箱(简化的规则)
let email_re = Regex::new(r"^[\w.+-]+@[\w-]+(\.[\w-]+)+$").expect("正则编译失败");
let emails = ["test@example.com", "user.name@mail.qq.com", "not-an-email"];
for email in emails {
println!("{email}: {}", email_re.is_match(email));
// test@example.com: true
// user.name@mail.qq.com: true
// not-an-email: false
}
// 校验手机号(1 开头, 第二位 3-9, 共 11 位数字)
let phone_re = Regex::new(r"^1[3-9]\d{9}$").expect("正则编译失败");
println!("{}", phone_re.is_match("13800138000")); // true
println!("{}", phone_re.is_match("12345678901")); // false (第二位不是 3-9)
}注意点
注意点
- 正则编译有开销: 不要在循环里反复
Regex::new, 应该提前编译好复用 \w默认匹配 Unicode:\w会匹配中文, 只想匹配 ASCII 用[a-zA-Z0-9_]或(?-u:\w)- 不支持回溯: regex crate 出于性能和安全考虑, 不支持反向引用(
\1)等回溯特性, 需要的话用 fancy-regex - 正则写错会返回 Err:
Regex::new返回 Result, 不要直接 unwrap 裸奔(至少 expect 一个提示信息)
rust
use regex::Regex;
fn main() {
// 正则表达式写错(括号没有闭合), Regex::new 返回 Err
let result = Regex::new(r"([a-z]");
match result {
Ok(_) => println!("编译成功"),
Err(e) => println!("编译失败: {e}"), // 编译失败: regex parse error: unclosed group
}
}相关开源库
- regex: Rust 最常用的正则库, 性能极好
- lazy-regex: 提供过程宏, 编译时检查语法,首次使用时惰性编译, 解决了 regex 的重复编译问题
- fancy-regex: 支持回溯/反向引用等高级特性
- once_cell / lazy_static: 把正则编译一次, 全局复用
rust
use once_cell::sync::Lazy;
use regex::Regex;
// 程序启动时只编译一次, 之后全局复用, 不需要每次使用都 Regex::new
static PHONE_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"^1[3-9]\d{9}$").unwrap());
fn main() {
println!("{}", PHONE_RE.is_match("13800138000")); // true
println!("{}", PHONE_RE.is_match("123456")); // false
}