Refs passed as argment are moved or untouched?

Like code below, the complicated and confused behavior is desired?

https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=963adba77ef722433925e5d2577ce48f

I prefer

fn move_it_out(r: &String) {}

fn prefer(r: &String) {
    let p = r as *const String;
    move_it_out(r);
    let r = unsafe{&(*p)};
    // continue use r
}

Yes, it introduces unsafe; but it's a simple rule: all args are moved;

I do not understand the question. Your inline example is certainly not valid Rust. Perhaps you can elaborate a bit.

What's up with your move functoin? As written, it doesn't do anything.

1 Like

If you want the argument to be moved into your function, why not specify that directly instead of trying to move out of an immutable reference?

fn takes_ownership(s: String) { /* ... */ }
1 Like

Fixed

No why not and choose another different way.
It is the confused moving rules related with reference arguments that is focused now

All of your examples work as I expect them to, but I’ve been around Rust for a while. Can you describe in words what you’re expecting to happen?

I prefer move_it_out moving r out --- the only and simple rule my desired.
But in fact, rust do nothing, but there are exceptions except the dothing-nothing rule.
See
https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=963adba77ef722433925e5d2577ce48f

Why I need to remember:

  1. in a normal fn, both ref and mut ref will not moved out;
  2. in a template fn, mut ref will be moved out
  3. in a template fn, ref will not be moved out
  4. in a fn which does not moving out mut ref argments, it can return another mut ref; both seems alive but the before the returning one gone, you can't access the older one

Surely, if you accept all of these and take it as natural, I disagree with you.
It's natural for normal person that: rules should keep simple, and should not have exceptions?

Right, ok, I see the issue now. First, let's cover mutable references. Those are guaranteed to be unique, and to provide a mutable reference to something, you must reborrow it, which leaves the original mutable reference inaccessible until the reborrowed mutable reference is no longer used.

The syntax for reborrowing a mutable reference is &mut *xref. However a mutable reference is automatically reborrowed when no generics are involved. Your test3 consumes the mutable reference because there are generics.

You can explicitly reborrow it, and it will work:

test3(&mut *xref);

As for immutable references, those are not guaranteed to be unique, so they are copied instead of reborrowed, hence the issues go away.

3 Likes

I do not know why mutable references are not automatically reborrowed in the case of generic functions.

It is not how to explain the behavior.
I can keep in brain all of the rules without understanding them, right?

The key is the rule designer may avoid all the complicated things by choosing a simple one.

There’s a tradeoff here that makes it hard to make these kinds of decisions: most of the time, the exceptions are there because they’re the most common case and you don’t want to clutter up all those callsites with line noise. On the flip side of this equation is the extra learning difficulty when exceptions are present.

You can see the same sort of thing in natural languages: the irregular verbs always tend to be the extremely common ones, like “to be”.

2 Likes

Hmm, it's an interesting point. A mutable reference does not implement the Copy trait, but still sometimes behaves like one when it is automatically reborrowed. On the contrary, all other non-Copy types are moved when given to a function.

2 Likes

Can you elaborate on that? The rules are simple: by-value arguments move, by-reference arguments don't. What exactly is the confusing bit?

As for mutable references — indeed, they are sometimes confusing even for the designers of the language (the standard library has had mutable aliasing bugs before IIRC), but that's because the rules are necessary to eliminate mutable aliasing in safe code.

1 Like

For me, simple and no exceptions rule is most important, because it leads a trust-able and clear result.
More than 100 pages of rules consist of "in case of ..." is un-acceptable.
Especially the rules come to you sight only after it has surprised you.

fn test3<T: std::fmt::Display>(a: T) {
    println!("{}", a)
}

which T is a mut reference, and it is not moved when T is a none-mul reference. Surprised?

Sorry, I'm not sure I understand. Is T a mutable reference or not? What is moved?

1 Like

There is a example code above in play. You can check it to see what happened.
And all the complicated rules are not necessary for safe code.

Obviously, all arguments moved out -- no matter they are references or not -- is not dangerous.
The only problem is :
when passes a reference to a sub-function and the caller want to use the reference after that, it needs to re-generate the reference.

which is not complicated in my preferred solution.

fn move_it_out(r: &String) {}

fn prefer(r: &String) {
    let p = r as *const String;
    move_it_out(r);
    let r = unsafe{&(*p)};
    // continue use r
}

&mut T implements Display, but is apparently not automatically reborrowed when passed as an argument to this function. The net result is that calling test3 will consume the reference, making it inaccessible to subsequent code.

This is expected behavior for non-Copy types in general, but surprising for &muts which usually get automatically reborrowed in similar situations.

2 Likes

I really have no idea what you are trying to say with this snippet.

fn move_it_out(r: &String) {}

fn prefer(r: &String) {
    let p = r as *const String;
    move_it_out(r);
    let r = unsafe{&(*p)};
    // continue use r
}

Your move_it_out function does not do anything, and does not consume the reference, as immutable references are Copy, which means that they cannot be consumed.

The raw pointers also do not seem to do anything useful here.

5 Likes