不需要转移所有权
基础数据类型的值可以直接被拷贝, 因为它们的结构通常都很简单, 拷贝的效率很高.
而且有时候需要深拷贝一个值, 这样的话新的值与原先的值可以共存, 而且双方没有任何关联.
上面的情竞, 都不需要再转移值的所有权.
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
下面来解释一下数组的拷贝过程, 首先看看上面代码对应的汇编代码:
.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(),
它的核心代码如下所示:
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 数组的副本, 并且基于价格对书进行排序, 最后其内存结构如下图所示: