文章目录一、为什么需要所有权二、所有权三大规则三、移动语义Move3.1 问题引入3.2 String 的内存结构3.3 浅拷贝的问题3.4 Rust 的解决方案移动语义四、简单类型的拷贝Copy trait4.1 整型的行为不同4.2 Copy trait4.3 Copy 和 Drop 互斥五、深拷贝Clone trait5.1 使用 clone()5.2 手动实现 Clone六、Copy vs Clone 对比七、函数调用与所有权转移7.1 传参时的移动7.2 返回值转移所有权八、函数参数与返回值配对模式九、常见陷阱与最佳实践十、总结与速查十一、思考题参考链接返回首页 | 上篇 | 下篇理解所有权的核心规则 —— 移动语义、Clone、Copy trait一、为什么需要所有权在开始学习所有权之前先来看看其他语言是怎么管理内存的语言内存管理方式优点缺点C/C手动管理性能最好极易出错Java/Go/Python垃圾回收(GC)使用简单运行时开销STW问题Rust所有权无GC性能好学习曲线陡Rust 的所有权系统在编译时就能保证内存安全既不需要 GC也不会出现悬垂指针。这就是 Rust 最独特的特性。二、所有权三大规则所有权系统基于三条简单规则每个值有唯一的所有者owner同一时刻只能有一个可变引用下篇细讲当所有者离开作用域值会被自动释放作用域示例:{// s 未声明letshello;// s 进入作用域生效// 可以使用 s}// s 离开作用域drop 被调用内存释放对于 String 类型离开作用域时会自动调用 drop 函数释放堆内存。三、移动语义Move3.1 问题引入fnmain(){lets1String::from(hello);lets2s1;println!({},s1);// ❌ 编译错误println!({},s2);// ✅ 正常工作}为什么 s1 不能用了这要从 String 的内存结构说起。3.2 String 的内存结构String 在内存中由三部分组成存储在栈上s1 (栈) --------------------------- | ptr | len | capacity | --------------------------- | ↓ 堆内存[h][e][l][l][o]ptr指向堆内存的指针len当前长度5capacity总容量3.3 浅拷贝的问题如果执行 let s2 s1只拷贝栈上的三部分浅拷贝会出现问题s1 (栈) s2 (栈) --------------------------- --------------------------- | ptr | len | capacity | | ptr | len | capacity | --------------------------- --------------------------- | | └────────┬───────────────────────────┘ ↓ 堆内存[h][e][l][l][o]当 s1 和 s2 都离开作用域时同一块堆内存会被释放两次double free这是严重的内存错误。3.4 Rust 的解决方案移动语义Rust 的做法是let s2 s1 时将 s1 的栈数据移动到 s2s1 被标记为无效。lets1String::from(hello);lets2s1;// s1 的所有权移动到 s2// s1 不再有效编译器禁止使用移动后 s1 (失效) s2 (有效) --------------------------- --------------------------- | ptr | len | capacity | | ptr | len | capacity | --------------------------- --------------------------- | ↓ 堆内存[h][e][l][l][o]这样 s2 离开作用域时只会释放一次内存。四、简单类型的拷贝Copy trait4.1 整型的行为不同fnmain(){letx5;letyx;// x 没有被移动println!(x {}, y {},x,y);// ✅ 两个都可以用}为什么整型和 String 的行为不同因为整型是存储在栈上的不需要堆分配。let y x 只是把 5 这个值拷贝了一份。4.2 Copy trait实现 Copy trait 的类型赋值时会隐式拷贝而不是移动// 实现了 Copy 的类型部分// 整数i8, i16, i32, i64, i128, isize, u8, ...// 浮点数f32, f64// 布尔bool// 字符char// 元组仅当所有元素都是 Copy 时如 (i32, i32)4.3 Copy 和 Drop 互斥structMyStruct{data:String,// String 没有实现 Copy}// ❌ 编译错误String 没有实现 Copy// impl Copy for MyStruct {}重要任何需要 drop 处理如释放堆内存的类型都不能实现 Copy。五、深拷贝Clone trait5.1 使用 clone()如果你确实需要完整复制堆上的数据深拷贝使用 .clone()fnmain(){lets1String::from(hello);lets2s1.clone();// 完整的深拷贝println!(s1 {}, s2 {},s1,s2);// ✅ 两者都有效}内存结构深拷贝后s1 (栈) s2 (栈) ----------------- ----------------- | ptr | len | | ptr | len | ----------------- ----------------- ↓ ↓ 堆[h][e][l][l][o] 堆[h][e][l][l][o] 两份独立数据5.2 手动实现 Clone可以为自定义类型实现 Clone#[derive(Debug, Clone)]structPerson{name:String,age:u32,}fnmain(){letp1Person{name:String::from(Alice),age:30,};letp2p1.clone();// 深拷贝println!(p1: {:?}, p2: {:?},p1,p2);// 两者都可用}六、Copy vs Clone 对比特性CopyClone发生时机隐式赋值、传参时自动发生显式需要调用 .clone()性能开销极低栈拷贝memcpy可能很大堆数据需深拷贝实现条件所有成员都必须是 Copy 类型无限制可为任意类型能否自定义不能自定义只能是按位拷贝可以自定义 clone() 方法典型类型整数、浮点、bool、char、数组String、Vec、HashMap、自定义类型选择建议小数据且频繁传递考虑实现 Copy如包装整数的结构体需要完整拷贝使用 Clone但要考虑性能不确定保持所有权转移移动这是最安全高效的七、函数调用与所有权转移7.1 传参时的移动fnmain(){letsString::from(hello);take_ownership(s);// s 移动到函数内// println!({}, s); // ❌ s 已无效letx5;make_copy(x);// x 是 Copy 类型不会移动println!({},x);// ✅ 仍然有效}fntake_ownership(some_string:String){println!({},some_string);}// some_string 离开作用域内存释放fnmake_copy(some_int:i32){println!({},some_int);}// 无内存释放7.2 返回值转移所有权fnmain(){lets1gives_ownership();// 返回值所有权给 s1lets2String::from(hello);lets3takes_and_gives_back(s2);// s2 移动进函数返回值给 s3}// s1 和 s3 被 drops2 已移动无事发生fngives_ownership()-String{letsString::from(hello);s// 所有权移出}fntakes_and_gives_back(s:String)-String{s// 接收后返回}八、函数参数与返回值配对模式如果需要在函数调用后继续使用原值有几种模式模式 1返回元组fnmain(){lets1String::from(hello);let(s2,len)calculate_length(s1);println!({} 的长度是 {},s2,len);}fncalculate_length(s:String)-(String,usize){letlengths.len();(s,length)// 返回原值和计算结果}但这种写法很繁琐下一篇讲的引用才是更好的解决方案。模式 2使用 clonefnmain(){lets1String::from(hello);letlencalculate_length(s1.clone());// 传入副本println!({} 的长度是 {},s1,len);// s1 仍然有效}fncalculate_length(s:String)-usize{s.len()}九、常见陷阱与最佳实践陷阱 1误以为赋值是深拷贝lets1String::from(hello);lets2s1;// ❌ 误以为是拷贝实际是移动println!({},s1);// 编译失败陷阱 2函数调用后还想用原值fnprocess(s:String){// 处理...}letsString::from(hello);process(s);println!({},s);// ❌ 所有权已转移陷阱 3对 Copy 类型误解letv1:Veci32vec![1,2,3];letv2v1;// 移动Vec 没有 Copyletv3v1.clone();// 深拷贝letarr1[1,2,3];// 数组元素是 Copyletarr2arr1;// 拷贝数组实现 Copy最佳实践总结场景推荐做法需要在多处使用同一个String 使用引用下篇需要独立副本使用 .clone()小数据且频繁赋值考虑使用 Copy 类型不确定所有权让 Rust 编译器告诉你十、总结与速查核心概念概念说明示例所有者每个值有唯一所有者let s String::from()移动所有权转移原值失效let s2 s1Copy隐式栈拷贝需实现 Copy trait整数、浮点数等Clone显式深拷贝调用 .clone()s2 s1.clone()Drop离开作用域自动释放内存编译器自动调用决策树栈上堆上是否是否需要复制一个值类型存储在...实现 Copy?需要独立副本?隐式拷贝无开销移动语义使用 .clone使用引用下篇见重要规则移动后不能使用原变量Copy 类型不会移动Clone 是显式深拷贝有性能开销所有需要释放内存的类型都不能实现 Copy十一、思考题以下代码能编译吗为什么fnmain(){letsString::from(hello);lets2s;lets3s2.clone();println!({} {},s2,s3);}设计一个结构体 Point包含两个 i32 字段能否实现 Copy为什么以下代码的输出是什么fnmain(){letmutx10;letyx;x5;println!(x {}, y {},x,y);}参考链接Rust Book - What is OwnershipRust Book - Copy and CloneRust by Example - Ownership返回首页 | 上篇 | 下篇
Rust 学习(6)-所有权规则、移动语义、Clone 与 Copy
发布时间:2026/7/16 4:40:14
文章目录一、为什么需要所有权二、所有权三大规则三、移动语义Move3.1 问题引入3.2 String 的内存结构3.3 浅拷贝的问题3.4 Rust 的解决方案移动语义四、简单类型的拷贝Copy trait4.1 整型的行为不同4.2 Copy trait4.3 Copy 和 Drop 互斥五、深拷贝Clone trait5.1 使用 clone()5.2 手动实现 Clone六、Copy vs Clone 对比七、函数调用与所有权转移7.1 传参时的移动7.2 返回值转移所有权八、函数参数与返回值配对模式九、常见陷阱与最佳实践十、总结与速查十一、思考题参考链接返回首页 | 上篇 | 下篇理解所有权的核心规则 —— 移动语义、Clone、Copy trait一、为什么需要所有权在开始学习所有权之前先来看看其他语言是怎么管理内存的语言内存管理方式优点缺点C/C手动管理性能最好极易出错Java/Go/Python垃圾回收(GC)使用简单运行时开销STW问题Rust所有权无GC性能好学习曲线陡Rust 的所有权系统在编译时就能保证内存安全既不需要 GC也不会出现悬垂指针。这就是 Rust 最独特的特性。二、所有权三大规则所有权系统基于三条简单规则每个值有唯一的所有者owner同一时刻只能有一个可变引用下篇细讲当所有者离开作用域值会被自动释放作用域示例:{// s 未声明letshello;// s 进入作用域生效// 可以使用 s}// s 离开作用域drop 被调用内存释放对于 String 类型离开作用域时会自动调用 drop 函数释放堆内存。三、移动语义Move3.1 问题引入fnmain(){lets1String::from(hello);lets2s1;println!({},s1);// ❌ 编译错误println!({},s2);// ✅ 正常工作}为什么 s1 不能用了这要从 String 的内存结构说起。3.2 String 的内存结构String 在内存中由三部分组成存储在栈上s1 (栈) --------------------------- | ptr | len | capacity | --------------------------- | ↓ 堆内存[h][e][l][l][o]ptr指向堆内存的指针len当前长度5capacity总容量3.3 浅拷贝的问题如果执行 let s2 s1只拷贝栈上的三部分浅拷贝会出现问题s1 (栈) s2 (栈) --------------------------- --------------------------- | ptr | len | capacity | | ptr | len | capacity | --------------------------- --------------------------- | | └────────┬───────────────────────────┘ ↓ 堆内存[h][e][l][l][o]当 s1 和 s2 都离开作用域时同一块堆内存会被释放两次double free这是严重的内存错误。3.4 Rust 的解决方案移动语义Rust 的做法是let s2 s1 时将 s1 的栈数据移动到 s2s1 被标记为无效。lets1String::from(hello);lets2s1;// s1 的所有权移动到 s2// s1 不再有效编译器禁止使用移动后 s1 (失效) s2 (有效) --------------------------- --------------------------- | ptr | len | capacity | | ptr | len | capacity | --------------------------- --------------------------- | ↓ 堆内存[h][e][l][l][o]这样 s2 离开作用域时只会释放一次内存。四、简单类型的拷贝Copy trait4.1 整型的行为不同fnmain(){letx5;letyx;// x 没有被移动println!(x {}, y {},x,y);// ✅ 两个都可以用}为什么整型和 String 的行为不同因为整型是存储在栈上的不需要堆分配。let y x 只是把 5 这个值拷贝了一份。4.2 Copy trait实现 Copy trait 的类型赋值时会隐式拷贝而不是移动// 实现了 Copy 的类型部分// 整数i8, i16, i32, i64, i128, isize, u8, ...// 浮点数f32, f64// 布尔bool// 字符char// 元组仅当所有元素都是 Copy 时如 (i32, i32)4.3 Copy 和 Drop 互斥structMyStruct{data:String,// String 没有实现 Copy}// ❌ 编译错误String 没有实现 Copy// impl Copy for MyStruct {}重要任何需要 drop 处理如释放堆内存的类型都不能实现 Copy。五、深拷贝Clone trait5.1 使用 clone()如果你确实需要完整复制堆上的数据深拷贝使用 .clone()fnmain(){lets1String::from(hello);lets2s1.clone();// 完整的深拷贝println!(s1 {}, s2 {},s1,s2);// ✅ 两者都有效}内存结构深拷贝后s1 (栈) s2 (栈) ----------------- ----------------- | ptr | len | | ptr | len | ----------------- ----------------- ↓ ↓ 堆[h][e][l][l][o] 堆[h][e][l][l][o] 两份独立数据5.2 手动实现 Clone可以为自定义类型实现 Clone#[derive(Debug, Clone)]structPerson{name:String,age:u32,}fnmain(){letp1Person{name:String::from(Alice),age:30,};letp2p1.clone();// 深拷贝println!(p1: {:?}, p2: {:?},p1,p2);// 两者都可用}六、Copy vs Clone 对比特性CopyClone发生时机隐式赋值、传参时自动发生显式需要调用 .clone()性能开销极低栈拷贝memcpy可能很大堆数据需深拷贝实现条件所有成员都必须是 Copy 类型无限制可为任意类型能否自定义不能自定义只能是按位拷贝可以自定义 clone() 方法典型类型整数、浮点、bool、char、数组String、Vec、HashMap、自定义类型选择建议小数据且频繁传递考虑实现 Copy如包装整数的结构体需要完整拷贝使用 Clone但要考虑性能不确定保持所有权转移移动这是最安全高效的七、函数调用与所有权转移7.1 传参时的移动fnmain(){letsString::from(hello);take_ownership(s);// s 移动到函数内// println!({}, s); // ❌ s 已无效letx5;make_copy(x);// x 是 Copy 类型不会移动println!({},x);// ✅ 仍然有效}fntake_ownership(some_string:String){println!({},some_string);}// some_string 离开作用域内存释放fnmake_copy(some_int:i32){println!({},some_int);}// 无内存释放7.2 返回值转移所有权fnmain(){lets1gives_ownership();// 返回值所有权给 s1lets2String::from(hello);lets3takes_and_gives_back(s2);// s2 移动进函数返回值给 s3}// s1 和 s3 被 drops2 已移动无事发生fngives_ownership()-String{letsString::from(hello);s// 所有权移出}fntakes_and_gives_back(s:String)-String{s// 接收后返回}八、函数参数与返回值配对模式如果需要在函数调用后继续使用原值有几种模式模式 1返回元组fnmain(){lets1String::from(hello);let(s2,len)calculate_length(s1);println!({} 的长度是 {},s2,len);}fncalculate_length(s:String)-(String,usize){letlengths.len();(s,length)// 返回原值和计算结果}但这种写法很繁琐下一篇讲的引用才是更好的解决方案。模式 2使用 clonefnmain(){lets1String::from(hello);letlencalculate_length(s1.clone());// 传入副本println!({} 的长度是 {},s1,len);// s1 仍然有效}fncalculate_length(s:String)-usize{s.len()}九、常见陷阱与最佳实践陷阱 1误以为赋值是深拷贝lets1String::from(hello);lets2s1;// ❌ 误以为是拷贝实际是移动println!({},s1);// 编译失败陷阱 2函数调用后还想用原值fnprocess(s:String){// 处理...}letsString::from(hello);process(s);println!({},s);// ❌ 所有权已转移陷阱 3对 Copy 类型误解letv1:Veci32vec![1,2,3];letv2v1;// 移动Vec 没有 Copyletv3v1.clone();// 深拷贝letarr1[1,2,3];// 数组元素是 Copyletarr2arr1;// 拷贝数组实现 Copy最佳实践总结场景推荐做法需要在多处使用同一个String 使用引用下篇需要独立副本使用 .clone()小数据且频繁赋值考虑使用 Copy 类型不确定所有权让 Rust 编译器告诉你十、总结与速查核心概念概念说明示例所有者每个值有唯一所有者let s String::from()移动所有权转移原值失效let s2 s1Copy隐式栈拷贝需实现 Copy trait整数、浮点数等Clone显式深拷贝调用 .clone()s2 s1.clone()Drop离开作用域自动释放内存编译器自动调用决策树栈上堆上是否是否需要复制一个值类型存储在...实现 Copy?需要独立副本?隐式拷贝无开销移动语义使用 .clone使用引用下篇见重要规则移动后不能使用原变量Copy 类型不会移动Clone 是显式深拷贝有性能开销所有需要释放内存的类型都不能实现 Copy十一、思考题以下代码能编译吗为什么fnmain(){letsString::from(hello);lets2s;lets3s2.clone();println!({} {},s2,s3);}设计一个结构体 Point包含两个 i32 字段能否实现 Copy为什么以下代码的输出是什么fnmain(){letmutx10;letyx;x5;println!(x {}, y {},x,y);}参考链接Rust Book - What is OwnershipRust Book - Copy and CloneRust by Example - Ownership返回首页 | 上篇 | 下篇