As a matter of policy Rust doesn't try to figure out behavior of functions from their bodies, because that would mean changes in the implementation could accidentally break the API.
While in this example taking ownership is pointless, there are cases where it's meanigful: e.g. a method that closes a file handle would take ownership of the handle to ensure that the handle can't be used anywhere else after it's closed.
In Rust ownership allows mutability (if you own an object, and there are no other references to it, you can do whatever you want to it).
I understand not trying to look at function bodies but the mutability of a function seems to be strictly a property of it's type signature.
But I think what really bothers me is that whether a value is actually passed to a function by pointer or by copying it onto the stack is governed by whether it has the 'copy' trait - not something obvious to the casual reader of the program. Silently converting what looks like a pass by value call to an in-practice pass by reference based on a trait definition elsewhere in the code base just seems excessively magical to me and I'd be much happier if the compiler threw an error at
let answer = add_first_element(v1, v2);
because pass by value requires the copy trait which Vecs v1 and v2 don't have.
Closing a file requires ownership and is actually an RAII action: when the file object itself goes out of scope (which by definition means all extant references have been removed) the handle is closed.
> But I think what really bothers me is that whether a value is actually passed to a function by pointer or by copying it onto the stack is governed by whether it has the 'copy' trait - not something obvious to the casual reader of the program.
That's not correct. The Copy trait means it's semantically copied rather than moved so the original value can still be used, it does not change the technicalities of parameter passing which is always by-value (unless you're explicitly creating a reference) although things may then get optimised differently.
> Silently converting what looks like a pass by value call to an in-practice pass by reference
There's no such thing. Both Vecs are passed by value. Outside of method calls (which have some syntactic sugar) Rust requires references to be created explicitly.
> I'd be much happier if the compiler threw an error at
> let answer = add_first_element(v1, v2);
> because pass by value requires the copy trait which Vecs v1 and v2 don't have.
It has no reason to and it does not. `add_first_element` gets its argument by value, since Vec is not a Copy type v1 and v2 are thus moved into the function and not accessible anymore from outside it.
Ah, no wonder I misunderstood then. I'd forgotten that RAII was something Rust had and that feature does very nicely explain that design decision.
I suppose the reason a Vec doesn't implement Copy then is that its members might not, like a file handle?
That is correct, Vec has a pointer to a heap allocated buffer, meaning if Vec were Copy, you'd get two Vec structures for the same backing buffer and that's a recipe for disaster.
In Rust there is no distinction between mutable owned data and immutable owned data. There are only mutable/immutable references and bindings, but if you own the data, you can mutate it.
fn foo(v: Vec)
fn foo(mut v: Vec)
are the same function. `mut` is not part of the type. Even the first function can do `let mut v = v` and make immutable owned `v` a mutable owned one (since Rust can guarantee there are no other references to it, so the mutation is not observable from outside).It's different from `&mut Vec`, which is a distinct type, and it mutably borrows the Vec.
> If the file handle is just a integer
That's why in Rust such values are wrapped in "newtypes" that don't allow copying, e.g. `struct Handle(i32)`.
> pass by value call to an in-practice pass by reference
Passing ownership of the `Vec` doesn't copy the items.
Ownership/references and passing by value/reference are different concepts.
`Box<Foo>` is an owned value, but it's implemented as a regular pointer (`Foo* ` in C). `&[Foo]` is a reference, but it's actually a small struct passed by value (`struct Ref {size_t len; Foo *data}` in C).
The difference between a copy and a move is whether you can still use the old value, and this is checked in the type system.