不需要转移所有权

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

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

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

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