不灭的焱

革命尚未成功,同志仍须努力 下载Java21

作者:AlbertWen  添加时间:2026-07-08 10:33:39  修改时间:2026-07-23 22:31:13  分类:01.Rust编程  编辑

目录

问题1:Rust Copy、Clone 等 derive 的使用说明,请详细举例 ?

核心原因一句话:

String 拥有堆内存资源,需要在变量生命周期结束时释放内存;如果 String 可以 Copy,就会产生多个对象同时拥有同一块堆内存,最终可能重复释放,所以 Rust 禁止 String 实现 Copy

1. 先看一个含 String 字段的结构体

#[derive(Debug, Clone)]
struct User {
    id: u64,
    name: String,
}

fn main() {
    let user1 = User {
        id: 1,
        name: String::from("Alice"),
    };

    let user2 = user1.clone();

    println!("user1 = {:?}", user1);
    println!("user2 = {:?}", user2);
}

这可以正常运行。

但是如果你写成:

#[derive(Debug, Clone, Copy)]
struct User {
    id: u64,
    name: String,
}

会报错。

因为:

String 不是 Copy
所以包含 String 字段的 User 也不能 Copy

Rust 的规则是:

一个 struct 能不能 Copy,取决于它的所有字段是否都能 Copy。
只要有一个字段不能 Copy,整个 struct 就不能 Copy。

2. 为什么 String 不能 Copy?

先看 String 内部大致长什么样。

一个 String 不是直接把字符串内容放在栈上,而是由三部分组成:

String
├── ptr      指向堆内存中真正的字符串内容
├── len      当前字符串长度
└── capacity 当前容量

例如:

let s1 = String::from("hello");

内存可以简单理解为:

栈上 s1
┌────────────┐
│ ptr ───────┼────> 堆内存 "hello"
│ len = 5    │
│ cap = 5    │
└────────────┘

String 本身在栈上,但真正的 "hello" 内容在堆上。

3. 如果 String 可以 Copy,会发生什么?

假设 Rust 允许这样:

let s1 = String::from("hello");
let s2 = s1;

如果这是 Copy,就会变成:

栈上 s1
┌────────────┐
│ ptr ───────┼────┐
│ len = 5    │    │
│ cap = 5    │    │
└────────────┘    │
                  │
                  ├──> 堆内存 "hello"
                  │
栈上 s2            │
┌────────────┐    │
│ ptr ───────┼────┘
│ len = 5    │
│ cap = 5    │
└────────────┘

这就出问题了。

因为 s1s2 都认为自己拥有这块堆内存。

程序结束时,Rust 会自动释放变量占用的资源:

释放 s1 指向的堆内存
释放 s2 指向的堆内存

同一块内存被释放两次,这叫 double free,是非常严重的内存安全问题。

所以 Rust 直接从类型系统层面禁止:

String 不能 Copy
包含 String 的 struct 也不能 Copy

4. Rust 实际怎么处理?

Rust 不允许 String 自动 Copy,而是默认执行 move

fn main() {
    let s1 = String::from("hello");

    let s2 = s1;

    println!("{}", s2);

    // println!("{}", s1); // 报错
}

这里:

let s2 = s1;

不是复制,而是移动所有权

可以理解为:

原来:
s1 拥有 "hello"

执行 let s2 = s1 之后:
s2 拥有 "hello"
s1 失效

所以 Rust 不允许你继续使用 s1

这样可以保证最终只有一个变量负责释放堆内存。

5. 含 String 字段的 struct 也是一样

#[derive(Debug)]
struct User {
    id: u64,
    name: String,
}

fn main() {
    let user1 = User {
        id: 1,
        name: String::from("Alice"),
    };

    let user2 = user1;

    println!("{:?}", user2);

    // println!("{:?}", user1); // 报错
}

执行:

let user2 = user1;

不是复制,而是移动。

因为 User 里面有 String,所以 User 不是 Copy 类型。

移动后:

user1 失效
user2 获得所有权

6. 那为什么 Clone 可以?

因为 Clone显式复制一份新的数据

例如:

#[derive(Debug, Clone)]
struct User {
    id: u64,
    name: String,
}

