Rust中String 和 &str 有什么区别?
Rust 中的 String 和 &str 是初学者最容易混淆的概念之一。简单来说,区别在于 所有权(Ownership) 和 内存管理。
可以用一个通俗的比喻:
String就像是你买下来的房子。你拥有它,可以随意装修(修改)、扩建(增加内容),但它比较重,买卖(传递)成本高。&str就像是透过窗户看房子。你只是在看(引用),不能修改房子结构,而且你看的可能只是房子的一部分(切片)。
详细对比
1. String (所有者 / 堆分配字符串)
- 定义:
String是 Rust 标准库提供的一个结构体(Struct)。 - 所有权:它拥有数据的所有权。当
String离开作用域时,数据会被自动清理(Drop)。 - 内存位置:数据存储在 堆(Heap) 上。
- 特性:
- 可变:可以修改(
push_str,pop等),前提是变量声明为mut。 - 可增长:由于在堆上,它可以在运行时动态改变大小。
- 可变:可以修改(
- 内部结构:在栈上包含三个字段:
- 指向堆内存的指针 (pointer)
- 长度 (length)
- 容量 (capacity)
2. &str (借用者 / 字符串切片)
- 定义:
&str是 Rust 的原生类型(Primitive Type),通常被称为“字符串切片”(String Slice)。 - 所有权:它不拥有数据,只是对数据的借用(Borrow)。
- 内存位置:它可以指向 堆(如果是
String的切片)或者 静态内存(如果是字符串字面量)。 - 特性:
- 不可变:你不能通过
&str去修改原数据。 - 固定大小:它只是一个视图(View),本身大小固定。
- 不可变:你不能通过
- 内部结构:它是一个“胖指针”(Fat Pointer),在栈上包含两个字段:
- 指向数据的指针 (pointer)
- 切片的长度 (length)
注意:它不知道容量。
内存模型图解
假设我们有以下代码:
plaintext
let s1 = String::from("Hello");
let s2: &str = &s1;
内存中的样子大致如下:
plaintext
栈 (Stack) 堆 (Heap)
+-------------------+ +-------------------+
| s1 (String) | | |
| ptr -------->|----------->| 'H', 'e', 'l', |
| len: 5 | | 'l', 'o' |
| capacity: 5 | | |
+-------------------+ +-------------------+
^
+-------------------+ |
| s2 (&str) | |
| ptr ---------|---------------------+
| len: 5 |
+-------------------+
核心区别总结表
| 特性 | String |
&str |
|---|---|---|
| 所有权 | 拥有数据 (Owned) | 借用数据 (Borrowed) |
| 内存位置 | 数据在堆 (Heap) 上 | 数据可以在堆、栈或静态区 |
| 可变性 | 可变 (Mutable) | 不可变 (Immutable) |
| 大小 | 动态大小 (可增长) | 运行时长度已知,但不可增长 |
| 开销 | 较重 (涉及内存分配) | 轻量 (只是指针和长度) |
| 生命周期 | 由所有权规则决定 | 必须带有生命周期 (虽然常被省略) |
常见用法与转换
1. 字符串字面量是 &str
plaintext
// "Hello" 存储在编译后的二进制文件中(静态内存)
// s 的类型是 &str (具体说是 &'static str)
let s = "Hello";
2. &str 转 String (复制数据,开销大)
当你需要拥有数据,或者需要修改数据时:
plaintext
let slice = "hello";
let s1 = slice.to_string();
let s2 = String::from(slice);
let s3 = slice.to_owned();
3. String 转 &str (零拷贝,开销极小)
当你只需要读取数据时:
plaintext
let s = String::from("hello");
let slice1 = &s; // 自动解引用强制转换 (Deref Coercion)
let slice2 = &s[..]; // 全切片
let slice3 = s.as_str(); // 显式转换
什么时候用哪个?
作为函数参数时:
- 推荐使用
&str:如果你只是想读取字符串的内容,不需要获取所有权。这样函数既可以接受String(通过&) 也可以接受&str,灵活性更高。plaintext// 好:既能传 String 也能传 &str fn print_me(text: &str) { println!("{}", text); } - 使用
String:如果你需要获取所有权(例如你要把这个字符串存到一个结构体里,或者你要在函数内部把它传给另一个线程)。
作为结构体字段时:
- 推荐使用
String:这最简单,结构体拥有字符串的所有权。 - 使用
&str:只有当你确定结构体的生命周期短于字符串数据的生命周期时才用(需要显式标注生命周期'a),这对初学者来说通常比较复杂。
总结
String= 拥有者,堆内存,可变,重。&str= 观察者,引用,不可变,轻。