在 Rust 中,哪怕是一行再普通不过的代码,也可能暗藏玄机。这次我们就来剖析这样一句看似简单的代码:
let s = "hello world".to_string();
 
这行代码触发了 只读数据段(.rodata)、堆(heap) 和 栈(stack) 三个内存区域的联动。我们将一步步解析这背后发生的事情,并最终弄清楚为什么一个字符串变量要占用 3 个 word(24 字节)。
一、从这行代码开始
我们先看代码:
let s = "hello world".to_string();
 
你可能以为这里只是创建了一个字符串对象,实则触发了一连串复杂的内存操作。
二、程序中的三大内存区域
1. .rodata(只读数据段)
- 存储内容:程序中所有不可变的字符串常量(比如 
"hello world") - 特点:编译时就确定,加载程序时直接映射进内存,不可修改
 - 举例:
"hello world"编译后会保存在.rodata里,是只读的静态内存 
2. 堆(Heap)
- 存储内容:运行时动态分配的内存
 - 特点:需要手动管理(Rust 用所有权系统保障安全)
 - 在这里:
.to_string()会在堆上开辟一块新空间,把"hello world"拷贝进来 
3. 栈(Stack)
- 存储内容:函数里的局部变量和结构体字段(固定大小)
 - 在这里:变量 
s是个String,它是一个结构体,保存在栈上,记录了堆那块内存的三个关键信息 
三、什么是一个 word?
- 在 64 位系统 中,一个 word = 8 字节
 - Rust 中很多基础类型如指针、
usize等都是一个 word 大小 - 一个 
String包含 3 个字段:指针 + 长度 + 容量 → 共 3 个 word = 24 字节 
四、Rust 中 String 的结构
Rust 的 String 实际上是下面这个结构:
struct String {ptr: *const u8,    // 指向堆上的字符串数据len: usize,        // 当前字符串的长度capacity: usize,   // 分配的总内存容量
}
 
举个例子,如果你用 .to_string() 创建了 "hello world",那么:
ptr→ 指向堆上的字符串数据len= 11(hello world 一共 11 个字符)capacity= 11(刚好分配了 11 字节)
五、这行代码到底做了什么?
来回顾这句代码:
let s = "hello world".to_string();
 
执行过程:
"hello world"是字符串字面量,编译阶段进入.rodata.to_string()时:- 在堆上申请 11 字节
 - 把 
.rodata中的数据逐字节拷贝过去 
- 在栈上创建一个 
String结构体(变量s):- 存储堆地址(ptr)
 - 长度(len = 11)
 - 容量(capacity = 11)
 
 
📦 三个 word 分别是什么?
| 字段 | 类型 | 意义 | 
|---|---|---|
ptr | *const u8 | 指向堆上数据的地址 | 
len | usize | 表示字符串当前长度 | 
capacity | usize | 表示堆中已分配的空间容量 | 
六、用代码验证 String 的大小
可以用 std::mem::size_of::<String>() 来验证:
use std::mem::size_of;fn main() {println!("Size of String: {}", size_of::<String>());
}
 
输出为:
Size of String: 24
 
七、可视化内存布局
.rodata(只读段):
+----------------+
| "hello world"  | ← 编译时写入,可读不可改堆:
+-----------------------------------+
| 'h' 'e' 'l' 'l' 'o' ' ' 'w' 'o' 'r' 'l' 'd' |
^
|__ s.ptr 指向这里,len = 11,cap = 11栈:
+------------------------------+
| ptr(指向堆)               |
| len = 11                    |
| capacity = 11               |
+------------------------------+
 
八、延伸思考与解答
❓1. 如果改用 let s = "hello world"; 会发生什么?
 
这是 &'static str,不是 String:
"hello world"仍然在.rodata段s是对该段的引用,不会复制也不会在堆上分配内存s的类型是&'static str,本质上就是一个指针和长度的组合,占 16 字节(2 个 word)
👉 更高效,但不可变。
❓2. 为什么还要用 .to_string(),不直接用 &str?
 
&str是只读的,不能修改.to_string()创建一个 可变字符串,你可以.push()、.insert()等- 在需要修改字符串内容时,必须使用 
String 
❓3. 如果字符串很长或频繁修改,是否性能差?
- 如果你不断修改字符串,比如 
.push_str(),可能会多次触发重新分配(扩容) - 最好使用 
.with_capacity()预先分配内存,避免多次扩容 
例如:
let mut s = String::with_capacity(100);
s.push_str("hello");
 
❓4. Vec<T> 和 String 内部结构一样吗?
 
几乎一样!
String就是Vec<u8>的封装- 二者都有指针、长度、容量
 - 所以你可以通过 
.into_bytes()把String转成Vec<u8>,而.from_utf8()可以反转回来 
九、总结
这一行代码表面上只是创建了一个字符串,但它背后涉及了:
.rodata存储静态常量- 堆上拷贝数据(可变性)
 - 栈上维护指针、长度、容量
 
它完美体现了 Rust 的内存模型:安全、高效、结构清晰。
