Skip to content

函数与 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
fn 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
}
▶ Run

运行结果

=== 所有权在函数间流转 ===
创建后: 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
fn 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")
}
▶ Run

运行结果

=== 函数参数最佳实践 ===

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
#[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);
}
▶ Run

运行结果

=== 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
fn take(s: String) {
    println!("{}", s);
}

fn main() {
    let s = String::from("hello");
    take(s);
    println!("{}", s);  // ❌ 错误:s 已移动
}
▶ Run

完整编译错误信息

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
fn take(s: &String) {
    println!("{}", s);
}

fn main() {
    let s = String::from("hello");
    take(&s);  // 借用
    println!("{}", s);  // ✅ 正确
}
▶ Run

修复方法 2:返回所有权

rust
fn take(s: String) -> String {
    println!("{}", s);
    s
}

fn main() {
    let s = String::from("hello");
    let s = take(s);  // 返回所有权
    println!("{}", s);  // ✅ 正确
}
▶ Run

错误 2:循环中移动所有权

错误代码

rust
fn main() {
    let s = String::from("hello");
    
    for _ in 0..3 {
        process(s);  // ❌ 第一次后 s 就无效
    }
}

fn process(s: String) {
    println!("{}", s);
}
▶ Run

完整编译错误信息

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
fn main() {
    let s = String::from("hello");
    
    for _ in 0..3 {
        process(&s);  // 借用,不移动
    }
}

fn process(s: &str) {
    println!("{}", s);
}
▶ Run

错误 3:无法为非 Copy 类型实现 Copy

错误代码

rust
#[derive(Clone, Copy)]
struct Bad {
    name: String,  // String 不是 Copy
}

fn main() {
    let b = Bad { name: String::from("test") };
}
▶ Run

完整编译错误信息

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
#[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);
}
▶ Run

性能分析

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
use 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();
}
▶ Run

运行结果示例

=== 函数调用性能测试 ===
移动调用: 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
  • [ ] 知道何时使用引用参数
  • [ ] 理解函数返回所有权

下一章

下一章将综合应用所有权知识,学习实战技巧和常见错误。

➡️ 实战总结