不需要转移所有权
基础数据类型的值可以直接被拷贝, 因为它们的结构通常都很简单, 拷贝的效率很高.
而且有时候需要深拷贝一个值, 这样的话新的值与原先的值可以共存, 而且双方没有任何关联.
上面的情竞, 都不需要再转移值的所有权.
Copy trait
当一个类型实现了 Copy
trait 后, 该类型的变量在赋值时, 进行的是复制的操作, 而不是转移(move)操作.
#[allow(unused_variables)] fn main() { // bool let enabled = true; let enabled2 = enabled; assert_eq!(enabled, enabled2); // i32 let x = 42_i32; let x2 = x; assert_eq!(x, x2); // f64 let radius = 2.78_f64; let radius2 = radius; assert_eq!(radius, radius2); // array let fib = [1, 1, 2, 3, 5, 8]; let fib2 = fib; assert_eq!(fib, fib2); // tuple let point = (3, 2); let point2 = point; assert_eq!(point, point2); // string slice let s = "Rust"; let s2 = s; assert_eq!(s, s2); // slice let b = &[1, 2, 3]; let b2 = b; assert_eq!(b, b2); // char let c = '中'; let c2 = c; assert_eq!(c, c2); }
上面的代码很好地演示了基础类型字面量的拷贝, 在标准库中都为它们实现了 Copy
trait.
这里的拷贝是将它们
这里类型包括:
- 整数类型
- 浮点类型
- bool
- 元组 tuple, 要求元组中的元素也都是基础数据类型
- 数组 array, 要求数组中的元素也都是基础数据类型
- 字符 char
- 切片 slice
- 字符串字面量 string literal
下面来解释一下数组的拷贝过程, 首先看看上面代码对应的汇编代码:
#![allow(unused)] fn main() { .LBB54_6: ; fib[0] = 1; movl $1, 376(%rsp) ; fib[1] = 1; movl $1, 380(%rsp) ; fib[2] = 2; movl $2, 384(%rsp) ; fib[3] = 3; movl $3, 388(%rsp) ; fib[4] = 5; movl $5, 392(%rsp) ; fib[5] = 8; movl $8, 396(%rsp) ; fib2[0] = fib1[0]; ; fib2[1] = fib1[1]; movq 376(%rsp), %rax movq %rax, 400(%rsp) ; fib2[2] = fib1[2]; ; fib2[3] = fib1[3]; movq 384(%rsp), %rax movq %rax, 408(%rsp) ; fib2[4] = fib1[4]; ; fib2[5] = fib1[5]; movq 392(%rsp), %rax movq %rax, 416(%rsp) ; assert_eq!(fib, fib2); leaq 376(%rsp), %rax movq %rax, 424(%rsp) leaq 400(%rsp), %rax movq %rax, 432(%rsp) movq 424(%rsp), %rdi movq %rdi, 64(%rsp) movq 432(%rsp), %rsi movq %rsi, 72(%rsp) callq _ZN4core5array8equality103_$LT$impl$u20$core..cmp..PartialEq$LT$$u5b$U$u3b$$u20$N$u5d$$GT$$u20$for$u20$$u5b$T$u3b$$u20$N$u5d$$GT$2eq17h6d520c7c19838dbfE }
整个过程如下图所示:
如何"深拷贝" 字符串 - Clone trait
前文演示了 C++ 中如何深拷贝一个字符串对象, 在 Rust 中实现同样的操作也很容易.
fn main() { let s = "Rust".to_owned(); let s2 = s.clone(); assert_eq!(s, s2); }
生成的汇编代码如下:
.type .L__unnamed_14,@object
.section .rodata.cst4,"aM",@progbits,4
.L__unnamed_14:
.ascii "Rust"
.size .L__unnamed_14, 4
.section .text._ZN12clone_string4main17hb10a0993d22ed25dE,"ax",@progbits
.p2align 4, 0x90
.type _ZN12clone_string4main17hb10a0993d22ed25dE,@function
_ZN12clone_string4main17hb10a0993d22ed25dE:
.Lfunc_begin6:
.cfi_startproc
.cfi_personality 155, DW.ref.rust_eh_personality
.cfi_lsda 27, .Lexception6
subq $168, %rsp
.cfi_def_cfa_offset 176
; let s = "Rust".to_owned();
leaq .L__unnamed_14(%rip), %rsi
leaq 32(%rsp), %rdi
movq %rdi, 24(%rsp)
movl $4, %edx
callq _ZN5alloc3str56_$LT$impl$u20$alloc..borrow..ToOwned$u20$for$u20$str$GT$8to_owned17ha8562f57be6c6ad8E
movq 24(%rsp), %rsi
.Ltmp33:
; let s2 = s.clone();
movq _ZN60_$LT$alloc..string..String$u20$as$u20$core..clone..Clone$GT$5clone17hdbaa59186bb9a20dE@GOTPCREL(%rip), %rax
leaq 56(%rsp), %rdi
callq *%rax
.Ltmp34:
jmp .LBB40_3
.LBB40_1:
.Ltmp45:
; drop(s);
leaq 32(%rsp), %rdi
callq _ZN4core3ptr42drop_in_place$LT$alloc..string..String$GT$17hb4f79947c261455cE
.Ltmp46:
jmp .LBB40_12
.LBB40_2:
.Ltmp44:
movq %rax, %rcx
movl %edx, %eax
movq %rcx, 152(%rsp)
movl %eax, 160(%rsp)
jmp .LBB40_1
.LBB40_3:
; assert_eq!(s, s2);
leaq 32(%rsp), %rax
movq %rax, 80(%rsp)
leaq 56(%rsp), %rax
movq %rax, 88(%rsp)
movq 80(%rsp), %rdi
movq %rdi, (%rsp)
movq 88(%rsp), %rsi
movq %rsi, 8(%rsp)
.Ltmp35:
callq _ZN62_$LT$alloc..string..String$u20$as$u20$core..cmp..PartialEq$GT$2eq17hc2b51f3ddbfca1c2E
.Ltmp36:
movb %al, 23(%rsp)
jmp .LBB40_6
.LBB40_4:
.Ltmp40:
; drop(s2);
leaq 56(%rsp), %rdi
callq _ZN4core3ptr42drop_in_place$LT$alloc..string..String$GT$17hb4f79947c261455cE
.Ltmp41:
jmp .LBB40_1
.LBB40_5:
.Ltmp39:
movq %rax, %rcx
movl %edx, %eax
movq %rcx, 152(%rsp)
movl %eax, 160(%rsp)
jmp .LBB40_4
.LBB40_6:
movb 23(%rsp), %al
testb $1, %al
jne .LBB40_8
jmp .LBB40_7
.LBB40_7:
movq 8(%rsp), %rdx
movq (%rsp), %rsi
movb $0, 103(%rsp)
movq $0, 104(%rsp)
movzbl 103(%rsp), %edi
.Ltmp37:
leaq .L__unnamed_15(%rip), %r8
leaq 104(%rsp), %rcx
callq _ZN4core9panicking13assert_failed17h6e6ea08a2257a330E
.Ltmp38:
jmp .LBB40_9
.LBB40_8:
.Ltmp42:
leaq 56(%rsp), %rdi
callq _ZN4core3ptr42drop_in_place$LT$alloc..string..String$GT$17hb4f79947c261455cE
.Ltmp43:
jmp .LBB40_10
.LBB40_9:
ud2
.LBB40_10:
leaq 32(%rsp), %rdi
callq _ZN4core3ptr42drop_in_place$LT$alloc..string..String$GT$17hb4f79947c261455cE
addq $168, %rsp
.cfi_def_cfa_offset 8
retq
.LBB40_11:
.cfi_def_cfa_offset 176
.Ltmp47:
movq _ZN4core9panicking16panic_in_cleanup17hd62aa59d1fda1c9fE@GOTPCREL(%rip), %rax
callq *%rax
.LBB40_12:
movq 152(%rsp), %rdi
callq _Unwind_Resume@PLT
.Lfunc_end40:
.size _ZN12clone_string4main17hb10a0993d22ed25dE, .Lfunc_end40-_ZN12clone_string4main17hb10a0993d22ed25dE
.cfi_endproc
.section .gcc_except_table._ZN12clone_string4main17hb10a0993d22ed25dE,"a",@progbits
.p2align 2, 0x0
以上操作完成之后, 内存的结构如下图所示:
上图中的 copy()
函数, 实际上是调用的 slice::to_vec()
函数实现的, 最终会调用
copy_nonoverlapping
, 它等价于 libc 中的 memcpy()
,
它的核心代码如下所示:
#![allow(unused)] fn main() { pub trait ConvertVec { fn to_vec<A: Allocator>(s: &[Self], alloc: A) -> Vec<Self, A> where Self: Sized; } impl<T: Copy> ConvertVec for T { #[inline] fn to_vec<A: Allocator>(s: &[Self], alloc: A) -> Vec<Self, A> { let mut v = Vec::with_capacity_in(s.len(), alloc); // SAFETY: // allocated above with the capacity of `s`, and initialize to `s.len()` in // ptr::copy_to_non_overlapping below. unsafe { s.as_ptr().copy_to_nonoverlapping(v.as_mut_ptr(), s.len()); v.set_len(s.len()); } v } } }
一个更复杂的深拷贝示例
#![allow(dead_code)] #![allow(clippy::inconsistent_digit_grouping)] use std::mem::size_of; #[derive(Debug, Clone)] pub struct Book { title: String, isbn: u64, price: f64, } fn main() { assert_eq!(size_of::<Book>(), 40); let books = vec![ Book { title: "Programming Rust".to_owned(), isbn: 978_1_492_05259_3, price: 69.99, }, Book { title: "Rust Atomics and Locks".to_owned(), isbn: 978_1_098_11944_7, price: 59.99, }, ]; let mut books_sorted = books.clone(); books_sorted.sort_by(|a, b| a.price.total_cmp(&b.price)); println!("books sorted: {books_sorted:?}"); }
先创建 books
数组, 里面包含了两本书:
然后再创建 books
数组的副本, 并且基于价格对书进行排序, 最后其内存结构如下图所示: