如何创建包装数组的自定义类型以规避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
相关产品推荐
相关产品推荐