fn main() {
    let user1 = User {
        id: 1,
        name: String::from("Alice"),
    };

    let user2 = user1.clone();

    println!("user1 = {:?}", user1);
    println!("user2 = {:?}", user2);
}

这里调用:

user1.clone()

会创建一个新的 User

其中:

id 是 u64,可以直接复制
name 是 String,会重新复制一份字符串内容

内存可以理解为:

user1.name ───> 堆内存 "Alice"

user2.name ───> 另一块堆内存 "Alice"

所以 user1user2 分别拥有不同的堆内存。

程序结束时:

释放 user1.name 的堆内存
释放 user2.name 的堆内存

不会重复释放同一块内存。

7. Copy 和 Clone 的本质区别

Copy:自动复制,不能自定义复杂逻辑

let b = a;

如果 aCopy 类型,Rust 自动复制。

例如:

fn main() {
    let a = 10;
    let b = a;

    println!("{}", a);
    println!("{}", b);
}

i32 很小,没有堆内存,没有释放逻辑,所以可以 Copy

Clone:显式复制,可以有成本

let b = a.clone();

例如:

fn main() {
    let s1 = String::from("hello");
    let s2 = s1.clone();

    println!("{}", s1);
    println!("{}", s2);
}

clone()String 来说,会重新分配堆内存并复制字符串内容。

所以 Rust 要你显式写出来:

.clone()

这样你能意识到这里可能有性能成本。

8. 为什么 Rust 不让 Copy 做深拷贝?

你可能会想:

那 Rust 为什么不让 StringCopy 自动变成深拷贝?

比如:

let s2 = s1;

自动复制一份新的堆内存,不就安全了吗?

Rust 不这样设计,原因有几个。

8.1 Copy 必须是便宜、简单、可预测的

Copy 的含义是:

像复制数字一样,简单、快速、隐式地复制。

例如:

let a = 1;
let b = a;

这很轻量。

但如果 String 也可以 Copy,下面这行代码可能会偷偷分配内存:

let s2 = s1;

这会让性能变得不透明。

Rust 的原则是:

可能昂贵的操作,必须显式写出来。

所以复制 String 必须写:

let s2 = s1.clone();

8.2 Copy 不能自定义逻辑

Copy 是一种简单的按位复制。

它不会调用你自定义的复制逻辑。

Clone 可以自定义复制逻辑。

例如:

#[derive(Debug)]
struct User {
    id: u64,
    name: String,
}

impl Clone for User {
    fn clone(&self) -> Self {
        Self {
            id: self.id,
            name: self.name.clone(),
        }
    }
}

Clone 可以明确告诉 Rust:

name 字段需要调用 String 的 clone()

但是 Copy 不做这种复杂逻辑。

9. CopyDrop 不能共存

String 是需要释放资源的类型,它内部实现了类似释放堆内存的逻辑。

在 Rust 中,这类释放逻辑来自 Drop

你可以理解为:

String 生命周期结束时,会自动调用 Drop,释放堆内存。

Copy 类型不能实现 Drop

为什么?

因为如果一个类型是 Copy,它可能被偷偷复制很多份。

let a = value;
let b = a;
let c = a;
let d = a;

如果这些值都需要释放同一个资源,就非常危险。

所以 Rust 规定:

实现了 Drop 的类型不能 Copy。

String 需要 Drop,因此不能 Copy。

10. 对比:纯数字字段的 struct 可以 Copy

#[derive(Debug, Clone, Copy)]
struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let p1 = Point { x: 10, y: 20 };

    let p2 = p1;

    println!("p1 = {:?}", p1);
    println!("p2 = {:?}", p2);
}

这里可以。

因为 Point 里面只有:

i32
i32

它们都是 Copy 类型。

内存上只是复制两个整数,没有堆内存,没有资源释放问题。

11. 对比:含 String 字段的 struct 不能 Copy

#[derive(Debug, Clone)]
struct User {
    id: u64,
    name: String,
}

fn main() {
    let u1 = User {
        id: 1,
        name: String::from("Alice"),
    };

    let u2 = u1;

    println!("{:?}", u2);

    // println!("{:?}", u1); // 报错
}

因为 name: String 不是 Copy

如果你想继续使用 u1,就用:

