字符串 String 延申
str &str 与 String 区别
str是字符串字面量,是DST(动态大小类型)&str是字符串切片,是胖指针(Fat Pointer)String是可变的字符串类型,是包装了Vec<u8>的结构体
字符串的本质是
u8数组,str是字符串切片,&str是字符串切片引用,String是可变的字符串类型
| 特性 | str (字符串切片) | &str (字符串引用) | String (字符串对象) |
|---|---|---|---|
| 类型本质 | 原生字节序列 (Unsized) | 指向 str 的指针 (胖指针) | 包装了 Vec<u8> 的结构体 |
| 内存位置 | 静态区(字面量)或堆/栈上 | 栈 (存放指针和长度) | 栈 (存放元数据) + 堆 (存放内容) |
| 大小是否固定 | 不固定 (DST),编译时无法确定 | 固定 (2个字长:地址+长度) | 固定 (3个字长:地址+长度+容量) |
| 所有权 | 不拥有所有权 | 借用所有权 | 拥有所有权 |
| 可变性 | 无法直接操作 | 通常不可变 | 可变 (通过 push, insert 等) |
| 生命周期 | 取决于数据源 (字面量是 'static) | 受其指向的数据源限制 | 离开作用域时自动释放内存 (Drop) |
| 常用场景 | 作为泛型参数或底层表示 | 函数参数、匹配模式、切片操作 | 数据存储、动态拼接、修改内容 |
rust
let s = "hello"; // 字符串字面量是 &'static str
let s2 = &s; // &str
let s3 = s2.to_string(); // String
println!("{}", s3); // hello
let s4 = String::from("hello"); // String
let s5 = &s4; // &str
println!("{}", s5); // hello为什么 str 是 DST?
我们可以从计算机内存分配的角度,用一个极简的例子拆解为什么 str 是 DST(动态大小类型).
在 高级类型 章节对 DST 会有更详细的介绍
1. 编译器的困惑
假设 Rust 允许你直接定义 str 类型,请看这段代码:
rust
// 假设这段代码能跑通
fn main() {
let a: str = "hi"; // 这段数据占 2 个字节
let b: str = "rustacean"; // 这段数据占 10 个字节
}问题出在"栈内存分配"上:
在 Rust 中,函数内部的局部变量是存在栈(Stack)上的.编译器在编译代码时,必须确切知道每个函数需要多少栈空间,而且同样类型的变量必须占用相同的栈空间.
- 如果 a 是
str,编译器要分 2 字节. - 如果 b 是
str,编译器要分 10 字节.
对于编译器来说,str 这个类型本身没有一个固定的体型.它像是一块可以无限拉长或缩短的橡皮筋.编译器没法在编译阶段说:"所有的 str 统一占 X 字节".
这就是 DST 的定义: 只要一个类型在编译时无法确定具体占多少字节,它就是 DST.
2. 为什么 &str 却不是 DST?
当你加上 & 变成 &str 时,奇迹发生了:
rust
let s1: &str = "hi";
let s2: &str = "rustacean";&str 是一个胖指针(Fat Pointer),它包含了两个部分:
- 一个指向字符串内容的地址 (通常是 8 字节)
- 一个表示字符串长度的字段 (通常也是 8 字节)
无论字符串内容有多长,&str 的大小都是固定的 (16 字节).编译器在编译时就能确定 &str 的大小,所以它不是 DST.
3. 形象类比:电影 vs 电影海报
str是"电影本身": 有的电影 90 分钟,有的电影 4 小时.如果你问:"一场电影占用多少磁盘空间?"没有固定答案.这就是 DST.&str是"电影海报(带二维码)": 不管电影多长,海报的大小都是固定的(一张纸).海报上印着电影在哪看(地址)以及电影时长(长度).你可以在手里(栈上)拿很多张海报,因为它们大小一致.
总结
str是那串具体的"字节内容",由于内容长短不一,所以它是 DST(没法直接放变量里).&str是一个描述这串内容的"管理员",它有固定的大小,所以我们平时用的都是它.