They are moved in exact same fashion as any other type. Old copy may remain valid, sure, but it's still only valid as long as it exists.
And if you want to remove that issue you would need to forget about dynamic memory management, no local variables, no heap… indeed classic BASIC doesn't have or need ownership, but that would be entirely different language from Rust.
Sorry, but I think your reply is not very helpful -- at least I do not really understand it. I think, at least as a simplified model, one can ignore ownership and borrowing concerns for Copy types (types implementing the copy trait), as these types are just copied, not moved. I don't know all the possible definitions of move operations, but I would call it a move for Rust, when the old source variable is invalid after the move and can not be used any longer. E.g, moving a String is copying only the struct with capacity, length, and the pointer to the heap data. The actual heap data remain unchanged.
[EDIT] An important point is, that COPY types typically contain no heap data. Types with heap data. e.g. strings, have the issue, that heap data have to be deallocated when owners go out of scope, so two owners could cause a double free. For plain copy types this problem does not exist.
There four major events that may happen to variable and that affect owenership:
Variable (place in memory) is created (new function is started, memory is alloacted on heap).
Value is placed into variable, it “takes ownership of that value” (not we can borrow).
Value is taken out of variable, it “loses ownership of that value” (now we can no longer borrow).
Variable (place in memory) is destroyed (function ends, memory is deallocated).
For Copy types #3 and #4still happen, they just always happen simultaneously. And #1 and #2 may happen at different times, still.
Imagine Rust dialect (that's not even imaginary, it's under discussions!) where some type may optionally yet silently call clone when you do something like a = b… they would behave like Copy types in some modules and like non-Copy in other modules… does it mean that for them ownership would or wouldn't exist depending on whether “auto-claim” is disabled or enabled?
It depends on what all you consider "ownership" to entail. Borrow checking still applies and variables (and temporaries) so go out of scope. Sometimes it's useful to consider a field owned even if it can be copied. Etc.
So you are introducing difference between “unitialized” and “moved-out” variable.
What's the difference and where does it matter?
Of course, with “ownership” being an informal term (it's not in Rust Reference, but only in Rust Tutorial) we would never know what's “one and true” definition of “onwership”, but I don't know where that separation of “not yet initialized” and “moved out” value would make sense.
Maybe it would help to establish an idea that Copy types don't have ownership… but then what we use that clarification for?
Sounds like complication for the sake of complication to me.
My understanding is that "a variable owns a value" simply means the same thing as "a variable contains a value". So:
let x = 5;
"x contains 5" and that is synonymous with "x owns 5".
So if you don't want to "think of ownership" you can just think about what variable "contains" what value, but it's the same exact thing, regardless of whether the type is Copy or not.
Another actual problem is, that for basic stuff like
let x = 5;
often the term binding is used, e.g x is bound to "5". This can generate the the impression, that really two distinct entities are involved, the variable x, and the actual value, both stored at different locations in memory and somehow "linked". That this is an unfortunate terminology was confirmed in the "internals" forum some time ago.
I agree. I also find this terminology super confusing. To me the term "binding" is a synonym for "named reference", and that's how it is really used in other languages such as Python and SML because in those languages such a statement really creates a reference. I think Rust was influenced by SML and that's where the terminology may have come from but at the same time the original meaning of the word was lost!
I don't know whether it would help, the idea was to discuss it. I've started with Rust days ago, so am not stating things as eternal truths, but to discuss it and learn from others.
My initial thought was that copyable isn't a new concept. A vec<int> would be moved if assigned to another var, but not the int.
If one applies the idea that the ints "own something" then doing *&path_to_int would be moving it and leaving a hole in the vector.
This interferes with the idea of ownership, but not with the idea of copying.
I'm sure there are inconveniences, I just don't know them.
Inconvenience arises when you want to write generic code. Okay, Vec<int> would be moved, int would be copied… but what if you are writing code that deals with abstract type T… would it be movied or copied?
Rust's answer, today, is that all types can always be moved and could be treated as non-copy type, no exceptions.
But in a certain places, in context where both you and compiler know that some types T is, actually, T: Copy – there and only there – these types have “superpower” of keeping valid, fully-functional clone behind after being moved.
That means that in a generic code you, normally, don't think about whether your code is Copy or not… you just assume it's not Copy and if it's actually copy… oh, well, not a big loss, code is still correct and works.
Compare with something like Java where primitive types and non-primitive types have radically different properties and where you always have to remember whether you are using int or Integer.
This *bx access doesn't error because we aren't accessing through a reference?
fn main() {
let bx = Box::new(String::new());
let z = *bx;
}
Follows the pointer to the String. I didn't expect it to be moved though.
So here first case is moved, second case can't be copied. I think they classify as:
Variable assignment, so it's moved
Attempt to copy through reference, so can't be done.
fn main() {
// case 1
let bx = Box::new(String::new());
let z = *bx;
// fails when using "moved" variable bx
// println!("{bx:?}");
// case 2
// fails in attempt to copy String through a ref.
let bx2 = Box::new(String::new());
let z2 = **&bx2;
}
Box<_> is magical; it's * operation is built in which admits more refined borrow checking, and the initialization / deinitialization of it's place is tracked like it is for local variables.
The hypothetical traits to make this magic available to others is often called DerefMove and/or DerefPure.