函数与 Copy Trait
> 理解函数参数的所有权传递和 Copy 类型
本章目标
完成本章学习后,你将能够:
- 掌握函数参数传递的所有权机制
- 理解 Copy trait 的作用和规则
- 区分 Copy 类型和非 Copy 类型
- 运用所有权最佳实践优化代码
核心概念
函数与所有权
函数参数的所有权转移
函数调用时,参数传递会涉及所有权转移。理解这个过程是编写安全 Rust 代码的关键。
┌─────────────────────────────────────────────────────┐
│ 函数参数所有权传递 │
├─────────────────────────────────────────────────────┤
│ │
│ 规则: │
│ • 传递值(非引用):所有权移动到函数 │
│ • 传递引用:借用,所有权不变 │
│ • 函数结束:参数离开作用域 │
│ │
│ 三种传递方式: │
│ │
│ 1. 移动所有权:fn take(s: String) │
│ • 参数获得所有权 │
│ • 函数结束参数被释放 │
│ • 调用者不能再使用 │
│ │
│ 2. 借用:fn borrow(s: &String) │
│ • 参数获得引用 │
│ • 函数结束引用失效 │
│ • 调用者仍拥有所有权 │
│ │
│ 3. Copy:fn copy(x: i32) │
│ • 参数获得副本 │
│ • 函数结束副本失效 │
│ • 调用者原值仍有效 │
│ │
└─────────────────────────────────────────────────────┘内存变化详解
示例 1:移动所有权
fn take_ownership(s: String) {
println!("{}", s);
} // s 在这里被释放
调用前:
┌─────────────────────────────────────────────┐
│ main 栈 堆 │
│ ┌──────────────┐ ┌─────────┐ │
│ │ s │──▶│ "hello" │ │
│ │ (所有者) │ └─────────┘ │
│ └──────────────┘ │
└─────────────────────────────────────────────┘
调用时(所有权移动):
┌─────────────────────────────────────────────┐
│ main 栈 堆 │
│ ┌──────────────┐ ┌─────────┐ │
│ │ s │ │ "hello" │ │
│ │ (无效) │ └─────────┘ │
│ └──────────────┘ ↑ │
│ │ │
│ take_ownership 栈 │ │
│ ┌──────────────┐ │ │
│ │ s (参数) │───────┘ │
│ │ (新所有者) │ │
│ └──────────────┘ │
└─────────────────────────────────────────────┘
函数结束:
┌─────────────────────────────────────────────┐
│ main 栈 堆 │
│ ┌──────────────┐ ┌─────────┐ │
│ │ s │ │ (已释放)│ │
│ │ (无效) │ └─────────┘ │
│ └──────────────┘ │
│ │
│ take_ownership 栈 │
│ ┌──────────────┐ │
│ │ (已销毁) │ │
│ └──────────────┘ │
└─────────────────────────────────────────────┘示例 2:借用(引用)
fn borrow_value(s: &String) {
println!("{}", s);
} // s 是引用,不释放原数据
调用前:
┌─────────────────────────────────────────────┐
│ main 栈 堆 │
│ ┌──────────────┐ ┌─────────┐ │
│ │ s │──▶│ "hello" │ │
│ │ (所有者) │ └─────────┘ │
│ └──────────────┘ │
└─────────────────────────────────────────────┘
调用时(传递引用):
┌─────────────────────────────────────────────┐
│ main 栈 堆 │
│ ┌──────────────┐ ┌─────────┐ │
│ │ s │──▶│ "hello" │ │
│ │ (所有者) │ └─────────┘ │
│ └──────────────┘ ↑ │
│ │ │
│ borrow_value 栈 │ │
│ ┌──────────────┐ │ │
│ │ s (&String) │───────┘ │
│ │ (借用者) │ │
│ └──────────────┘ │
└─────────────────────────────────────────────┘
函数结束:
┌─────────────────────────────────────────────┐
│ main 栈 堆 │
│ ┌──────────────┐ ┌─────────┐ │
│ │ s │──▶│ "hello" │ │
│ │ (仍然有效) │ └─────────┘ │
│ └──────────────┘ │
│ │
│ borrow_value 栈 │
│ ┌──────────────┐ │
│ │ (已销毁) │ │
│ │ 引用失效 │ │
│ └──────────────┘ │
└─────────────────────────────────────────────┘示例 3:Copy 类型
fn copy_value(x: i32) {
println!("{}", x);
} // x 是副本,不影响原值
调用前:
┌─────────────────────────────────────────────┐
│ main 栈 │
│ ┌──────────────┐ │
│ │ x = 5 │ │
│ │ (所有者) │ │
│ └──────────────┘ │
└─────────────────────────────────────────────┘
调用时(复制值):
┌─────────────────────────────────────────────┐
│ main 栈 │
│ ┌──────────────┐ │
│ │ x = 5 │ │
│ │ (仍然有效) │ │
│ └──────────────┘ │
│ │
│ copy_value 栈 │
│ ┌──────────────┐ │
│ │ x = 5 │(复制) │
│ │ (副本) │ │
│ └──────────────┘ │
└─────────────────────────────────────────────┘
函数结束:
┌─────────────────────────────────────────────┐
│ main 栈 │
│ ┌──────────────┐ │
│ │ x = 5 │ │
│ │ (仍然有效) │ │
│ └──────────────┘ │
│ │
│ copy_value 栈 │
│ ┌──────────────┐ │
│ │ (已销毁) │ │
│ │ 副本失效 │ │
│ └──────────────┘ │
└─────────────────────────────────────────────┘Copy Trait详解
什么是 Copy Trait?
Copy trait 标记一个类型可以通过简单的位复制来创建副本。对于 Copy 类型,赋值时自动复制而不是移动。
┌─────────────────────────────────────────────────────┐
│ Copy Trait详解 │
├─────────────────────────────────────────────────────┤
│ │
│ 定义: │
│ pub trait Copy: Clone { } │
│ │
│ 特点: │
│ • 编译时自动复制 │
│ • 只复制栈数据(位复制) │
│ • 零运行时开销 │
│ • 原变量赋值后仍然有效 │
│ │
│ 实现 Copy 的条件: │
│ • 所有数据都在栈上 │
│ • 没有堆分配 │
│ • 没有 Drop 实现(不能自定义析构) │
│ │
│ Copy 的语义: │
│ let x = 5; │
│ let y = x; // 自动复制,x 仍然有效 │
│ │
│ vs 非 Copy 类型: │
│ let s = String::from("hello"); │
│ let t = s; // 移动,s 无效 │
│ │
└─────────────────────────────────────────────────────┘Copy 类型完整列表
┌─────────────────────────────────────────────────────┐
│ 所有 Copy 类型 │
├─────────────────────────────────────────────────────┤
│ │
│ 基本类型(所有都是 Copy): │
│ ┌────────────────────────────────────┐ │
│ │ 整数类型 │ │
│ │ • i8, i16, i32, i64, i128, isize │ │
│ │ • u8, u16, u32, u64, u128, usize │ │
│ ├────────────────────────────────────┤ │
│ │ 浮点类型 │ │
│ │ • f32, f64 │ │
│ ├────────────────────────────────────┤ │
│ │ 布尔类型 │ │
│ │ • bool │ │
│ ├────────────────────────────────────┤ │
│ │ 字符类型 │ │
│ │ • char │ │
│ ├────────────────────────────────────┤ │
│ │ 指针类型 │ │
│ │ • *const T, *mut T(原始指针) │ │
│ │ • &T(共享引用,不可变引用) │ │
│ └────────────────────────────────────┘ │
│ │
│ 组合类型(条件 Copy): │
│ ┌────────────────────────────────────┐ │
│ │ 元组 │ │
│ │ • 所有元素都是 Copy → 元组是 Copy │ │
│ │ • 例:(i32, i32) 是 Copy │ │
│ │ • 例:(String, i32) 不是 Copy │ │
│ ├────────────────────────────────────┤ │
│ │ 数组 │ │
│ │ • 元素类型是 Copy → 数组是 Copy │ │
│ │ • 例:[i32; 10] 是 Copy │ │
│ │ • 例:[String; 5] 不是 Copy │ │
│ ├────────────────────────────────────┤ │
│ │ 函数指针 │ │
│ │ • fn() 是 Copy │ │
│ └────────────────────────────────────┘ │
│ │
│ 非 Copy 类型: │
│ ┌────────────────────────────────────┐ │
│ │ • String │ │
│ │ • Vec<T> │ │
│ │ • Box<T> │ │
│ │ • HashMap, HashSet │ │
│ │ • 所有堆分配类型 │ │
│ │ • 实现了 Drop 的类型 │ │
│ └────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────┘Copy 的内存复制过程
基本类型复制:
let x: i32 = 42;
let y = x; // Copy
栈内存变化:
┌─────────────────────────────────────────────┐
│ 复制前: │
│ ┌──────────────┐ │
│ │ x = 42 │ │
│ └──────────────┘ │
│ │
│ 复制后: │
│ ┌──────────────┐ │
│ │ x = 42 │(仍然有效) │
│ ├──────────────┤ │
│ │ y = 42 │(新副本) │
│ └──────────────┘ │
│ │
│ 操作:栈上的 4 字节被复制 │
│ 成本:~1 纳秒 │
│ 结果:x 和 y 都有效 │
└─────────────────────────────────────────────┘
元组复制:
let t1: (i32, f64) = (1, 3.14);
let t2 = t1; // Copy(所有元素都是 Copy)
栈内存变化:
┌─────────────────────────────────────────────┐
│ 复制前: │
│ ┌────────────────────────────────────┐ │
│ │ t1 = (i32:1, f64:3.14) │ │
│ │ 大小:4 + 8 = 12 字节 │ │
│ └────────────────────────────────────┘ │
│ │
│ 复制后: │
│ ┌────────────────────────────────────┐ │
│ │ t1 = (i32:1, f64:3.14) │ │
│ │ (仍然有效) │ │
│ ├────────────────────────────────────┤ │
│ │ t2 = (i32:1, f64:3.14) │ │
│ │ (新副本) │ │
│ └────────────────────────────────────┘ │
│ │
│ 操作:栈上的 12 字节被复制 │
│ 成本:~1 纳秒 │
│ 结果:t1 和 t2 都有效 │
└─────────────────────────────────────────────┘Copy vs Clone 对比
┌─────────────────────────────────────────────────────┐
│ Copy vs Clone 对比 │
├──────────────────┬────────────────┬─────────────────┤
│ 特性 │ Copy │ Clone │
├──────────────────┼────────────────┼─────────────────┤
│ 调用方式 │ 自动 │ 显式 .clone() │
│ 复制位置 │ 只栈数据 │ 可能包含堆数据 │
│ 性能开销 │ 零成本 │ 可能昂贵 │
│ 原变量 │ 仍有效 │ 仍有效 │
│ 适用类型 │ 简单类型 │ 所有类型 │
│ Trait关系 │ 必须实现Clone │ 独立trait │
├──────────────────┼────────────────┼─────────────────┤
│ 示例 │ let y = x; │ let y = x.clone();│
│ │ (自动) │ (显式) │
├──────────────────┴────────────────┴─────────────────┤
│ │
│ 关键区别: │
│ │
│ Copy: │
│ • 编译器隐式调用 │
│ • 只复制栈上的位 │
│ • 适用于:i32, f64, bool, char等 │
│ • 例:let x = 5; let y = x; │
│ │
│ Clone: │
│ • 必须显式调用 │
│ • 可能复制堆数据 │
│ • 适用于:String, Vec, 自定义类型 │
│ • 例:let s1 = String::from("hi"); │
│ let s2 = s1.clone(); │
│ │
│ 设计哲学: │
│ • Copy:简单、廉价、自动 │
│ • Clone:复杂、昂贵、显式 │
│ • Clone 显式调用让程序员意识到成本 │
│ │
└─────────────────────────────────────────────────────┘实战案例
案例 1:函数所有权流转
问题描述
理解函数调用中的所有权转移和返回。
代码实现
rust
▶ Runfn main() {
println!("=== 所有权在函数间流转 ===");
// 创建所有权
let s1 = create_string();
println!("创建后: {}", s1);
// 传递并返回
let s2 = process_and_return(s1);
println!("处理后: {}", s2);
// 再次传递
let s3 = append_suffix(s2);
println!("添加后缀: {}", s3);
// 最终释放
println!("main 结束,s3 被释放");
}
fn create_string() -> String {
String::from("hello")
}
fn process_and_return(s: String) -> String {
println!("函数内: {}", s);
s // 返回所有权
}
fn append_suffix(mut s: String) -> String {
s.push_str("_processed");
s
}运行结果
=== 所有权在函数间流转 ===
创建后: hello
函数内: hello
处理后: hello
添加后缀: hello_processed
main 结束,s3 被释放所有权流转图
┌─────────────────────────────────────────────────────┐
│ 所有权流转时间线 │
├─────────────────────────────────────────────────────┤
│ │
│ 时间 1:create_string() │
│ ┌────────────────────────────────────┐ │
│ │ 函数内创建 String │ │
│ │ 返回所有权给 main │ │
│ │ s1 = "hello" │ │
│ └────────────────────────────────────┘ │
│ │
│ 时间 2:process_and_return(s1) │
│ ┌────────────────────────────────────┐ │
│ │ s1 移动到函数参数 │ │
│ │ 函数返回所有权 │ │
│ │ s2 = "hello" │ │
│ │ (s1 无效) │ │
│ └────────────────────────────────────┘ │
│ │
│ 时间 3:append_suffix(s2) │
│ ┌────────────────────────────────────┐ │
│ │ s2 移动到函数参数 │ │
│ │ 函数修改并返回 │ │
│ │ s3 = "hello_processed" │ │
│ │ (s2 无效) │ │
│ └────────────────────────────────────┘ │
│ │
│ 时间 4:main 结束 │
│ ┌────────────────────────────────────┐ │
│ │ s3 离开作用域 │ │
│ │ Rust 自动调用 drop │ │
│ │ 内存释放 │ │
│ └────────────────────────────────────┘ │
│ │
│ 特点: │
│ • 所有权明确流转 │
│ • 无悬垂指针 │
│ • 无内存泄漏 │
│ • 编译器全程保证 │
│ │
└─────────────────────────────────────────────────────┘案例 2:函数参数最佳实践
问题描述
学习函数参数的最佳实践,选择合适的参数类型。
代码实现
rust
▶ Runfn main() {
println!("=== 函数参数最佳实践 ===");
let s = String::from("hello, rust!");
println!("\n1. 需要所有权:");
consume_string(s.clone());
println!("原值: {}", s);
println!("\n2. 只需要读取:");
print_string(&s);
print_string_slice(&s);
print_literal("literal");
println!("原值仍有效: {}", s);
println!("\n3. 需要修改:");
let mut s2 = String::from("hello");
modify_string(&mut s2);
println!("修改后: {}", s2);
println!("\n4. Copy类型参数:");
let x = 42;
process_copy(x);
println!("原值: {}", x);
println!("\n5. 返回所有权:");
let result = create_and_return();
println!("返回值: {}", result);
}
// ❌ 不推荐:消耗所有权
fn consume_string(s: String) {
println!("消耗: {}", s);
} // s 被释放
// ✅ 推荐:接受 &String
fn print_string(s: &String) {
println!("借用String: {}", s);
}
// ✅ 最佳:接受 &str(更通用)
fn print_string_slice(s: &str) {
println!("借用&str: {}", s);
}
// 可以接受字面量
fn print_literal(s: &str) {
println!("字面量: {}", s);
}
// 修改:需要可变引用
fn modify_string(s: &mut String) {
s.push_str("_modified");
}
// Copy类型:自动复制
fn process_copy(x: i32) {
println!("Copy类型: {}", x);
}
// 返回所有权
fn create_and_return() -> String {
String::from("returned")
}运行结果
=== 函数参数最佳实践 ===
1. 需要所有权:
消耗: hello, rust!
原值: hello, rust!
2. 只需要读取:
借用String: hello, rust!
借用&str: hello, rust!
字面量: literal
原值仍有效: hello, rust!
3. 需要修改:
修改后: hello_modified
4. Copy类型参数:
Copy类型: 42
原值: 42
5. 返回所有权:
返回值: returned参数类型选择指南
┌─────────────────────────────────────────────────────┐
│ 函数参数类型选择指南 │
├─────────────────────────────────────────────────────┤
│ │
│ 需求分析: │
│ 1. 函数需要所有权吗? │
│ 2. 函数需要修改吗? │
│ 3. 参数是什么类型? │
│ │
│ 决策树: │
│ │
│ 参数类型 │
│ │ │
│ ├─▶ Copy 类型(i32, bool等) │
│ │ └─────────▶ fn f(x: Type) │
│ │ (自动复制) │
│ │ │
│ ├─▶ 非 Copy 类型(String, Vec等) │
│ │ │
│ │ 需要所有权? │
│ │ │ │
│ │ ├─▶ 是 │
│ │ │ └──▶ fn f(s: String) │
│ │ │ (消耗所有权) │
│ │ │ │
│ │ ├─▶ 否 │
│ │ │ │
│ │ 需要修改? │
│ │ │ │
│ │ ├─▶ 是 │
│ │ │ └──▶ fn f(s: &mut String)│
│ │ │ │
│ │ ├─▶ 否 │
│ │ │ │
│ │ │ 字符串类型? │
│ │ │ │ │
│ │ │ ├─▶ 是 │
│ │ │ │ └──▶ fn f(s: &str)│
│ │ │ │ (最佳)│
│ │ │ │ │
│ │ │ ├─▶ 否 │
│ │ │ └──▶ fn f(s: &Type)│
│ │ │ (借用)│
│ │ │ │
│ │
│ 示例对比: │
│ │
│ ❌ 过度消耗所有权: │
│ fn print(s: String) { println!("{}", s); } │
│ • 不必要地消耗所有权 │
│ • 调用者失去使用权 │
│ │
│ ✅ 使用引用: │
│ fn print(s: &String) { println!("{}", s); } │
│ • 只借用 │
│ • 调用者保留所有权 │
│ │
│ ✅✅ 最佳(字符串): │
│ fn print(s: &str) { println!("{}", s); } │
│ • 最通用 │
│ • 接受 String、&String、字面量 │
│ │
└─────────────────────────────────────────────────────┘案例 3:自定义 Copy 类型
问题描述
如何为自定义类型实现 Copy trait。
代码实现
rust
▶ Run#[derive(Debug, Clone, Copy)]
struct Point {
x: i32,
y: i32,
}
#[derive(Debug, Clone)]
struct NamedPoint {
name: String,
x: i32,
y: i32,
}
fn main() {
println!("=== Copy 结构体 ===");
let p1 = Point { x: 10, y: 20 };
let p2 = p1; // Copy
println!("p1 = {:?}", p1); // ✅ 仍然有效
println!("p2 = {:?}", p2);
println!("\n=== 非 Copy 结构体 ===");
let np1 = NamedPoint {
name: String::from("origin"),
x: 0,
y: 0,
};
let np2 = np1; // Move
// println!("np1 = {:?}", np1); // ❌ 错误:np1 已移动
println!("np2 = {:?}", np2);
println!("\n=== 克隆非 Copy 类型 ===");
let np3 = NamedPoint {
name: String::from("target"),
x: 100,
y: 200,
};
let np4 = np3.clone(); // Clone
println!("np3 = {:?}", np3); // ✅ 仍然有效
println!("np4 = {:?}", np4);
}运行结果
=== Copy 结构体 ===
p1 = Point { x: 10, y: 20 }
p2 = Point { x: 10, y: 20 }
=== 非 Copy 结构体 ===
np2 = NamedPoint { name: "origin", x: 0, y: 0 }
=== 克隆非 Copy 类型 ===
np3 = NamedPoint { name: "target", x: 100, y: 200 }
np4 = NamedPoint { name: "target", x: 100, y: 200 }Copy 实现条件详解
┌─────────────────────────────────────────────────────┐
│ Copy 实现条件 │
├─────────────────────────────────────────────────────┤
│ │
│ 条件 1:所有字段都是 Copy │
│ ┌────────────────────────────────────┐ │
│ │ #[derive(Copy, Clone)] │ │
│ │ struct Good { │ │
│ │ x: i32, // Copy │ │
│ │ y: f64, // Copy │ │
│ │ } │ │
│ │ ✅ 可以实现 Copy │ │
│ └────────────────────────────────────┘ │
│ │
│ 条件 2:不能有 Drop 实现 │
│ ┌────────────────────────────────────┐ │
│ │ #[derive(Copy, Clone)] │ │
│ │ struct Bad { │ │
│ │ x: i32, │ │
│ │ } │ │
│ │ │ │
│ │ impl Drop for Bad { │ │
│ │ fn drop(&mut self) { } │ │
│ │ } │ │
│ │ ❌ 不能实现 Copy │ │
│ │ (编译错误) │ │
│ └────────────────────────────────────┘ │
│ │
│ 条件 3:没有堆分配 │
│ ┌────────────────────────────────────┐ │
│ │ #[derive(Clone)] │ │
│ │ struct NotCopy { │ │
│ │ name: String, // 堆分配 │ │
│ │ } │ │
│ │ ❌ 不能实现 Copy │ │
│ │ (String 不是 Copy) │ │
│ └────────────────────────────────────┘ │
│ │
│ 正确实现方式: │
│ │
│ // 方式 1:derive 宏 │
│ #[derive(Debug, Clone, Copy)] │
│ struct Point { │
│ x: i32, │
│ y: i32, │
│ } │
│ │
│ // 方式 2:手动实现(需先实现 Clone) │
│ struct Point { │
│ x: i32, │
│ y: i32, │
│ } │
│ │
│ impl Clone for Point { │
│ fn clone(&self) -> Self { │
│ *self │
│ } │
│ } │
│ │
│ impl Copy for Point {} │
│ │
│ 注意: │
│ • Copy 必须实现 Clone │
│ • derive 最简单 │
│ • 检查所有字段类型 │
│ │
└─────────────────────────────────────────────────────┘常见错误
错误 1:函数调用后使用变量
错误代码
rust
▶ Runfn take(s: String) {
println!("{}", s);
}
fn main() {
let s = String::from("hello");
take(s);
println!("{}", s); // ❌ 错误:s 已移动
}完整编译错误信息
error[E0382]: borrow of moved value: `s`
--> src/main.rs:8:20
|
6 | let s = String::from("hello");
| - move occurs because `s` has type `String`, which does not implement the `Copy` trait
7 | take(s);
| - value moved here
8 | println!("{}", s);
| ^ value borrowed here after move
|
= note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)
help: consider cloning the value before moving it
|
7 | take(s.clone());
| ++++++++++++修复方法 1:使用引用
rust
▶ Runfn take(s: &String) {
println!("{}", s);
}
fn main() {
let s = String::from("hello");
take(&s); // 借用
println!("{}", s); // ✅ 正确
}修复方法 2:返回所有权
rust
▶ Runfn take(s: String) -> String {
println!("{}", s);
s
}
fn main() {
let s = String::from("hello");
let s = take(s); // 返回所有权
println!("{}", s); // ✅ 正确
}错误 2:循环中移动所有权
错误代码
rust
▶ Runfn main() {
let s = String::from("hello");
for _ in 0..3 {
process(s); // ❌ 第一次后 s 就无效
}
}
fn process(s: String) {
println!("{}", s);
}完整编译错误信息
error[E0382]: use of moved value: `s`
--> src/main.rs:5:18
|
4 | for _ in 0..3 {
5 | process(s);
| ^ value moved here in previous iteration of loop
|
= note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)修复方法
rust
▶ Runfn main() {
let s = String::from("hello");
for _ in 0..3 {
process(&s); // 借用,不移动
}
}
fn process(s: &str) {
println!("{}", s);
}错误 3:无法为非 Copy 类型实现 Copy
错误代码
rust
▶ Run#[derive(Clone, Copy)]
struct Bad {
name: String, // String 不是 Copy
}
fn main() {
let b = Bad { name: String::from("test") };
}完整编译错误信息
error[E0204]: the trait `Copy` cannot be implemented for this type
--> src/main.rs:2:10
|
2 | #[derive(Clone, Copy)]
| ^^^^
3 | struct Bad {
4 | name: String,
| ---- this field does not implement `Copy`
|
= note: this error originates in the derive macro `Copy` (in Nightly builds, run with -Z macro-backtrace for more info)修复方法
rust
▶ Run#[derive(Clone)] // 只实现 Clone,不实现 Copy
struct Good {
name: String,
}
fn main() {
let g = Good { name: String::from("test") };
let g2 = g.clone(); // 显式克隆
println!("{:?}", g.name);
println!("{:?}", g2.name);
}性能分析
Copy vs Clone 性能对比
┌─────────────────────────────────────────────────────┐
│ Copy vs Clone 性能对比 │
├─────────────────────────────────────────────────────┤
│ │
│ Copy 类型(i32): │
│ ┌────────────────────────────────────┐ │
│ │ let x: i32 = 42; │ │
│ │ let y = x; │ │
│ │ │ │
│ │ 操作:复制 4 字节栈数据 │ │
│ │ 时间:~1 纳秒 │ │
│ │ 成本:零成本(编译时优化) │ │
│ └────────────────────────────────────┘ │
│ │
│ Clone 类型(String): │
│ ┌────────────────────────────────────┐ │
│ │ let s = String::from("hello"); │ │
│ │ let s2 = s.clone(); │ │
│ │ │ │
│ │ 操作: │ │
│ │ 1. 分配堆内存(5 字节) │ │
│ │ 2. 复制堆数据 │ │
│ │ 3. 复制栈结构(24 字节) │ │
│ │ │ │
│ │ 时间:~100 纳秒 │ │
│ │ 成本:堆分配 + 数据复制 │ │
│ └────────────────────────────────────┘ │
│ │
│ 性能差异: │
│ • Copy:约 1 纳秒 │
│ • Clone(小字符串):约 100 纳秒 │
│ • Clone(大字符串 1MB):约 1 毫秒 │
│ │
│ 结论: │
│ • Copy 几乎免费 │
│ • Clone 需谨慎使用 │
│ • 大数据克隆昂贵 │
│ │
└─────────────────────────────────────────────────────┘函数调用性能分析
rust
▶ Runuse std::time::Instant;
fn main() {
println!("=== 函数调用性能测试 ===");
let large = String::from("x").repeat(1_000_000);
// 测试移动
let start = Instant::now();
take_move(large);
let move_time = start.elapsed();
println!("移动调用: {:?}", move_time);
// 重新创建
let large = String::from("x").repeat(1_000_000);
// 测试借用
let start = Instant::now();
take_borrow(&large);
let borrow_time = start.elapsed();
println!("借用调用: {:?}", borrow_time);
println!("借用后仍可用: {}", large.len());
// 测试克隆传递
let start = Instant::now();
take_clone(large.clone());
let clone_time = start.elapsed();
println!("克隆调用: {:?}", clone_time);
println!("克隆后仍可用: {}", large.len());
}
fn take_move(s: String) {
let _ = s.len();
}
fn take_borrow(s: &String) {
let _ = s.len();
}
fn take_clone(s: String) {
let _ = s.len();
}运行结果示例
=== 函数调用性能测试 ===
移动调用: 1ns
借用调用: 1ns
借用后仍可用: 1000000
克隆调用: 500μs
克隆后仍可用: 1000000最佳实践
✅ 推荐做法
- 函数参数优先引用:不需要所有权时使用
&T或&str - 字符串参数用 &str:最通用,接受多种类型
- 返回值明确所有权:返回
String转移所有权 - Copy 类型放心传递:无需担心所有权问题
- Clone 显式调用:让性能开销可见
❌ 避免做法
- 不要过度消耗所有权:不必要的
String参数 - 不要在循环中移动:会导致编译错误
- 不要为堆类型实现 Copy:编译器会阻止
- 不要忽略 Clone 成本:大数据克隆昂贵
小结
本章核心知识点
| 概念 | 关键字/语法 | 核心要点 |
|---|---|---|
| 函数参数移动 | fn f(s: String) | 所有权转移给函数 |
| 函数参数借用 | fn f(s: &String) | 借用,所有权不变 |
| Copy trait | #[derive(Copy)] | 自动复制,栈数据 |
| Clone trait | #[derive(Clone)] | 显式克隆,含堆数据 |
学习检查清单
- [ ] 理解函数参数的三种传递方式
- [ ] 掌握 Copy trait 的实现条件
- [ ] 能够区分 Copy 和 Clone
- [ ] 知道何时使用引用参数
- [ ] 理解函数返回所有权
下一章
下一章将综合应用所有权知识,学习实战技巧和常见错误。
➡️ 实战总结