let u2 = u1.clone();

完整示例:

#[derive(Debug, Clone)]
struct User {
    id: u64,
    name: String,
}

fn main() {
    let u1 = User {
        id: 1,
        name: String::from("Alice"),
    };

    let u2 = u1.clone();

    println!("u1 = {:?}", u1);
    println!("u2 = {:?}", u2);
}

12. 如果字段是 &str,能不能 Copy?

可以,但要理解区别。

#[derive(Debug, Clone, Copy)]
struct User {
    id: u64,
    name: &'static str,
}

fn main() {
    let u1 = User {
        id: 1,
        name: "Alice",
    };

    let u2 = u1;

    println!("{:?}", u1);
    println!("{:?}", u2);
}

这个可以 Copy

因为 &str 是引用。

复制引用只是复制一个地址和长度,不复制字符串内容本身。

可以理解为:

u1.name ─┐
         ├──> 静态字符串 "Alice"
u2.name ─┘

但是注意:

String

拥有者

&str

借用者

区别很大。

13. String&str 的关键区别

类型 是否拥有数据 是否需要释放 是否 Copy
String 拥有字符串数据 需要
&str 借用字符串数据 不需要
&String 借用 String 不需要
u64 直接存值 不需要

所以这个不能 Copy

#[derive(Debug, Clone, Copy)]
struct User {
    name: String,
}

这个可以 Copy

#[derive(Debug, Clone, Copy)]
struct UserRef<'a> {
    name: &'a str,
}

示例:

#[derive(Debug, Clone, Copy)]
struct UserRef<'a> {
    id: u64,
    name: &'a str,
}

fn main() {
    let name = String::from("Alice");

    let u1 = UserRef {
        id: 1,
        name: &name,
    };

    let u2 = u1;

    println!("{:?}", u1);
    println!("{:?}", u2);
}

这里复制的是引用,不是复制 String 本体。

14. 业务对象为什么一般不建议 Copy?

例如:

#[derive(Debug, Clone)]
struct Role {
    id: u64,
    code: String,
    name: String,
    permissions: Vec<String>,
}

这个类型包含:

String
String
Vec<String>

这些都涉及堆内存。

如果允许它自动 Copy,下面这行代码可能非常昂贵:

let role2 = role1;

你看不出来它复制了多少权限、多少字符串、多少堆内存。

所以 Rust 要求你显式写:

let role2 = role1.clone();

这样你能清楚知道:

这里正在复制一个完整的 Role 对象,可能有成本。

15. 更推荐的写法:读取时传引用,不要随便 clone

很多初学者遇到所有权报错,会直接 .clone()

例如:

#[derive(Debug, Clone)]
struct User {
    id: u64,
    name: String,
}

fn print_user(user: User) {
    println!("{:?}", user);
}

fn main() {
    let user = User {
        id: 1,
        name: String::from("Alice"),
    };

    print_user(user.clone());

    println!("{:?}", user);
}

这能运行,但不一定好。

如果函数只是读取用户信息,更推荐传引用:

#[derive(Debug)]
struct User {
    id: u64,
    name: String,
}

fn print_user(user: &User) {
    println!("{:?}", user);
}

fn main() {
    let user = User {
        id: 1,
        name: String::from("Alice"),
    };

    print_user(&user);

    println!("{:?}", user);
}

这样不会复制 String,性能更好。

16. 函数传参中的区别

16.1 Copy 类型传参

#[derive(Debug, Clone, Copy)]
struct UserId(u64);

fn print_user_id(id: UserId) {
    println!("{:?}", id);
}

fn main() {
    let id = UserId(1001);

    print_user_id(id);

    println!("{:?}", id);
}

id 传入函数后仍然能用。

因为 UserIdCopy

16.2 含 String 的 struct 传参

#[derive(Debug)]
struct User {
    id: u64,
    name: String,
}

fn print_user(user: User) {
    println!("{:?}", user);
}

fn main() {
    let user = User {
        id: 1,
        name: String::from("Alice"),
    };

    print_user(user);

    // println!("{:?}", user); // 报错
}

这里 user 被移动进函数了。

因为 User 不是 Copy

如果只是读取,应该改成:

