
Rust 复制字符串:栈和堆的 “复制大战”
一、字符串复制?不只是 “Ctrl+C” 那么简单
想象你有一本《编程圣经》(字符串数据):
- 如果你只是在扉页写个 “借阅人:张三”(引用),书还是那本,只是多了个标记。
- 如果你复印了一整本(深复制),那就有两本完全一样的书,改其中一本不影响另一本。
- 如果你只复印了封面(浅复制),里面的内容还是共享的,改原书内容,复印件也会变(这就坑了)。
Rust 里复制字符串就像处理这本书,有三种 “复制姿势”,每种对栈和堆的操作都不一样。今天咱们就扒开内存看究竟,让栈和堆的 “小动作” 无所遁形~
二、准备工作:搭个 “字符串复制实验室”
- 创建项目(终端操作,老规矩):
bash
cargo new rust_string_copy # 新建项目
cd rust_string_copy # 进入项目
- 咱们所有代码都写在src/main.rs里,边写边看内存变化。
三、案例实操:三种复制方式的 “内存大戏”
案例 1:最 “抠门” 的复制 —— 引用(&str)
特点:不复制数据,只分享访问权,栈上多一个指针,堆上数据不动。
代码:
rust
fn main() {
// 原字符串:栈上有String控制块,堆上有数据
let s1 = String::from("Rust字符串");
println!("s1: 地址={:p}, 内容={}", &s1, s1);
// 引用复制:栈上新增一个&str,指向s1的堆数据
let s2 = &s1; // s2是引用,类似"看一眼s1的书"
println!("s2: 地址={:p}, 指向的数据地址={:p}, 内容={}", &s2, s2.as_ptr(), s2);
// 验证:s1和s2指向同一个堆数据
println!("s1的堆地址: {:p}, s2的堆地址: {:p}", s1.as_ptr(), s2.as_ptr());
}
运行命令:cargo run
输出(地址是示例,你的会不一样):
plaintext
s1: 地址=0x7ffd9a5b9a70, 内容=Rust字符串
s2: 地址=0x7ffd9a5b9a90, 指向的数据地址=0x55f8d9a52b60, 内容=Rust字符串
s1的堆地址: 0x55f8d9a52b60, s2的堆地址: 0x55f8d9a52b60
内存解析:
- 栈上:s1(String 控制块:ptr=0x55f8d9a52b60, len=9, cap=9)和s2(&str:ptr=0x55f8d9a52b60, len=9)
- 堆上:只有一份数据”Rust字符串”(地址 0x55f8d9a52b60)
- 就像s1买了本书,s2只是借来看,书还是那本,只是多了个读者。
案例 2:”半复制”——Clone(深复制)
特点:栈上新建控制块,堆上复制数据,两份完全独立。
代码:
rust
fn main() {
let s1 = String::from("Rust字符串");
println!("s1: 栈地址={:p}, 堆地址={:p}, 内容={}", &s1, s1.as_ptr(), s1);
// Clone复制:栈上新建String,堆上复制数据
let s2 = s1.clone(); // 相当于"复印全书"
println!("s2: 栈地址={:p}, 堆地址={:p}, 内容={}", &s2, s2.as_ptr(), s2);
// 改s2不影响s1
let mut s2 = s2; // 加mut让s2可变
s2.push_str("Clone");
println!("改后s1: {}, 改后s2: {}", s1, s2);
}
运行输出:
plaintext
s1: 栈地址=0x7ffd9a5b9a70, 堆地址=0x55f8d9a52b60, 内容=Rust字符串
s2: 栈地址=0x7ffd9a5b9a90, 堆地址=0x55f8d9a52ba0, 内容=Rust字符串
改后s1: Rust字符串, 改后s2: Rust字符串Clone
内存解析:
- 栈上:s1和s2各有自己的控制块(地址不同)
- 堆上:s1的数据在 0x55f8d9a52b60,s2的复制数据在 0x55f8d9a52ba0(新地址!)
- 就像s1有本书,s2复印了一本,改复印本完全不影响原书。
案例 3:”偷梁换柱”—— 移动(Move)
特点:栈上控制权转移,堆数据不变,原变量失效(Rust 的 “独占欲”)。
代码:
rust
fn main() {
let s1 = String::from("Rust字符串");
println!("s1: 栈地址={:p}, 堆地址={:p}, 内容={}", &s1, s1.as_ptr(), s1);
// 移动:s1的栈控制块"搬家"到s2,堆数据不变
let s2 = s1; // s1被"掏空",不能再用了
println!("s2: 栈地址={:p}, 堆地址={:p}, 内容={}", &s2, s2.as_ptr(), s2);
// 下面这行会报错!由于s1已经"无家可归"了
// println!("s1还能用吗?{}", s1);
}
运行输出:
plaintext
s1: 栈地址=0x7ffd9a5b9a70, 堆地址=0x55f8d9a52b60, 内容=Rust字符串
s2: 栈地址=0x7ffd9a5b9a90, 堆地址=0x55f8d9a52b60, 内容=Rust字符串
内存解析:
- 栈上:s1的控制块被 “转移” 到s2,s1变成无效(编译器不让用了)
- 堆上:数据地址不变(还是 0x55f8d9a52b60),但所有权从s1转到s2
- 就像s1把书送给s2,s1再也不能说 “这是我的书” 了(Rust 的 “独生子女政策”,数据只能有一个 owner)
案例 4:特殊情况 —— 字符串字面量(&'static str)
特点:数据存在常量区,栈上复制引用,既不是深复制也不是移动。
代码:
rust
fn main() {
// 字符串字面量:数据在常量区,栈上是&'static str
let s1 = "Rust字面量"; // 注意:没有String::from
println!("s1: 栈地址={:p}, 数据地址={:p}, 内容={}", &s1, s1.as_ptr(), s1);
// 复制引用:栈上多一个指针,指向同一个常量区数据
let s2 = s1;
println!("s2: 栈地址={:p}, 数据地址={:p}, 内容={}", &s2, s2.as_ptr(), s2);
// 两者都能修改吗?不能,由于是&str(不可变)
}
运行输出:
plaintext
s1: 栈地址=0x7ffd9a5b9ab0, 数据地址=0x55f8d9a3e0c0, 内容=Rust字面量
s2: 栈地址=0x7ffd9a5b9ab8, 数据地址=0x55f8d9a3e0c0, 内容=Rust字面量
内存解析:
- 常量区:”Rust字面量”在编译时就存在,地址固定
- 栈上:s1和s2都是指向常量区的引用(类似两个路标指向同一个景点)
- 就像公园的地图上有两个箭头都指向 “喷泉”,箭头可以复制,但喷泉只有一个。
四、三种复制方式的 “内存开销” 对比
|
方式 |
栈操作 |
堆操作 |
特点 |
适用场景 |
|
引用(&str) |
新增指针(8 字节) |
无 |
零成本,共享数据 |
临时访问,不修改 |
|
Clone |
新增控制块(24 字节) |
复制整个数据 |
成本高,独立数据 |
需要修改副本 |
|
移动(Move) |
控制块转移 |
无 |
零成本,所有权转移 |
数据传递,唯一所有者 |
简单说:引用像 “借看”,Clone 像 “复印”,移动像 “赠送”—— 根据钱包(性能)和需求选就行~
五、编译运行全指南(小白也能看懂)
- 安装 Rust(已安装跳过):
打开 PowerShell(Windows)或终端(Mac/Linux): - bash
- curl –proto '=https' –tlsv1.2 -sSf https://sh.rustup.rs | sh
- 按提示操作,最后输入rustc –version看到版本号就成功了。
- 写代码:
把上面的案例代码复制到src/main.rs,每次运行一个案例(注释掉其他的)。 - 运行:
- bash
- cargo run # 自动编译并运行
- 看报错(很重大!):
列如案例 3 中如果强行打印s1,会得到: - plaintext
- error[E0382]: use of moved value: `s1`
- 这是 Rust 在保护你,避免 “double free” 错误(就像防止你把同一本书送给两个人)。
总结:Rust 的 “复制哲学”
Rust 对待字符串复制就像对待贵重物品:
- 能共享就不复制(引用)
- 必须独立就深复制(Clone)
- 转移所有权就彻底移交(移动)
这种 “斤斤计较” 的内存管理,让 Rust 程序既安全又高效 —— 毕竟,谁也不想为没必要的复制浪费内存,更不想由于共享数据搞出 bug 来~
标题
- 《Rust 字符串复制:栈堆联动的 “复制三部曲” 全解析》
- 《从内存视角看 Rust 字符串复制:引用、Clone 与移动的奥秘》
简介
本文用 “书籍复制” 的趣味类比,详解 Rust 中字符串复制的三种方式(引用、Clone、移动)在栈和堆中的具体操作。通过四个实战案例,直观展示每种方式的内存变化、适用场景及性能差异,附完整代码和编译步骤,让你轻松理解 Rust 独特的内存管理哲学。
关键词
#Rust #字符串复制 #栈和堆 #内存管理 #所有权


