Ways to avoid implicit Copy? or warn/error when it happens?


#1

Hi. Is there a way to disable the Copy trait globally? That might not make sense and I’m probably failing to realize how useful it is.
Or a way to get notified(warn/error on compiling my program) when a Copy is happening?

I’m trying to avoid shooting myself in the foot by having the implicit copy happening when I’d be expecting the implicit move to be happening.

An example of that, here:
https://rust-lang.github.io/book/ch04-02-ownership.html#Ownership%20and%20functions

An obvious workaround would probably be to wrap the type that I want Copy disabled for, into a newtype pattern struct, like:

#[derive(Debug)]
struct NoCopyI32(i32);
// ^ does not implicitly derive Copy

fn main() {
    let s = String::from("hello");

    takes_ownership(s);

    let x:NoCopyI32 = NoCopyI32(5);

    makes_copy(x);
    //println!("{:?}",x);//used of moved value!
}

fn takes_ownership(some_string: String) {
    println!("{}", some_string);
}

fn makes_copy(some_integer: NoCopyI32) {
    println!("{:?}", some_integer);
}

I’m still reading the rust book(s), but the following is an example which I think might give a hint on what could go wrong with implicit Copy, or maybe not(but, in principle, I just want to prevent myself from using a stale value because it was implicitly copied - I’d rather have to use .copy() or something when I want that):

#![feature(plugin)]

#![plugin(clippy)]


#![deny(clippy)]
#![deny(clippy_pedantic)]
#![allow(print_stdout)]
#![allow(clone_on_copy)]

fn main() {
    let mut a = [1, 2, 3, 4, 5];

    let first = a[0]; //can I prevent this Copy from happening? a lint for it? eg. I actually wanted &a[0] or a move
    let oldfirst = a[0].clone();//this is acceptable! but implicitly copy like above isn't! TODO: find a way to disable Copy trait!

    a[0] = 7;
    let reffirst = &a[0]; //TODO: find out how to keep a ref and still allow assignment - eg. move this line above the assignment line a[0]=7; and have it work!

    println!("The value of first is: {} {} {}", first, oldfirst, reffirst);
}

Relevant conversation:




This explains better what I was trying to say:


#2

Moves are copies. The only difference is if you can use the value afterward or not; otherwise, they are identical.

There’s no flag to turn off Copy.


#3

If there are semantics attached to the values which may result in severe logic errors if the values are passed around as bare primitives, you do want to define types that reflect those semantics.
Most of the time you end up defining a few struct and enum types anyway and so you can just leave Copy out on some or all of them.


#4

It would probably help to understand why you think this would be shooting yourself in the foot.


#5

I’m unsure, it seems to be mainly unconscious…
One one hand having an assignment(or passing of value to a function) sometimes cause a move, sometimes a copy seems kind of … not what I’d want to have to keep track of in my mind, but rather have the compiler do it for me: either just have move happen all the time or copy happen all the time but not either or , depending on the type(or rather, depending on whether they have the Copy trait implemented or not) - or just have the compile warn me when one of them(the copy preferably) happens so that I can always expect the move to happen and get a warning(or error) when the copy happens. In other words, I wish the move or copy would have to be explicit, maybe like .clone() is.

In truth, I don’t yet know how I would shoot myself in the foot, but I imagine that if I were able to pull out just a field of a complex struct via Copy trait and meanwhile have other fields of that struct change which make that copied value now inconsistent with these changed fields, and then later on I get to Copy out another field from that struct and then present those two copied fields as if they are part of the whole (as eddyb properly explains above) then… I have a problem because those two fields are each part of two different snapshots of that struct and not what I would’ve expected: of the same one snapshot of that struct and keep that struct from changing while I hold them(due to that implicit move that would’ve happened, but an implicit Copy happened instead, because eg. the fields were primitives and thus had the Copy trait) . But I don’t see an example of this to give you at this time - I’m too much of a beginner to have started writing anything.

I’ll let you know if I encounter such a “dangerous” situation where I get bitten by the implicit move-OR-copy on assignment(or value passing to a function).
Though, I wish it were either implicit move all the time in all cases period. OR implicit copy all the time in all cases. I don’t really know how to explain why having either or, like it currently is in Rust, is not good thing, if the above attempt at explaining it isn’t enough.