fn print_user(user: &User) {
    println!("{:?}", user);
}

调用:

print_user(&user);

17. 实战设计建议

17.1 适合 Copy 的类型

这些通常可以 Copy

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
struct UserId(u64);

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
struct RoleId(u64);

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
enum UserStatus {
    Enabled,
    Disabled,
    Locked,
}

它们特点是:

小
简单
没有堆内存
没有资源释放逻辑
复制成本极低

17.2 适合 Clone 但不适合 Copy 的类型

#[derive(Debug, Clone)]
struct User {
    id: UserId,
    username: String,
    nickname: String,
    status: UserStatus,
}

#[derive(Debug, Clone)]
struct Role {
    id: RoleId,
    code: String,
    name: String,
}

它们特点是:

包含 String、Vec、HashMap 等堆内存字段
复制可能有成本
不能自动 Copy
需要显式 clone()

18. 总结

含有 String 字段的 struct 只能 Clone,不能 Copy,根本原因是:

String 拥有堆内存
String 生命周期结束时需要释放堆内存
Copy 是隐式按位复制
如果 String 可以 Copy,会导致多个变量指向同一块堆内存
最终可能重复释放,造成内存安全问题
所以 Rust 禁止 String 实现 Copy
因此包含 String 的 struct 也不能 Copy

最常见的判断规则:

字段都是 i32、u64、bool、char、简单 enum:可以 Copy

字段里有 String、Vec、HashMap、Box、文件句柄、数据库连接:不能 Copy,最多 Clone

只是读取数据:优先传 &T

确实需要一份独立数据:使用 .clone()

一个合理的业务写法:

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
struct UserId(u64);

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum UserStatus {
    Enabled,
    Disabled,
    Locked,
}

#[derive(Debug, Clone)]
struct User {
    id: UserId,
    username: String,
    nickname: String,
    status: UserStatus,
}

这里:

UserId 是数字包装,适合 Copy
UserStatus 是简单枚举,适合 Copy
User 包含 String,不适合 Copy,只适合 Clone

 

问题2:String Copy的时候 为什么不能像手动 .clone() 样的效果呢,数据一起复制出来一份 ??

不能的核心原因是:

Copy 的语义就是“像复制整数一样,隐式、廉价、按位复制”。 而 String::clone() 的语义是“显式分配一块新堆内存,然后把字符串内容复制过去”。

这两个语义完全不一样。

1. Copy 不是“自动调用 clone()”

很多初学者容易这样想:

let s2 = s1;

能不能让 Rust 自动变成:

let s2 = s1.clone();

对于 String 来说,这样看起来确实很方便。

但 Rust 故意不这么设计。

因为在 Rust 里:

let b = a;

对于 Copy 类型,含义是:

直接复制栈上的那几个字节

而不是:

调用某个函数,分配内存,再复制内容

2. String 的 clone() 做了什么?

例如:

fn main() {
    let s1 = String::from("hello");

    let s2 = s1.clone();

    println!("{}", s1);
    println!("{}", s2);
}

String::clone() 大概做了这些事:

1. 读取 s1 的字符串内容
2. 在堆上重新申请一块内存
3. 把 "hello" 的字节复制到新内存中
4. 创建一个新的 String
5. 让 s2 拥有这块新内存

内存效果类似:

s1 ───> 堆内存 A:"hello"

s2 ───> 堆内存 B:"hello"

这是安全的。

但是这不是简单复制,它有明显成本:

要分配内存
要复制内容
字符串越大,成本越高

3. Copy 做的是什么?

例如:

fn main() {
    let a = 10;
    let b = a;

    println!("{}", a);
    println!("{}", b);
}

i32Copy

它的复制可以理解为:

把 a 栈上的 4 个字节复制给 b

非常简单。

再看结构体:

#[derive(Debug, Clone, Copy)]
struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let p1 = Point { x: 10, y: 20 };

    let p2 = p1;

    println!("{:?}", p1);
    println!("{:?}", p2);
}

Point 里面只有两个 i32

所以:

let p2 = p1;

只是复制两个整数。

没有分配内存,没有释放资源,没有复杂逻辑。

4. 如果 String 的 Copy 等于自动 clone,会有什么问题?

