You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何创建包装数组的自定义类型以规避Copy trait?

如何将数组类型包装为自定义类型以规避Copy trait?

背景:用自定义类型规避Copy trait

我们可以通过包装已有类型创建新类型来绕过Copy trait,比如包装bool为MyBool:

#[derive(Debug)]
struct MyBool(bool);

MyBool默认不会实现Copy,能让编译器在变量被移动后仍被使用时抛出错误,避免Copy带来的隐蔽问题。

比如下面的bool代码,即便运行cargo clippy也不会报错——因为bool实现了Copy,线程闭包的move实际是拷贝,主线程还能继续使用原变量:

#![deny(clippy::all, clippy::pedantic, clippy::nursery, warnings, future_incompatible,
 nonstandard_style, non_ascii_idents, clippy::restriction, rust_2018_compatibility,
 rust_2021_compatibility, unused)]
#![allow(clippy::print_stdout, clippy::use_debug, clippy::missing_docs_in_private_items)]

#![allow(clippy::blanket_clippy_restriction_lints)]

fn main() {
    let mut has_spawned: bool = false;
    let handler = std::thread::spawn(move || {
        println!("Before {has_spawned}!"); // false
        has_spawned = true;
        println!("Set {has_spawned}!"); // true
    });
    #[allow(clippy::unwrap_used)]
    handler.join().unwrap();
    println!("Current {has_spawned}!"); // false
}

包装成MyBool后,编译器会抛出我们期望的“移动后借用”错误:

#![deny(clippy::all, clippy::pedantic, clippy::nursery, warnings, future_incompatible,
 nonstandard_style, non_ascii_idents, clippy::restriction, rust_2018_compatibility,
 rust_2021_compatibility, unused)]
#![allow(clippy::print_stdout, clippy::use_debug, clippy::missing_docs_in_private_items)]

#![allow(clippy::blanket_clippy_restriction_lints)]

#[derive(Debug)]
struct MyBool(bool);
fn main() {
    let mut my_has_spawned: MyBool = MyBool(false);
    let handler = std::thread::spawn(move || {
        println!("Before {my_has_spawned:?}!"); // MyBool(false)
        my_has_spawned.0 = true;
        println!("Set {my_has_spawned:?}!"); // MyBool(true)
    });
    #[allow(clippy::unwrap_used)]
    handler.join().unwrap();
    println!("Current {my_has_spawned:#?}!"); // 触发移动后借用错误
}

对应的错误信息:

error[E0382]: borrow of moved value: `my_has_spawned`
   --> /home/user/sandbox/rust/copy_trait/gotcha1/copy_trait_thread_newtype/src/main.rs:20:24
    |
10  |     let mut my_has_spawned:MyBool=MyBool(false);
    |         ------------------ move occurs because `my_has_spawned` has type `MyBool`, which does not implement the `Copy` trait
11  |     //...
12  |     let handler=std::thread::spawn(move || {
    |                                    ------- value moved into closure here
13  |         println!("Before {my_has_spawned:?}!"); //MyBool(false)
    |                           -------------- variable moved due to use in closure
...
20  |     println!("Current {my_has_spawned:#?}!"); // value borrowed here after move, XXX: this is what
    |     -------------------^^^^^^^^^^^^^^-------
    |     |                  |
    |     |                  value borrowed here after move
    |     in this macro invocation (#1)
    |
   ::: /usr/lib/rust/1.64.0/lib/rustlib/src/rust/library/std/src/macros.rs:101:1
    |
101 | macro_rules! println {
    | -------------------- in this expansion of `println!` (#1)
...
106 |         $crate::io::_print($crate::format_args_nl!($($arg)*));
    |                            --------------------------------- in this macro invocation (#2)
    |
   ::: /usr/lib/rust/1.64.0/lib/rustlib/src/rust/library/core/src/macros/mod.rs:906:5
    |
906 |     macro_rules! format_args_nl {
    |     --------------------------- in this expansion of `$crate::format_args_nl!` (#2)

For more information about this error, try `rustc --explain E0382`.
error: could not compile `copy_trait_thread_newtype` due to previous error

数组的问题场景

类似地,数组类型(比如[i32;4])也实现了Copy,下面的代码调用foo(a)时会悄悄拷贝a,主线程后续还能使用原变量,不会触发任何警告或错误:

#![deny(clippy::all, clippy::pedantic, clippy::nursery, warnings, future_incompatible,
 nonstandard_style, non_ascii_idents, clippy::restriction, rust_2018_compatibility,
 rust_2021_compatibility, unused)]
#![allow(clippy::print_stdout, clippy::use_debug, clippy::missing_docs_in_private_items)]

#![allow(clippy::blanket_clippy_restriction_lints)]

#![allow(clippy::default_numeric_fallback)]
#![allow(clippy::dbg_macro)]

fn foo(mut x: [i32; 4]) {
    println!("x(before) = {:?}", x);
    x = [1, 2, 3, 4];
    println!("x(after) = {:?}", x);
}

fn print_type_of<T>(_: &T) {
    println!("{}", core::any::type_name::<T>());
}

fn main() {
    let a = [0; 4];
    println!("{:#?}", print_type_of(&a));
    foo(a); // 悄悄拷贝,而非移动
    println!("a = {:?}", a); // 仍能使用原变量,无错误
}

程序输出:

[i32; 4]
()
x(before) = [0, 0, 0, 0]
x(after) = [1, 2, 3, 4]
a = [0, 0, 0, 0]

我希望创建数组的自定义类型,让foo(a)调用时触发移动而非拷贝,这样编译器会在后续使用a时抛出错误,避免这类隐蔽问题。

核心问题

如何为数组类型创建这样的自定义类型?可以针对特定元素类型、特定长度的数组创建,还是只能基于泛型的数组创建(是否需要在新类型中嵌入长度信息)?

题外话

如果实现特定Clippy lint,能否针对这类场景发出警告或报错?这会很实用。我个人不喜欢Copy trait,因为它会绕过借用检查器,导致代码出现潜在的隐蔽bug——比如仍能使用已“移动”(实际是拷贝)的变量旧值,而编译器不会报错。


内容的提问来源于stack exchange,提问作者correabuscar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 05:01:36