bigywater.tech
Rust · 17

Copy Trait 为什么可以避免 Move

上一篇说赋值会移动,但 let b = a 对 i32 就没有移动 —— 因为它是 Copy。这篇把「哪些类型是 Copy」直接编译出来,再验证 Copy 的两个限制(必须带 Clone、不能有 Drop)和 Copy 的真实代价:它就是一次按位复制。

21 种
逐个编译判断是否 Copy 的类型数
E0184
Copy 类型实现 Drop 的错误
≈ 21 µs
复制一个 1 MiB 的 [u8; N](release)

Copy 类型

实测 · 本机执行输出
  源码:
      1 | fn main() {
      2 |     let a = 5;
      3 |     let b = a;
      4 |     println!("a = {a}, b = {b}");
      5 |     let p = (1, 2.5);
      6 |     let q = p;
      7 |     println!("p = {:?}, q = {:?}", p, q);
      8 | }
  编译:成功
  运行(退出码 0):
    a = 5, b = 5
    p = (1, 2.5), q = (1, 2.5)

哪些是 Copy

实测 · 本机执行输出
  类型                    是 Copy 吗  
  i32                   是         
  f64                   是         
  bool                  是         
  char                  是         
  ()                    是         
  &i32                  是         
  &mut i32              否         
  (i32, i32)            是         
  (i32, String)         否         
  [i32; 3]              是         
  [String; 1]           否         
  String                否         
  Vec<i32>              否         
  Box<i32>              否         
  Option<i32>           是         
  Option<String>        否         
  std::rc::Rc<i32>      否         
  fn(i32) -> i32        是         
  std::cell::Cell<i32>  否         
  &str                  是         
  std::ops::Range<i32>  否         

derive(Copy) 的限制

实测 · 本机执行输出
  源码:
      1 | #[derive(Copy)]
      2 | struct P { x: i32 }
      3 | fn main() {}
  编译:失败
    error[E0277]: the trait bound `P: Clone` is not satisfied
       --> <实验目录>/src/u17b.rs:2:8
        |
      1 | #[derive(Copy)]
        |          ---- in this derive macro expansion
      2 | struct P { x: i32 }
        |        ^ the trait `Clone` is not implemented for `P`
        |
    note: required by a bound in `Copy`
       --> <rust 标准库源码>/library/core/src/marker.rs:454:17
        |
    454 | pub trait Copy: Clone {
        |                 ^^^^^ required by this bound in `Copy`
    help: consider annotating `P` with `#[derive(Clone)]`
        |
      2 + #[derive(Clone)]
      3 | struct P { x: i32 }
        |
    …(共 22 行)
  源码:
      1 | #[derive(Clone, Copy)]
      2 | struct P { x: i32 }
      3 | impl Drop for P { fn drop(&mut self) {} }
      4 | fn main() {}
  编译:失败
    error[E0184]: the trait `Copy` cannot be implemented for this type; the type has a destructor
     --> <实验目录>/src/u17c.rs:2:8
      |
    1 | #[derive(Clone, Copy)]
      |                 ---- in this derive macro expansion
    2 | struct P { x: i32 }
      |        ^ `Copy` not allowed on types with destructors
      |
    note: destructor declared here
     --> <实验目录>/src/u17c.rs:3:19
      |
    3 | impl Drop for P { fn drop(&mut self) {} }
      |                   ^^^^^^^^^^^^^^^^^^
    
    error: aborting due to 1 previous error
    
    For more information about this error, try `rustc --explain E0184`.

Copy 的成本

实测 · 本机执行输出
  源码:
      1 | use std::hint::black_box;
      2 | use std::time::Instant;
      3 | fn main() {
      4 |     for &n in &[16usize, 4096, 1 << 20] {
      5 |         macro_rules! bench { ($N:expr) => {{
      6 |             let a = black_box([7u8; $N]);
      7 |             let t = Instant::now();
      8 |             let mut acc = 0u64;
      9 |             for _ in 0..2000 { let b = black_box(a); acc += b[0] as u64; }
     10 |             black_box(acc);
     11 |             t.elapsed().as_nanos() as f64 / 2000.0
     12 |         }} }
     13 |         let ns = match n { 16 => bench!(16), 4096 => bench!(4096), _ => bench!(1048576) };
     14 |         println!("[u8; {:>7}] 每次赋值(拷贝)约 {:>9.0} ns", n, ns);
     15 |     }
     16 | }
  编译:成功
  运行(退出码 0):
    [u8;      16] 每次赋值(拷贝)约         1 ns
    [u8;    4096] 每次赋值(拷贝)约       129 ns
    [u8; 1048576] 每次赋值(拷贝)约     20866 ns

计时用 std::hint::black_box 防止编译器把复制优化掉,数字是单次运行的结果;小到 1 ns 的值已经接近计时本身的精度。

分步演示

Copy
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / rustc 1.98.1(brew)/ cargo 1.98.1;单个文件用 rustc 直接编译,需要依赖时用 cargo(经代理从 crates.io 下载);计时用 std::time::Instant,数字每次运行会有小幅抖动。计时使用 release 构建与 Instant,数字每次运行会有抖动
    • 没有验证:Copy 在泛型代码里的单态化代价、闭包是否 Copy(取决于捕获,这里没有测)、Copy 对 ABI(寄存器传参还是栈传参)的影响、clone 对 Copy 类型是否被优化成复制
    京ICP备2026067502号-1京公网安备11010502063487号