假设 Rust 允许:

let s1 = String::from("hello");
let s2 = s1; // 自动深拷贝

看起来很好。

但是问题在于:Rust 中很多地方都会发生“看起来像普通传递”的操作。

例如函数传参:

fn print_name(name: String) {
    println!("{}", name);
}

fn main() {
    let name = String::from("Alice");

    print_name(name);

    println!("{}", name);
}

如果 StringCopy,那 print_name(name) 就会偷偷复制一份完整字符串。

等价于:

print_name(name.clone());

这就很危险了。

因为你看代码的时候,以为只是传了一个变量:

print_name(name);

但背后可能做了:

分配内存
复制字符串
构造新 String
释放旧 String

Rust 不希望昂贵操作偷偷发生。

5. 一个更明显的例子

假设有一个很大的字符串:

fn handle_log(log: String) {
    println!("{}", log.len());
}

fn main() {
    let log = "A".repeat(100_000_000);

    handle_log(log);

    println!("{}", log.len());
}

如果 String 可以像 Copy 一样自动 clone,那么这一句:

handle_log(log);

可能会偷偷复制 100MB 数据。

如果在循环里:

fn handle_log(log: String) {
    println!("{}", log.len());
}

fn main() {
    let log = "A".repeat(100_000_000);

    for _ in 0..10 {
        handle_log(log);
    }
}

如果 StringCopy = 自动 clone,这段代码可能会偷偷复制 10 次 100MB 数据。

也就是:

复制 1GB 数据
多次堆内存分配
性能灾难

所以 Rust 要求你显式写:

handle_log(log.clone());

这样你一眼就知道:

这里正在复制数据,可能有成本

6. Rust 的设计原则:昂贵操作必须显式

Rust 很强调这一点:

便宜、简单、无副作用的复制:可以 Copy
可能昂贵、可能分配内存的复制:必须 clone()

所以:

let a = 10;
let b = a;

可以自动复制。

但:

let s1 = String::from("hello");
let s2 = s1.clone();

必须显式 .clone()

这样代码的性能成本更清楚。

7. Copy 是一种承诺

一个类型实现了 Copy,等于告诉别人:

这个类型可以随便复制
复制成本很低
复制不会分配资源
复制不会执行复杂逻辑
复制后原值还能继续用

例如:

#[derive(Debug, Clone, Copy)]
struct UserId(u64);

这个很合理。

fn query_user(id: UserId) {
    println!("{:?}", id);
}

fn main() {
    let id = UserId(1001);

    query_user(id);

    println!("{:?}", id);
}

UserId 只是一个 u64 包装,复制它没问题。

但如果是:

struct User {
    id: u64,
    name: String,
}

如果它也能 Copy,那复制 User 就可能复制 String 堆内存。

这不是一个“低成本复制”的类型。

8. Copy 不能写自定义复制逻辑

Clone 可以自定义:

#[derive(Debug)]
struct User {
    id: u64,
    name: String,
}

impl Clone for User {
    fn clone(&self) -> Self {
        Self {
            id: self.id,
            name: self.name.clone(),
        }
    }
}

这里可以明确写:

name: self.name.clone()

也就是复制一份新的字符串内容。

但是 Copy 不是这样。

Copy 基本上是告诉编译器:

这个类型直接按位复制就行

你不能让 Copy 变成:

复制时执行一堆自定义逻辑
复制时申请内存
复制时调用函数

如果要这些行为,就应该用 Clone

9. String 直接按位复制为什么不行?

String 内部大概是:

String {
    ptr,
    len,
    capacity,
}

例如:

let s1 = String::from("hello");

内存类似:

栈上 s1
┌────────────┐
│ ptr ───────┼────> 堆内存 "hello"
│ len = 5    │
│ cap = 5    │
└────────────┘

如果对 StringCopy 的那种按位复制,就会得到:

栈上 s1
┌────────────┐
│ ptr ───────┼────┐
│ len = 5    │    │
│ cap = 5    │    │
└────────────┘    │
                  ├──> 堆内存 "hello"
栈上 s2            │
┌────────────┐    │
│ ptr ───────┼────┘
│ len = 5    │
│ cap = 5    │
└────────────┘

