不需要转移所有权

基础数据类型的值可以直接被拷贝, 因为它们的结构通常都很简单, 拷贝的效率很高.

而且有时候需要深拷贝一个值, 这样的话新的值与原先的值可以共存, 而且双方没有任何关联.

上面的情竞, 都不需要再转移值的所有权.

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. 这里的拷贝是将它们

这里类型包括:

下面来解释一下数组的拷贝过程, 首先看看上面代码对应的汇编代码:

#![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 }

整个过程如下图所示:

array copy mem layout

如何"深拷贝" 字符串 - 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

以上操作完成之后, 内存的结构如下图所示:

rust clone string

上图中的 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 数组, 里面包含了两本书:

clone book books

然后再创建 books 数组的副本, 并且基于价格对书进行排序, 最后其内存结构如下图所示:

clone book books sorted