Is the &mut in 'for i in &mut array' reference to the address of the array itself?

There's the code

fn main() {
    let array = &mut [1, 2];
    for i in &mut *array { // ok
        println!("{i}");
    }
    for i in &mut array { // error
        println!("{i}");
    }
}

I've got two questions ) :sweat_smile:

  1. Am I correct in understanding that &mut *array dereferences the array's reference and then creates a mutable reference to the contents?

  2. Is the &mut in 'for i in &mut array' reference to the address of the array itself?

Sure!
let array = &mut [1, 2];
let array2: [i32; 2] = *array;
let array3: &mut [i32; 2] = &mut *array; // same as 'array'

  • array has type &mut [i32; 2]
  • *array has type [i32; 2]
  • &mut *array has type &mut [i32; 2] (again)

And &mut *thing generally is a reborrow of *thing. It may seem pointless on the surface, but like the link says, reborrows are necessary to make &mut _ usable.

Reborrows happen automatically most of the time, but not always. Here:

fn main() {
    let array = &mut [1, 2];
    for i in array {
        println!("{i}");
    }
}

The compiler sees that &mut [i32; 2] implements IntoIterator and calls the into_iter method which takes self by value. The compiler is considering the &mut [i32; 2] as the implementing type without considering reborrows, so it ends up moving (consuming) array instead of reborrowing *array. That means you can't use array afterwards.

But if you use for i in &mut *array in the first loop, array isn't moved (only the reborrow is moved), and you can still use array afterwards.

So that's why you may have seen, and may want, an explicit reborrow.

&mut array is a &mut &mut [i32; 2]. So there's two indirections from the [i32; 2].

3 Likes

Not quite. These are not two individual operations. Rust always merges &* and &mut * into a single operation that gives you the final result without actually dereferencing.

let x = &*array;

is NOT the same as:

let tmp = *array;
let x = &tmp;

Behavior of & and * can also be affected by the Deref trait.

3 Likes

In simple terms, the move occurs because the "&mut [i32; _]" type does not implement the Copy trait? Or does any "&mut _" not implement the Copy trait?

In simple words, any type, no matter a reference, is not a reference, if it does not implement the Copy trait, will it be moved?

Yes, there is no &mut _ type that implements Copy. They cannot,[1] because if they did, they would no longer be exclusive β€” you'd have two &mut simultaneously active and pointing to the same memory, which instantly breaks Rust's memory safety model.

Yes, the rules for moves don't care whether a type is a reference type, only whether it implements Copy.

However, it's not quite that β€œmoves happen if the type is not Copy”. Rather, values of all types are movable, but Copy adds the bonus feature that moving the value doesn't invalidate its previous place. Or you could say: if a type implements Copy, then all move operations on that type are upgraded into copy operations, which offer everything move operations do and more.


  1. I suppose a &mut to a zero-sized type, like &mut [u8; 0] or &mut (), could implement Copy, because no bytes are actually aliased, but the language doesn't offer that special case, and it would be undesirable for libraries that use zero-sized types as global access tokens, so it'd have to be opt-in. β†©οΈŽ

4 Likes

Note that this also applies to the move closures (you may read about them later). These closures use move keyword to move environment into a closure, yet because of how Copy behaves this still leaves functional copy behind.

Sometimes it may be a bit of footgun.

This is at best subjective. It doesn't have to be "a single operation". It doesn't have to involve "merging". Since * means "the place at the following address", and & means "the address of the following place", there's no additional semantics to &*. It's simply a composition of two operations that are inverses of each other, so the idenity naturally falls out of their composition.

That's a property of the use of the place (the assignment/initialization/binding), and not inherently that of the dereferencing operator.

The dereferencing operator gives you a place. If you want, you can move or copy out of it, but you don't have to.

This is really perfect explanation of something that doesn't happen in Rust, isn't it?

Yes, if &* and &mut * would have been identity operations then your explanation would have been natural.

But they not identity operations, they are reborrows! They β€œfeel” like they should be really simple, but they are not (read the RFC for all the gory details).

Another perfect exlanation of something that doesn't happen in Rust, isn't?

If things would have worked like you explained then both of these would have been valid:

fn foo(x: &mut String) {
    let y = &mut *x;
}

fn bar(x: &mut String) {
    let tmp = *x;
    let y = &mut tmp;
}

But, no, foo is Ok while bar is not Ok.

It's possible to model &mut _ and * as distinct operations that compose a certain way depending on context, like is done in the reference. Then &mut * _ isn't a move (or copy) followed by taking a reference because &mut _ introduces a place expression context.

1 Like

Sorry but this doesn't make any sense. A dereference is not a reborrow.

No, you clearly don't understand what I am saying. I already stated that the act of using the place in a certain way is what performs the move. That's the exact same mechanism by which using a binding (that is not behind a reference) also performs a move, or it doesn't, depending on the context.

The location of a place can't be copied or moved, which is what you are assuming I'm saying. I'm not. Values move between places.

First of all, your function bar doesn't only compose & and *. It composes three operations: a dereference, a move to a distinct, newly-declared place, and a reference. That's clearly different from taking a reference to the original place, hence you are attacking a strawman.

But the actual reason why bar doesn't compile is completely different anyway. The reason is that you are trying to move out of a reference without replacing the pointed place with a new value. If it were unconditionally impossible to move out of a reference, full stop, then functions like mem::replace() and mem::swap() would be impossible to implement. So this is another reason that your code snippets don't refute anything I explained.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.