这就变成:

s1 和 s2 都认为自己拥有同一块堆内存

程序结束时:

s1 释放一次
s2 再释放一次

同一块内存被释放两次,会出严重问题。

所以 String 不能 Copy

10. Rust 为什么不把 Copy 改成“深拷贝”?

因为那样 Copy 就失去了本来的意义。

现在 Rust 的规则很清楚:

Copy = 简单按位复制
Clone = 显式复制,可能有复杂逻辑

如果让 Copy 自动深拷贝,那么:

let b = a;

这行代码就可能有两种完全不同的成本:

复制 i32:几乎没有成本
复制 String:分配内存 + 复制字符串
复制 Vec<User>:可能复制大量对象
复制 HashMap:可能复制整张表

代码表面看起来都一样:

let b = a;

但背后的成本完全不可预测。

Rust 不喜欢这种隐式成本。

11. 为什么 Java / Go 好像可以?

有些语言里对象变量赋值看起来很方便:

User u2 = u1;

但这通常不是深拷贝,而是复制引用。

也就是说:

u1 ─┐
    ├──> 同一个 User 对象
u2 ─┘

Rust 不是这种默认模型。

Rust 的 String 是明确的资源所有者。

它要保证:

一块资源同一时间只有一个明确所有者

所以:

let s2 = s1;

表示所有权移动。

如果你真的要复制数据,必须写:

let s2 = s1.clone();

12. 含 String 的 struct 为什么也是一样?

例如:

#[derive(Debug, Clone)]
struct User {
    id: u64,
    name: String,
}

这个结构体里:

id: u64      可以 Copy
name: String 不能 Copy

因为只要有一个字段不能 Copy,整个结构体就不能 Copy

如果你写:

#[derive(Debug, Clone, Copy)]
struct User {
    id: u64,
    name: String,
}

会报错。

原因是:

User 如果 Copy,就要求 name 也 Copy
但 String 不能 Copy
所以 User 不能 Copy

正确写法:

#[derive(Debug, Clone)]
struct User {
    id: u64,
    name: String,
}

使用时:

fn main() {
    let user1 = User {
        id: 1,
        name: String::from("Alice"),
    };

    let user2 = user1.clone();

    println!("{:?}", user1);
    println!("{:?}", user2);
}

13. 那什么时候用 clone,什么时候用引用?

不要为了绕过所有权就到处 .clone()

如果只是读取,应该传引用:

#[derive(Debug)]
struct User {
    id: u64,
    name: String,
}

fn print_user(user: &User) {
    println!("{:?}", user);
}

fn main() {
    let user = User {
        id: 1,
        name: String::from("Alice"),
    };

    print_user(&user);

    println!("{:?}", user);
}

这里没有复制 User,也没有复制 String

如果你确实需要一份独立的数据,再用:

let user2 = user1.clone();

14. 可以类比成这三种情况

情况一:移动所有权

let s2 = s1;

含义:

s1 把字符串所有权交给 s2
s1 失效
没有复制堆数据

成本低。

情况二:显式 clone

let s2 = s1.clone();

含义:

s1 还在
s2 是一份新的 String
堆数据复制了一份

安全,但有成本。

情况三:借用

let r = &s1;

含义:

r 只是借用 s1
不拥有数据
不复制数据

成本最低,但有生命周期限制。

15. 总结

StringCopy 不能设计成自动 .clone() 效果,主要原因是:

1. Copy 的语义是隐式、简单、按位复制
2. Clone 的语义是显式、可自定义、可能有成本的复制
3. String::clone() 需要分配堆内存并复制内容
4. Rust 不希望昂贵操作在 let b = a、函数传参、循环中偷偷发生
5. Copy 类型通常要求复制便宜、无资源管理、无 Drop
6. String 拥有堆内存,需要 Drop,所以不能 Copy

最重要的一句话:

Rust 不是做不到让 String 自动 clone,
而是故意不这么设计。
因为 Rust 要让“可能昂贵的复制”显式暴露出来。

所以:

let s2 = s1;

表示移动所有权。

let s2 = s1.clone();

表示明确复制一份新数据。

let r = &s1;

表示只借用,不复制。