Skip to content

类型理解

他们二者可以相互转换, 因为本质都是用于描述"字符串"这个概念的

  • 字符串切片 &str 是最原始的字符串切片类型, 不可变引用, 固定大小(胖指针+长度)
  • 可增长字符串 String 是标准库模块中的结构体, 拥有所有权, 用其他编程语言的话说他是一个类一个引用类型

关键差异说明

  1. String 独有特点:

    • 所有修改操作(push、insert、remove、pop等)
    • 容量管理(capacity、reserve、shrink等)
    • 所有权转移(into_bytes、into_boxed_str等)
    • 原地修改(make_ascii_*、replace_range等)
  2. 共有但行为不同:

    • to_string() / to_owned(): String 克隆自身, &str 创建新 String
    • as_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_strString 借用为 &str
as_mut_strString 借用为可变 &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_bytesString 转为 Vec<u8>(转移所有权)
String::from_utf8Vec<u8> 转为 String(可能失败)
String::from_utf8_lossyVec<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::capacityString 的容量
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)
}

注意点

注意点

  1. 正则编译有开销: 不要在循环里反复 Regex::new, 应该提前编译好复用
  2. \w 默认匹配 Unicode: \w 会匹配中文, 只想匹配 ASCII 用 [a-zA-Z0-9_](?-u:\w)
  3. 不支持回溯: regex crate 出于性能和安全考虑, 不支持反向引用(\1)等回溯特性, 需要的话用 fancy-regex
  4. 正则写错会返回 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
}

Released under the MIT License.