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