Cheers


#6

Rust couldn’t copy by default because many types manage pointers into the heap, and copying them would cause their destructor to run twice, leaing to double frees. However, for types that are ‘just data’ - like integers - there are many times in which it would be onerous to have to explicitly clone them. Rust could have not had Copy types, but many mathy algorithms would’ve become quite illegible as a result. Consider let y = x * x; - this is invalid unless x is a Copy type.

I cannot think of any times I have encountered bugs because something was implicitly copied. I’d be interested in hearing about any bugs you do encounter.


#7

The solution there is privacy: don’t let code you don’t trust see the individual fields.


#8

Oh i see what you mean:

use std::ops::Add;

//#[derive(Copy,Clone)] // to fix
#[derive(Debug)]
struct Point {
    x: i32,
    y: i32,
}

impl Add for Point {
    type Output = Point;

    fn add(self, other: Point) -> Point {
        Point { x: self.x + other.x, y: self.y + other.y}
    }
}

fn main() {
    let p1 = Point { x: 1, y: 2 };

    let p3 = p1 + p1;

    println!("{:?}", p3);
}
error[E0382]: use of moved value: `p1`
  --> <anon>:21:19
   |
21 |     let p3 = p1 + p1;
   |              --   ^^ value used here after move
   |              |
   |              value moved here
   |
   = note: move occurs because `p1` has type `Point`, which does not implement the `Copy` trait

error: aborting due to previous error

Got it! Seems like the one for me, until I learn more.

Thanks all! I’ll revisit this after I gain (more) knowledge - reading from 3 places currently: nightly book, rust by example and the other new book


#9

//rant follows, so feel free to ignore it, I’m sure it won’t make any difference - but I just have to express it or else I don’t think I can overcome it.

So, I’m having some serious trouble at the subconscious level, accepting this copy thing :smiley: I know, it’s ridiculous, but I’m just being honest here.(maybeee, unacceptably honest? or just rude)

I was thinking about this:

The way I would “solve” it is: consider "xx" being an expression, or the top/root expression(not contained in any other (sub)expression) and thus any amount of x occurrences inside this expression(or any subexpressions within it) is to be considered as x being moved into the top expression(the xx expression, not the first x or second x (sub)expressions), so it doesn’t have to move into it twice for x*x, but only once.

But in a case like this call some_func(x*x, x); it would fail for the third x, but not for the “x*x”. Because the 2nd arg(aka 3rd x) is another expression, and x already moved into the first expression(arg 1).

Now, granted you still need to copy x twice for “x*x” to work, but this copy isn’t what’s bothering me, it’s the fact that it’s still accessible after the copy that is. Just as steveklabnik mentioned: [quote=“steveklabnik, post:2, topic:6736”]
Moves are copies. The only difference is if you can use the value afterward or not; otherwise, they are identical.
[/quote]

Of course, this is obvious! That’s my problem. That’s what I don’t find acceptable, that with the same syntax, you can have either of the two happening:

fn main() {
    let s = String::from("hello");  // s goes into scope.

    takes_ownership(s);             // s moves into the function...
                                    // ... and so is no longer valid here.
    let x = 5;                      // x goes into scope.

    makes_copy(x);                  // x would move into the function,
                                    // but i32 is Copy, so it’s okay to still
                                    // use x afterward.

} // Here, x goes out of scope, then s. But since s was moved, nothing special
  // happens.

fn takes_ownership(some_string: String) { // some_string comes into scope.
    println!("{}", some_string);
} // Here, some_string goes out of scope and `drop()` is called. The backing
  // memory is freed.

fn makes_copy(some_integer: i32) { // some_integer comes into scope.
    println!("{}", some_integer);
} // Here, some_integer goes out of scope. Nothing special happens.

I’m saying x, should move too! Always! And if you want copy, you could do x.copy() or x.clone() (I forget the difference, but it’s irrelevant for this context)

Why wouldn’t the copy be explicit? especially since it allows you to keep using the binding afterwards.

