Refs passed as argment are moved or untouched?

That is not the real rust code. It is my preferred code, which follows a simple rule:

anything in the argments list will be moved out by a function call.

Thanks for explaining. Indeed, reborrowing seems to be the focus here.

Your move_it_out function does not move out of the reference. It simply copies the reference, then does nothing with the copy.

You cannot move out of references.

4 Likes

Simple is not simplistic, though. There's a good reason why rules around moving, ownership, and borrowing (taking references) are the way they are.

Values must have a single owner in order for automatic memory management via RAII (Drop) to work.

References can't allow moving out of the referred value, because then the referred location would be garbage or it would result ij double-drops/double-frees.

Mutable references are not allowed to be copied because mutable aliasing leads to memory safety errors such as the phenomenon of "iterator invalidation" (which you might be familiar with from C++). Reborrowing is merely a convenience feature that enables more concise code in some situations. You don't have to rely on it, you can always be explicit.

All in all, the ownership and borrowing system is in place for the sake of compile-time memory safety. There's nothing more complex than necessary in them.

Also it looks like some magic transitive moving through references. Apart from not being possible to move out of the referent of a reference for the reasons described above, it would not make sense. Rust is perfectly regular in this regard: the argument of the function is moved. If the argument happens to be a reference, it is moved. The reference itself, and not the referent — after all, it was the reference that you passed to the function, and not its referent.

1 Like

Simple is not simplistic, though. There's a good reason why rules around moving, ownership, and borrowing (taking references) are the way they are.

Values must have a single owner in order for automatic memory management via RAII ( Drop ) to work.

agree

References can't allow moving out of the referred value, because then the referred location would be garbage or it would result ij double-drops/double-frees.

what is moved out is the reference itself, not the referenced object. So, moving out any thing in argument list does not conflict with this.

Mutable references are not allowed to be copied because mutable aliasing leads to memory safety errors such as the phenomenon of "iterator invalidation" (which you might be familiar with from C++). Reborrowing is merely a convenience feature that enables more concise code in some situations. You don't have to rely on it, you can always be explicit.

Yes, if moving out any thing in argument list is the rule, it will even be NOT necessary to introduce the Reborrowing conception.

All in all, the ownership and borrowing system is in place for the sake of compile-time memory safety. There's nothing more complex than necessary in them.

I suspect that you’d quickly be annoyed by this rule if it were implemented. It means that every numeric or boolean argument would need to be explicitly cloned if you wanted to be able to use it again later. This includes when using them inside expressions:

fn quad_roots(a:f64, b:f64, c:f64)->Option<(f64,f64)> {
    let radical:f64 = b.clone() * b.clone() - 4 * a.clone() * c;
    if radical.clone() < 0 { return None; }
    let dist:f64 = radical.sqrt() / (2 * a.clone());
    let center: f64 = -b / (2*a);
    Some((center.clone() - dist.clone(), center + dist))
}
2 Likes

Yes. You are right.
So what is the rule as:

argument passed to a function is a copy if the argument is a Copy; all arguments in the arguments list will be moved out

It is exactly the current rule, except that mut refs should be moved out in any case

Indeed. That is why I, as a long time C++ user, am here. I find it unacceptable to be expected to remember a thousand pages of C++ coding guidelines to stop me running into "foot guns":
https://google.github.io/styleguide/cppguide.html

https://sceweb.uhcl.edu/helm/RationalUnifiedProcess/manuals/cpp/cpp.htm

I'd much rather have the compiler tell me when I am going wrong.

OK, that is perhaps off topic As a Rust newbie I don't understand your question. Have you discovered hole in the Rust anti-alias/borrow checking rules?

Perhaps a full example with a main() and all I could compile would make your issue clearer.

And what is all this "reborrow" everyone is talking about here?

&mut references aren't Copyable, so you normally wouldn't expect code like this to work; the first push call should consume the reference leaving it unavailable for the second call:

fn append2(v: &mut Vec<u32>) {
    v.push(4);
    v.push(7);
}

Because this is a common pattern, &mut is treated specially by the compiler. The above code is silently turned into this behind the scenes:

fn append2(v: &mut Vec<u32>) {
    (&mut *v).push(4);
    (&mut *v).push(7);
}

The &mut *v is the reborrow: you're making a brand-new &mut that points to the same place as the old one, and the borrow checker prevents you from using the original until the new one you made is destroyed.

9 Likes

My understanding is: This happens because reborrowing is a coercion, and for coercions to happen the source and target types need to be known. When passing something as an argument, and the argument is generic, the target type is not known.

2 Likes

Yes, it is the reason.
However, it can be realized when compiler concretizes the template function and generates different code .
It is a bug of compiler.

If you believe it is a bug, why not support a bug report?

I don'r know how.....:slight_smile:

Rust's generics don't work that way, type inference runs on the generic function, not on its monomorphizations.

This is a known limitation, there should already be bug reports about this.

To submit a bug report against the compiler, go to its GitHub issues page and press the green “New Issue” button on the right side just above the list of known bugs.

Rust type checks before monomorphization, and this is intentional, not a "bug". The reason for it is that once a generic function type checks, it's guaranteed to compile (and work as intended) for all possible instantiations.

If type checking depended on the monomorphized code, it would be possible to write a generic function that would compile at first, but it would be rejected for certain instantiations that otherwise satisfy the trait bounds and other constraints. This happens all the time with C++ templates, for example, and it is highly undesirable because it makes generic code a lot less robust, testing harder, and basically involves testing a bunch of different instantiations "exhaustively" in order to ensure a clean compile.

5 Likes

Can't understand what is type checks and monomorphization

If you mean type checks is the process to analyse the template function itself, making sure all using of T satisfies T: trait1 + trait2 ... etc. And monomorphization is replace T with specific type.

Then I think both the 2 places don't concern the T's re-borrowing.

after type checks and monomorphization, a normal function is generated and there is no different with another typing-by-hand normal function.

And we know the re-borrowing occurs when calls a typing-by-hand normal function at the position passing arguments to it, then why it can't happen when calls a template-generated normal function?

1 Like

It's not that it can't – it would indeed be possible to analyze the already-generated (monomorphized) code instead of analyzing the generic code. C++ does that.

However, it comes with all sorts of bad surprises, as I already mentioned in my previous reply. Hence, it results in more robust code to only check the generic code, therefore Rust chose to do that.


And no, type checking isn't only concerned with resolving trait bounds. Borrows are absolutely part of it in Rust, because borrowing is built into the type system.

It's not a bug. This would fundamentally change the language, so this should be an RFC or pre-RFC instead of a bug report.

I am curious about that why it need analyse the generic code or monomorphized code. It just need to add re-borrowing when calls that function, like what has been done for a typing-by-hand normal function.

Seems it happens when analyzing the caller.