correabuscar
correabuscar

Reputation: 53

How to create my own type wrapping the array type, in order to avoid the Copy trait?

You know how you can create a new type by wrapping an existing type just because you want to avoid the Copy trait?! For example, you have bool and you want a new type MyBool, you do struct MyBool(bool); and then you can use that to avoid the Copy trait.

What I want to know is how do you do it(create a new type, that is) for an array type? eg. for the type of a in let a = [0; 4]; which is [{integer}; 4], array of four integer elements. And can you do it for an array of a specific type X and of a specific len Y ? or, only for an array of type T (think generics) and with/without(?) a specific array length embedded in the new type ?

So, for bool, the following code doesn't show any warnings/errors(even if you run cargo clippy on it):

#![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)] //workaround clippy

// might want to deny later:
//#![allow(clippy::default_numeric_fallback)] // might want to deny later!
//#![allow(clippy::dbg_macro)]

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
}

But I want it to show me errors, thus, I use a new type for bool, 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)] //workaround clippy


#[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=MyBool(true);
        my_has_spawned.0=true;
        println!("Set {my_has_spawned:?}!"); // MyBool(true)
    });
    #[allow(clippy::unwrap_used)]
    handler.join().unwrap();
    println!("Current {my_has_spawned:#?}!"); // value borrowed here after move, XXX: this is what
                                              // I wanted!
}

and now it shows me exactly what I want to see:

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

Similarly to the above, I want to use the new type for this array program that yields no warnings or errors(even through cargo clippy):

#![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)] //workaround clippy

// might want to deny later:
#![allow(clippy::default_numeric_fallback)] // might want to deny later!
#![allow(clippy::dbg_macro)]

//src: https://users.rust-lang.org/t/rust-book-suggestion-add-a-section-regarding-copy-vs-move/1549/2
fn foo(mut x: [i32; 4]) {
    println!("x(before) = {:?}", x);
    x = [1, 2, 3, 4];
    println!("x(after) = {:?}", x);
}

//src: https://stackoverflow.com/a/58119924/19999437
fn print_type_of<T>(_: &T) {
        //println!("{}", std::any::type_name::<T>());
        println!("{}", core::any::type_name::<T>());
}

//struct MyArray(array); //TODO: how?

fn main() {
    let a = [0; 4];
    //a.something();//method not found in `[{integer}; 4]`
    //a=1;//so this is an array
    //dbg!(a);
    println!("{:#?}", print_type_of(&a)); // i32
    foo(a); //sneakily copied! thanks Copy trait!
    println!("a = {:?}", a);//unchanged, doh! but since it was just copied above, can use it here
                            //without errors!
}

output of that is:

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

So you see, I want a new type in order to avoid the copying of a when foo(a); is called, so that the computer/compiler can keep track of when I would introduce such easy bugs in my code, by error-ing instead, which happens only when a is moved(instead of just copied) when foo(a); is called.

Side note: do you think that this clippy lint https://github.com/rust-lang/rust-clippy/issues/9061 if implemented would be able to warn/err for me in such cases? That would be cool!

Personally I dislike the Copy trait for the only reason that it bypasses the borrow checker and thus allows you to write code that introduces subtle bugs. ie. you can keep using the stale value of the "moved"(copied) variable and the compiler won't complain.

Even if there are better ways to do what I want, please also do answer the title question: how I can wrap the array type in my own new type?

Upvotes: 0

Views: 527

Answers (2)

Jmb
Jmb

Reputation: 23329

You can wrap an array the same way you would wrap a bool:

struct MyArray ([i32; 4]);

And you can use generics if you don't want to redefine different wrapper types for each array:

struct MyArray<T, const N: usize> ([T; N]);

Upvotes: 2

Ferdinand Keller
Ferdinand Keller

Reputation: 118

I don't understand why you don't want your variable to be copied. Your variable being copied isn't a bug or won't create bugs.

point 1

The only way in rust to modify data via a function is either :

  • to pass a mutable reference of your variable to a function
  • to assign the result of the function back to your original mutable value

In the example you show, your variable isn't even defined as mutable in the first place, there is no way it would change.

point 2

The Copy trait doesn't bypass the borrow checker, as the borrow checker only goal is :

  • to make sure a pointer always points to valid data
  • to make sure there is always only one owner of the data

In the case you show, you don't involve any pointer, and the data itself is duplicated, so the borrow checker won't give a crap, it isn't even involved. Whatever happens in the function stays in the function.

anyway

Now if you want, for whatever reason, the borrow checker to show you errors in this situation (once again, there is no reason for the borrow checker to be involved), the proper way would probably be to give the ownership of your variable to a dedicated type like a Box, and use the box for your operations (box don't implement the Copy trait).

The following code involving a bool works

fn main() {
    let a = false;
    effect(a);
    println!("main function : {a}");
}

fn effect(mut a: bool) {
    println!("effect before : {a}");
    a = !a;
    println!("effect after : {a}");
}

The following code involving a bool wrapped inside a box doesn't work

`fn main() {
    let a = Box::from(false);
    effect(a);
    println!("main function : {a}"); // error
}

fn effect(mut a: Box<bool>) {
    println!("effect before : {a}");
    *a = !*a;
    println!("effect after : {a}");
}

Now, once again, I don't understand why anyone would want to do this. This change has a performance impact, as you now need to take your variable from the heap before any operation, instead if just reading it from the stack.

Upvotes: 0

Related Questions