So, maybe I’m conflating two things:

  1. that a value can be copied
  2. that a value can move
    It seems that 2. can always happen. But if 1. can happen then you choose to not do 2.
    In other words, all values can move, but not all values can be copied(because I don’t know, deep structure with heap and stuff? and maybe needs the user to have a block of code implemented that does the copying - the Copy trait?).
    So, a value can be copy or not - but either way, it could always move, right? I mean, the move and copy seem to be two different things. (and I’m not talking about memcopy and memmove kind of copy and move, but obviously, Rust’s understanding of Copy trait and the binding moving into another place and thus being inaccessible in the current context - so in this sense, moves are not copies)

So, the fact that takes_ownership(s); moves and makes_copy(x); copies, with those functions having the same implementation (just different types used) is just so unacceptable to me considering all other Rust safety stuff. It just boggles my mind, at the subconscious level - which is why I may not be able to full explain it why.
(By moves I mean, s is inaccessible afterwards and by copies I mean x is accessible afterwards.)
I mean, wtf, two different behaviors just based on type being different? Why do you make the programmer keep track of which behavior happens here, instead of the compiler? Why can’t the behavior just be the same regardless of type : aka be move! always! And if you want copy, then do x.copy() or something, so now it’s obvious that x didn’t move(and yeah x could be a struct with Copy trait when it’s not as obvious from reading the code(as the above code is))

I’m guessing this kind of move doesn’t really do any copy, or it does a shallow one anyway. But copy likely does a deep copy(or else I could think of some bugs if the passed copy gets some of it’s on-heap structures modified via interior mutability or something). So in reading some Rust code, where you see assignments or binding being passed to functions you’ll be wondering: geee, I wonder if it’s a copy or move happening here… oh yeah because that’s so f’in explicit.

Bottom line: implicit copy OR move which is what’s currently happening is evil and seems against (what I percieve to be) Rust’s principles.
If anything is wrong(in my mind, right) with Rust so far, this (has)gotta be it.

I’m sorry, for the above, my subconscious is evil(it needs reprogramming, right?) - I had to let it spew its discontent :smiley: but you’ll forgive me, right? I mean, why wouldn’t you? you’re a good person!!(reader) Also, I’ll forgive you for this implicit (copy OR move) in Rust xDD ok, that’s not helping. What I mean to say is, thank you for all that is Rust so far - I’m sure I’ll find a way to workaround this while at the same time not hating it too much xD! even tho I feel so strongly about it, at the subconscious level.


#10

You simply can’t. Copy requires bit-level copies to work, and the closest heap pointer to that, Rc<T>, can’t implement Copy because it needs to update the reference count. You can copy references pointing into the heap, but those don’t own the data.

Also, there is no deep copy/clone in Rust. At all.
Copy lets you do shallow clones (doesn’t go through pointers) and Clone is usually implemented to do “sheep clone” (do the minimal amount of work required to produce a separate value).
This means that, e.g. Rc<T> is cloned by simply doing a refcount increment and returning the same pointer.
There is nothing atm in the standard library or the language to actually do Rc::new((**self).clone()).


#11

One thing to keep in mind is that Copy types are always trivial, they are just “values”. IMO it’s really harmless to leave the original value intact.

For the callee, move and copy are equivalent. For the caller, a copy is strictly more flexible than a move.

IMO this is a very ugly special case. I’d rather keep the language consistent.

  • It only works for copy types
  • It only works if you don’t consider arithmetic expressions equivalent to calling a function.

In the beginning I also thought that a move should have different syntax than a copy, but this would make the language very unergonomic.

The programmer doesn’t have to keep track. The compiler will complain if you use it the wrong way.


#12

ohhh, I completely forgot that operators can be overridden and are thus function calls. I stand very corrected on my reasoning there :slight_smile:

Good point.

Also reading eddyb’s post,
ok, I’m convinced I need to read more to further my understanding… for my reasoning seems flawless :slight_smile: I mean, flawed :smiley:
Thanks all!


#13

Don’t read too much, just start coding! :wink:


#15

If you write the code generically then types have no traits unless you include them in the bounds for the function, So in:

fn x(y : int32)

y would be copyable, but in:

fn x<Y>(y : Y) where Y : Integer

y is not copyable (nor cloneable unless you include a Clone trait bound for Y), and you gain the added advantage that the function will work on all types that implement the Integer trait not just int32, so you can pass int64 for example without rewriting the function.