Rust 基础语法(一):变量、数据类型与字符串
📚 Rust 课程系列
Rust 是一门注重内存安全、性能和并发性的系统编程语言。本篇聚焦变量、数据类型与字符串–Rust 类型系统与所有权机制的地基,涵盖变量绑定与常量、标量与复合类型、字符串的 UTF-8 设计及类型转换。
注释与文档注释
1 | |
💡 提示:
cargo doc --open会把///文档注释渲染成 HTML 文档;文档注释里的示例代码块会被cargo test当作文档测试(doc test)实际编译运行–这是保证示例代码不腐化的利器,开源库广泛使用。⚠️ 注意:Rust 注释规范约定:解释"为什么"用普通注释(
//),解释"做什么、怎么用"用文档注释(///)。文档注释是公共 API 的一部分,会出现在 docs.rs 上。
变量与常量
| 声明方式 | 可变性 | 类型标注 | 作用域 | 典型用途 |
|---|---|---|---|---|
let x = 5; | 不可变(默认) | 可推断 | 块级 | 一般绑定 |
let mut y = 10; | 可变 | 可推断 | 块级 | 需要修改的值 |
const MAX: u32 = 100; | 不可变 | 必须标注 | 可全局 | 编译期常量 |
static G: &str = "hi"; | 不可变(默认) | 必须标注 | 全局 | 静态生命周期值 |
💡 提示:Rust 变量默认不可变–这是与 C++/Go/Java 最大的区别之一。不可变让编译器能做更多优化,也从根本上杜绝"意外修改"类 bug。需要修改时显式写
mut,让意图一目了然。
let 解构绑定
let 不仅能绑定单个变量,还能直接解构元组、数组与结构体–这是 Rust 模式匹配能力在绑定语法上的体现(完整模式匹配见《模式匹配》)。
1 | |
💡 提示:数组解构中的
..是通配模式,可忽略任意数量的元素。解构在函数参数中同样适用:fn f((x, y): (i32, i32))直接按位置取参数,省去中间变量。
遮蔽(Shadowing)
1 | |
🔄 对比:遮蔽 ≠ 可变赋值。遮蔽创建新绑定(可改变类型),
mut修改现有绑定(类型固定)。C++ 中同名变量在同级作用域会报错,Rust 允许遮蔽。⚠️ 注意:遮蔽常用于值转换链(如
let s = s.trim();),但过度使用会降低可读性。同一块内遮蔽超过 2-3 次时应换用更有意义的变量名。
忽略变量与未使用前缀
1 | |
⚠️ 注意:
_与_x的语义差异常被忽略。let _ = lock.lock()会让锁守卫立即释放,几乎总是 bug;而let _x = lock.lock()则让锁存活到块结束。处理MutexGuard、Rc等有副作用的类型时,务必用_x而非_。
作用域与块级 drop
Rust 变量遵循 RAII(Resource Acquisition Is Initialization)模式:变量在离开作用域时自动调用 drop,释放其拥有的资源(堆内存、文件句柄、锁等),无需手动释放。
1 | |
💡 提示:drop 顺序与声明顺序相反(LIFO),这一规则保证后创建的变量(可能引用了先创建的)先被释放。作用域与所有权的完整推导见所有权、借用与生命周期。
常量与静态变量
1 | |
⚠️ 注意:
static mut是 unsafe 的–多线程并发访问会导致数据竞争。需要全局可变状态时,用Atomic*类型或Mutex封装,或用once_cell::sync::Lazy延迟初始化。
const fn 扩大了编译期求值的范围,但仍有严格限制:
1 | |
| 特性 | const | static |
|---|---|---|
| 内存布局 | 内联到使用处 | 固定地址,全局唯一 |
| 取地址 | 每处可能不同 | 程序生命周期内不变 |
| 求值时机 | 编译期内联 | 编译期初始化 |
| 可变性 | 不可变 | static mut(unsafe) |
1 | |
💡 提示:需要编译期常量用
const,需要固定地址或全局可变状态用static。const fn的限制在 Rust 版本迭代中逐步放宽,但堆分配、浮点比较等仍不可用。
类型别名与字面量后缀
1 | |
数据类型
标量类型速查
| 类别 | 类型 | 备注 |
|---|---|---|
| 有符号整数 | i8 i16 i32 i64 i128 | 默认推断为 i32 |
| 无符号整数 | u8 u16 u32 u64 u128 | 字节操作常用 u8 |
| 架构相关 | isize / usize | 与指针同宽,索引/长度用 usize |
| 浮点数 | f32 f64 | 默认推断为 f64 |
| 布尔 | bool | true / false,占 1 字节 |
| 字符 | char | Unicode 标量值,占 4 字节 |
💡 提示:
char是 Unicode 标量值(4 字节),不是 C 的 1 字节char。'😊'是合法的char,但字符串内部是 UTF-8 编码,一个 Unicode 字符可能占 1-4 字节–按字节切片可能截断字符。
bool 详解
bool 看似简单,但 Rust 对它的约束比多数语言更严格:
1 | |
⚠️ 注意:Rust 不像 C/C++ 那样将整数隐式转为布尔值,也没有
as bool转换–这是刻意设计,杜绝if (x = 1)这类经典错误。判断非零必须写x != 0,让意图显式。
char 方法表
| 方法 | 返回类型 | 说明 |
|---|---|---|
is_ascii() | bool | 是否 ASCII 字符 |
is_alphabetic() | bool | 是否字母(含中文等) |
is_numeric() | bool | 是否数值字符 |
is_digit(radix) | bool | 是否指定进制数字 |
to_digit(radix) | Option<u32> | 转为数字值 |
to_lowercase() | ToLowercase | 迭代器(一对多) |
to_uppercase() | ToUppercase | 迭代器(一对多) |
len_utf8() | usize | UTF-8 编码字节数 |
len_utf16() | usize | UTF-16 编码单元数 |
encode_utf8(buf) | &mut str | 写入缓冲区 |
1 | |
💡 提示:
to_lowercase/to_uppercase返回迭代器而非char,是因为 Unicode 大小写转换可能一对多–例如德语ß大写为SS(两个字符)。len_utf8()在编译期即可求值,常用于预分配缓冲区。
类型内存占用
1 | |
💡 提示:
&str、&[T]等切片引用是胖指针(fat pointer),除了数据地址还携带长度信息,所以是普通引用的两倍宽。size_of::<T>()在编译期即可求值,是理解零成本抽象与内存布局的入门工具。
整数字面量与进制
| 形式 | 示例 | 值 |
|---|---|---|
| 十进制 | 98_222 | 98222 |
| 十六进制 | 0xff | 255 |
| 八进制 | 0o77 | 63 |
| 二进制 | 0b1111_0000 | 240 |
| 字节字面量 | b'A' | 65(类型为 u8) |
💡 提示:
_分隔符可放在字面量任意位置提高可读性(1_000_000、0b1101_0010),编译器直接忽略。字节字面量b'A'只适用于 ASCII 字符,类型是u8而非char。
整数溢出行为
Rust 对整数溢出的处理是两阶段的,这是其他语言少有的精细设计:
- Debug 模式:溢出时 panic(如
255u8 + 1),帮助尽早发现 bug - Release 模式:按二进制补码回绕(wrapping),
255u8 + 1->0,静默不报错
需要明确的溢出语义时,用对应的显式方法族:
1 | |
⚠️ 注意:不要依赖 release 模式的回绕行为–那只是未定义语义的"托底",不是特性。需要回绕语义时显式写
wrapping_*;需要检测溢出用checked_*(返回Option);图像/音频处理常用saturating_*。若想让 release 也 panic,可在Cargo.toml中设置[profile.release] overflow-checks = true(有少量性能开销)。
数值方法族总览
| 类别 | 方法 | 说明 |
|---|---|---|
| 算术 | abs() | 绝对值 |
| 算术 | signum() | 符号值(-1/0/1) |
| 算术 | pow(exp) | 幂运算 |
| 算术 | powi(exp) | 整数幂(i32 指数) |
| 算术 | powu(exp) | 无符号幂(u32 指数) |
| 算术 | sqrt() / cbrt() | 平方根 / 立方根(浮点) |
| 算术 | min(b) / max(b) | 最小 / 最大值 |
| 算术 | clamp(lo, hi) | 钳位到区间 |
| 算术 | div_euclid(b) | 欧几里得除法(向下取整) |
| 算术 | rem_euclid(b) | 欧几里得余数(非负) |
| 位运算 | count_ones() | 1 的位数 |
| 位运算 | trailing_zeros() | 末尾 0 的个数 |
| 位运算 | leading_zeros() | 前导 0 的个数 |
| 位运算 | swap_bytes() | 字节序交换 |
| 位运算 | to_be() / to_le() | 转大端 / 小端 |
| 位运算 | rotate_left(n) | 循环左移 |
| 位运算 | rotate_right(n) | 循环右移 |
1 | |
💡 提示:
div_euclid/rem_euclid提供与 Python///%一致的向下取整语义,处理负数除法时比默认的向零取整更直观。位运算方法在协议解析、哈希、加密等场景常用。
运算符是方法调用的语法糖
Rust 的 +、-、== 等运算符并非内建魔法,而是 std::ops 模块中 trait 方法的语法糖;反过来,方法调用 a.abs() 本身也是完全限定语法 i32::abs(a) 的糖。
| 表达式 | 语法糖展开 | 对应 trait |
|---|---|---|
a + b | a.add(b) | Add |
a - b | a.sub(b) | Sub |
a * b | a.mul(b) | Mul |
a / b | a.div(b) | Div |
a % b | a.rem(b) | Rem |
a += b | a.add_assign(b) | AddAssign |
-a | a.neg() | Neg |
!a | a.not() | Not |
a == b | a.eq(&b) | PartialEq |
a < b | a.lt(&b) | PartialOrd |
1 | |
💡 提示:对整数、浮点等基本类型,编译器会把运算符直接编译为单条机器指令,不经过 trait 调用–语法糖零开销。但语义上二者完全等价,因此自定义类型只要实现对应 trait 即可重载运算符(如实现
Add后Point + Point合法),详见类型系统:泛型、trait 与多态。⚠️ 注意:方法调用糖还包含自动引用/解引用规则–
s.len()实际展开为String::len(&s),编译器自动补上&、&mut或*。这就是为什么String的方法能直接用在&String上。完整推导见智能指针、迭代器与闭包。
自增与自减:Rust 为何没有 ++/--
从 C/C++/Java 转来的开发者常会下意识地写 i++ 或 --count,结果 Rust 编译器直接报错–Rust 没有 ++ / -- 运算符。自增自减改用复合赋值:
1 | |
🔄 对比:C/C++/Java 的
++/--区分前缀(++i,返回新值)与后缀(i++,返回旧值),二者语义不同且容易在复杂表达式中混淆。Rust 刻意省去这对运算符,让"自增一步"统一写成i += 1,意图明确、无歧义。⚠️ 注意:
+= 1本质是AddAssigntrait 的方法调用(i.add_assign(&1)),返回单元类型()–它不能嵌入表达式求值。这从根上杜绝了a[i++] = i++这类依赖求值顺序的经典 bug。
若需要"取旧值并自增"(模拟后缀 i++),分两步即可:
1 | |
💡 提示:需要"取旧值并推进"的场景(如生成自增 ID),更地道的做法是用迭代器:
std::iter::repeat_with配合计数器,或直接Iterator::enumerate/(0..).zip(...),把可变状态收敛到迭代器中,避免散落各处的i += 1。
溢出处理四族对照
| 方法族 | 溢出行为 | 返回类型 |
|---|---|---|
wrapping_add | 回绕 | T |
checked_add | 返回 None | Option<T> |
saturating_add | 钳位到边界 | T |
overflowing_add | 回绕 + 标志 | (T, bool) |
1 | |
💡 提示:四族方法对所有算术运算(
add/sub/mul/div/rem/neg/shl/shr)均有对应版本。选择策略:检测溢出用checked_*,图像/信号处理用saturating_*,密码学/哈希用wrapping_*,同时需要值与标志用overflowing_*。
浮点数的特殊性
1 | |
⚠️ 注意:
f32/f64只实现了PartialOrd而非Ord–NaN 的存在破坏了全序关系(total order)。因此浮点数不能用作BTreeMap的键,数组也不能直接.sort()。排序要用sort_by(|a, b| a.partial_cmp(b).unwrap()),或用ordered_floatcrate 的OrderedFloat包装类型。💡 提示:金融计算不要用
f64,改用rust_decimal等十进制 crate,或以"分"为单位的整数。浮点精度问题在所有语言中都存在,Rust 只是把"不可全序"这一事实在类型系统中暴露了出来。
单元类型 () 与 Never 类型 !
1 | |
🔄 对比:
()类似 C++ 的void,但它是真正的类型且只有一个值(),可以参与泛型–例如Result<(), Error>表示"只关心成功与否"。!表示发散(diverging),可强制转换为任意类型,因此match分支中写panic!()不会破坏类型统一:
1
2
3
4let x: i32 = match some_option {
Some(v) => v,
None => panic!("missing"), // ! 可转为 i32,分支类型一致
};
类型推断与默认类型
1 | |
💡 提示:Rust 的推断是全函数体的–变量类型可由后续使用反推:
1
2let mut v = Vec::new();
v.push(1i64); // 编译器回推 v: Vec<i64>但函数签名、结构体字段等跨函数边界必须显式标注类型–这是刻意设计,保证错误信息本地化、API 稳定且文档自明。
复合类型
1 | |
🔄 对比:切片
&[T]/&str类似 Go 的 slice header(指针+长度),但不包含容量信息。Rust 切片是纯"视图",不能 append。💡 提示:数组 vs Vec 的选择–长度固定且小用数组(栈分配,零开销),需要动态增长用
Vec(用with_capacity减少重分配)。切片的完整用法见《切片(Slice)》。
元组详解
1 | |
💡 提示:
()是唯一的单元类型值,常作为"无返回值"的占位(如Result<(), Error>)。单元素元组(x,)的尾逗号不可省–(42)是括号包裹的表达式而非元组。元组结构体(struct Point(i32, i32))是元组与结构体的混合体,详见《结构体》。
array vs &slice vs Vec 对比
| 特性 | [T; N] 数组 | &[T] 切片 | Vec<T> |
|---|---|---|---|
| 内存位置 | 栈(默认) | 借用外部数据 | 堆 |
| 长度 | 编译期固定 | 运行时固定 | 可动态增长 |
| 所有权 | 拥有数据 | 不拥有 | 拥有数据 |
| 容量 | 无 | 无 | 有 capacity |
| 典型用途 | 固定大小数据 | 函数参数通用 | 动态集合 |
数组实用技巧与 const generics
1 | |
💡 提示:const generics(Rust 1.51+)让数组长度成为类型参数,标准库因此能为任意长度的
[T; N]统一实现 trait。早期 Rust 只覆盖长度 0…=32 的数组,这一历史遗留问题已被 const generics 彻底解决。
字符串与类型转换
Rust 的字符串设计比大多数语言更细致–核心原因是 UTF-8 编码与零成本抽象的矛盾。理解 String、&str、char、u8 四者的关系,是掌握 Rust 字符串的关键。
字符串类型总览
| 类型 | 内存 | 所有权 | 编码 | 典型来源 |
|---|---|---|---|---|
String | 堆分配,可增长 | 拥有数据 | UTF-8 | String::from()、to_string()、format! |
&str | 借用(栈上胖指针:指针+长度) | 不拥有 | UTF-8 | 字面量 "hello"、&s[..]、s.as_str() |
[u8] / &[u8] | 原始字节 | - | 无保证 | s.as_bytes()、文件 I/O、网络数据 |
Vec<char> | 堆分配 | 拥有 | Unicode 码点 | s.chars().collect() |
OsStr / OsString | 平台相关 | - | 平台原生 | 文件路径、环境变量 |
CString / CStr | 兼容 C | - | NUL 结尾字节 | FFI 调用 |
💡 提示:日常开发 99% 的时间只需关注
String和&str。&str是String的借用视图–类似&[T]与Vec<T>的关系(切片详见《切片(Slice)》)。函数参数优先用&str(更通用,可接受&String的自动解引用),返回值按需选择:有所有权返回String,借用返回&str。
1 | |
String 的堆增长机制
String 内部是 Vec<u8>,堆上存储 UTF-8 字节,并维护三个字段:指针、长度(len)和容量(capacity)。
1 | |
💡 提示:
push/push_str在容量足够时零分配(仅更新len);超出容量时触发重分配(新容量通常翻倍,拷贝旧数据到新堆块)。高频拼接场景用with_capacity预分配可显著减少重分配次数。shrink_to_fit用于"先大量 push 再只读"的模式,释放多余堆内存。
String 与 &str 的内存布局对比–&str 是栈上胖指针(ptr + len),String 是三元组(ptr + len + cap),二者均指向堆上的 UTF-8 字节序列:
graph TB
subgraph 栈 Stack
S["&str (16B)<br/>ptr | len"]
T["String (24B)<br/>ptr | len | cap"]
end
subgraph 堆 Heap
H["UTF-8 字节序列<br/>h e l l o"]
end
S -->|借用视图| H
T -->|拥有数据| H&str 借用的数据可变性
&str 是不可变借用–通过它永远无法修改底层数据。这一点与绑定本身的可变性是两回事,初学者很容易混淆:
1 | |
字符串字面量的类型是 &'static str,数据直接嵌入二进制的只读段,天然不可变:
1 | |
那 Rust 有没有"可变的字符串借用"?有–&mut str 存在但极少用,它只能原地等长修改,不能改变长度:
1 | |
⚠️ 注意:改变长度必须通过拥有者
String本身(push_str、truncate等)。&mut str的设计保证切片的长度字段永远与数据一致–借用的"视图"不可能越界。
借用期间还受借用规则约束:只要 &str 还存活,String 就不能被修改:
1 | |
🔄 对比:C++ 的
string_view/ Go 的子串可以共享底层数据,而原字符串随时可改,可能读到"半新半旧"的数据。Rust 用借用规则在编译期杜绝了这类"迭代器失效"问题–要么共享只读视图,要么独占修改权,二者不可兼得。可变性规则的完整推导见所有权、借用与生命周期。
&mut str 详解:等长约束下的原地修改
&mut str 是字符串上唯一允许修改的借用形式,但受等长约束–只能改写已有字节,不能增删。它的使用场景窄而明确:原地、保长的文本处理。
获取 &mut str 有三种方式(同一时刻只能存活一个):
1 | |
str 上接收 &mut self 的方法只有三个–这份"白名单"如此之短,正是因为任何可能改变长度或破坏 UTF-8 的操作都不能放进 &mut str:
| 方法 | 作用 | 说明 |
|---|---|---|
make_ascii_uppercase() | ASCII 转大写 | 原地,O(n),长度不变 |
make_ascii_lowercase() | ASCII 转小写 | 原地,O(n),长度不变 |
as_bytes_mut() | 转 &mut [u8] | unsafe,调用者须保证结果仍是合法 UTF-8 |
需要字节级控制时(如脱敏掩码),只能走 unsafe 的 as_bytes_mut:
1 | |
对比 Unicode 大小写转换–ß 大写为 SS,长度会变,所以它只能返回新的 String,永远不可能成为 &mut str 的方法:
1 | |
作为函数参数,&mut str 适合"原地归一化"类算法:
1 | |
💡 提示:函数参数的可变借用优先用
&mut String而非&mut str–前者能力超集(可改长度),后者只在"明确承诺不改长度"的算法里才有表达力优势(如 ASCII 归一化、掩码、就地编码转换)。这与&strvs&String的推荐方向正好相反,因为只读借用追求的是通用性,可变借用追求的是能力。
字符串拼接方式对比
1 | |
🔄 对比:
+拼接类似 Java 的+但会消费左侧所有权;format!类似 Python 的 f-string / C++ 的fmt::format,不移动所有权。高频拼接优先用push_str+ 预分配容量,避免反复重分配。
字符串修改方式总结
前文涉及的修改手段,按"谁来改、能否改长度、是否新分配"三个维度归纳:
| 类别 | 操作形式 | 改变长度 | 内存行为 | 代表方法 |
|---|---|---|---|---|
| 拥有者修改 | &mut String | ✅ | 原地(超容量时重分配) | push_str、truncate、clear |
| 切片等长修改 | &mut str | ❌ | 严格原地 | make_ascii_uppercase、unsafe as_bytes_mut |
| 消费式拼接 | String + &str | ✅ | 复用左侧缓冲区 | a + " " + &b |
| 格式化生成 | format! | ✅ | 总是新分配 | format!("{a} {b}") |
| 转换生成 | 返回 String 的方法 | ✅ | 总是新分配 | to_uppercase、replace、repeat |
| 编译期拼接 | concat! 宏 | - | 零运行时开销 | concat!("a", "b") |
选择时的三步判断:
1 | |
💡 提示:记忆口诀–等长找
&mut str,变长找String,新值找format!。性能敏感的循环内只做前两类(配合with_capacity可做到全程零分配),format!与转换类方法留给最终结果的组装。
format! 格式化说明符
| 说明符 | 含义 | 示例输出 |
|---|---|---|
{} | Display trait | 42 |
{:?} | Debug trait | [1, 2] |
{:#?} | Debug(美化缩进) | 多行展开 |
{:>8} | 右对齐宽 8 | 42 |
{:<8} | 左对齐宽 8 | 42 |
{:^8} | 居中宽 8 | 42 |
{:.2} | 2 位小数 | 3.14 |
{:x} | 十六进制 | ff |
{:o} | 八进制 | 77 |
{:b} | 二进制 | 1111 |
{:e} | 科学计数法 | 1.5e3 |
{:p} | 指针地址 | 0x.. |
1 | |
💡 提示:说明符可组合,如
{:>08x}表示右对齐宽 8、前补零、十六进制。自定义类型实现Display({})或Debug({:?})trait 即可被格式化;Debug可用#[derive(Debug)]自动生成。
String 与 &str 的选择决策
| 场景 | 推荐类型 | 理由 |
|---|---|---|
| 函数参数 | &str | 通用,接受 &String / &str / 字面量 |
| 返回值(有所有权) | String | 调用方获得所有权 |
| 返回值(借用输入) | &str | 零拷贝,生命周期跟随输入 |
| 结构体字段 | String | 拥有数据,生命周期独立 |
| 全局常量 | &'static str | 字面量直接嵌入二进制 |
1 | |
⚠️ 注意:返回
&str时,其生命周期不能超过输入–编译器会强制检查。若需返回全新字符串,必须返回String(拥有堆内存)。完整的生命周期推导见所有权、借用与生命周期。
字符串的遍历
UTF-8 是变长编码(1-4 字节),因此字符串有三种遍历粒度:字节、Unicode 码点(char)、字形簇(grapheme cluster)。选错粒度是 Rust 字符串 bug 的常见来源。
1 | |
1. 按字节遍历:.bytes()
1 | |
💡 提示:
.bytes()返回Iter<u8>,每次产出u8(值类型);.as_bytes()返回&[u8],需再迭代产出&u8。大多数场景用.bytes()更方便。字节遍历适用于 ASCII 文本处理、协议解析、哈希计算等不关心字符语义的场景。
2. 按 Unicode 码点遍历:.chars()
1 | |
⚠️ 注意:
.chars()产出的是 Unicode 标量值(scalar value),不是用户感知的"字符"。组合字符如é可以是单个码点U+00E9,也可以是e+\u{0301}(e + 组合重音)两个码点–.chars()会将后者拆成两个char。印地语、泰语、阿拉伯语等文字中组合序列更常见。
3. 按字形簇遍历:需要外部 crate
1 | |
💡 提示:字形簇(grapheme cluster)最接近用户感知的"一个字符"。若需按"视觉字符"截断、计数、对齐,用
unicode-segmentationcrate。标准库不提供此功能,因字形簇边界规则极其复杂(Unicode 标准附件 #29),且多数系统编程场景不需要。
4. 遍历与索引的对比
1 | |
⚠️ 注意:Rust 故意不支持
s[i]整数索引–因为 UTF-8 变长编码下,O(1) 索引无法保证返回有效字符,且会诱导开发者写出 O(n) 循环却以为是 O(1)。这是 Rust “不让你轻易犯错” 设计哲学的体现。
5. char_indices():同时获取字节位置与字符
1 | |
💡 提示:
char_indices()是.chars()的增强版,额外提供每个字符在字节流中的起始位置。需要同时操作字符和字节偏移(如构建解析器、实现文本编辑器)时非常有用。
6. 遍历方式速查表
| 方法 | 产出类型 | 含义 | 标准库 | 时间复杂度 | 适用场景 |
|---|---|---|---|---|---|
.bytes() | u8 | 原始 UTF-8 字节 | ✅ | O(n) | 协议解析、哈希、ASCII 处理 |
.chars() | char | Unicode 标量值 | ✅ | O(n) | 字符级处理、过滤、转换 |
.char_indices() | (usize, char) | 字节偏移 + 字符 | ✅ | O(n) | 解析器、文本编辑器 |
.graphemes(true) | &str | 用户感知字形簇 | ❌ crate | O(n) | UI 显示、截断、对齐 |
.lines() | &str | 按行分割 | ✅ | O(n) | 逐行处理文本 |
.split(char) | &str | 按分隔符分割 | ✅ | O(n) | CSV、路径、词法分析 |
类型转换
1 | |
⚠️ 注意:
as转换在数值截断时静默发生(如300i32 as u8->44)。安全转换请用try_into()或From/Intotrait。From只允许无损转换(如i32 -> i64),TryFrom允许有损转换但返回Result。⚠️ 注意:
String::from_utf8会验证 UTF-8 有效性,失败返回Err(FromUtf8Error)。若数据来源可信(如刚从into_bytes()转换而来),可用unsafe { String::from_utf8_unchecked(bytes) }跳过验证–但除非性能瓶颈已确认,否则别用。from_utf8_lossy是安全折中方案,将无效字节替换为�。
From / Into / TryFrom / TryInto
| Trait | 方向 | 成功 | 失败 |
|---|---|---|---|
From<T> | T -> Self | 必成功 | - |
Into<T> | Self -> T | 必成功 | - |
TryFrom<T> | T -> Self | Ok | Err |
TryInto<T> | Self -> T | Ok | Err |
💡 提示:实现
From会自动获得Into(标准库的 blanket impl:impl<T, U> Into<U> for T where U: From<T>)。同理TryFrom自动获得TryInto。因此只需实现From/TryFrom一侧即可。
1 | |
⚠️ 注意:
From只允许无损转换(如i32 -> i64、&str -> String),TryFrom允许有损但返回Result。标准库已为数值类型间实现了完整的From/TryFrom关系网。
as 转换总览
as 是 Rust 中唯一的显式强制转换关键字,适用于数值截断与指针转换:
| 转换方向 | 示例 | 说明 |
|---|---|---|
| 整数间 | 42i32 as u8 | 截断或扩展 |
| 整数->浮点 | 42i32 as f64 | 精确 |
| 浮点->整数 | 3.7f64 as i32 | 截断小数 |
char->u32 | 'A' as u32 | 取码点 |
u32->char | 65u32 as char | 无效码点行为未定义 |
指针↔usize | p as usize | 平台相关 |
*const T->*mut T | 需 unsafe | 不变性变化 |
⚠️ 注意:
as转换在数值截断时静默发生(如300i32 as u8->44),不做任何检查。安全转换请优先用From/TryFrom;as适用于确知范围的场景(如char->u32必然无损)。u32->char的as转换在无效码点时行为未定义,应改用char::from_u32(返回Option)。
deref coercion 与 turbofish
deref coercion 让 &String 自动转为 &str,无需手动调用 .as_str()–这一机制由 Deref trait 驱动,在函数传参时自动发生:
1 | |
💡 提示:deref coercion 在
&String->&str、&Vec<T>->&[T]、&Box<T>->&T等场景自动触发,详解见智能指针、迭代器与闭包。
turbofish ::<T> 在类型推断不足时显式指定泛型参数,常与 .parse() / .collect() 配合:
1 | |
💡 提示:能靠上下文推断时就不用 turbofish(如
let v: Vec<i32> = ...)。turbofish 适用于类型推断断链的链式调用中间步骤,让代码在保持链式风格的同时补全类型信息。
小结
- 变量默认不可变,遮蔽可换类型,
mut不能;const编译期求值且必须标注类型,static有固定内存地址;const fn扩大编译期求值范围但有严格限制 let支持元组/数组解构;_立即丢弃值,_x允许未使用;变量离开作用域时按 LIFO 顺序自动 drop(RAII)- 标量类型中
bool占 1 字节且不可做算术,char是 4 字节 Unicode 标量值,二者方法族覆盖判断、转换与编码查询 - 数值方法分算术类(
abs/pow/clamp/div_euclid等)与位类(count_ones/rotate_left等),溢出处理有wrapping_*/checked_*/saturating_*/overflowing_*四族;运算符(+/==等)是std::opstrait 方法的语法糖,方法调用a.abs()则是完全限定语法i32::abs(a)的糖;Rust 没有++/--运算符,自增自减用+= 1/-= 1(AddAssign/SubAssign) - 切片
&[T]/&str是胖指针借用视图,统一数组、Vec、字符串的访问接口,详见《切片(Slice)》 String堆分配且可增长(capacity/with_capacity/shrink_to_fit),&str是栈上胖指针借用视图;函数参数优先&str,返回值按所有权需求选择- 字符串修改三分法:等长原地改找
&mut str(make_ascii_*),变长找拥有者String(push_str/truncate,配合with_capacity零分配),新值找format!/to_uppercase等转换方法;concat!编译期拼接零运行时开销 - 字符串 UTF-8 编码下遍历粒度有字节/码点/字形簇三种;
format!支持{:?}/{:x}/{:>8}等说明符 - 类型转换优先用
From/Into(无损,自动互推)与TryFrom/TryInto(有损返回Result);as适用于整数截断与char↔u32;deref coercion 让&String自动转&str
下一篇切片(Slice):序列的借用视图将讲切片的胖指针布局、创建与索引、常用方法与可变切片。


















