What &* is really doing?

Hi,

After posting this question on StackOverflow I realized that I have some confusion with * dereference operator when used with a reference to something not implementing the Deref trait.

I have tried to look for some details on the book, but I didn't find so much details for this simple case.

Consider this simple code:

struct A;

fn mirror(x: &A) -> &A {
    &(*x)
}

fn main() {
    let x = A;
    let y = mirror(&x);
    println!("Original: {:p}\nMirrored: {:p}", &x, y);
}

It is working perfectly, but I was expecting that Rust compiler would complain about ownership and returning a reference to something that is going out of scope, as it is doing if we replace the mirror function body with:

    let y = *x;
    &y

I was thinking that &*x was doing something like the code above, but with a temporary variable.

So, what it is actually doing in this case? :thinking:

Thank you

For a regular reference, &* is a no-op, it just returns the original pointer verbatim.

This is because the dereference operator preserves lvalue-ness: if you take the address of an lvalue that you obtained by dereferencing a pointer, you get its address, which is just the original pointer (or a copy of it, if you will).

In other words, in these (non-special) situations, taking the address and dereferencing are exactly inverse operations, and consecutive ref and deref operations are collapsed, which does not involve the creation of any temporary values.

4 Likes

I'd say that &* is the reborrow operator, meaning that there is no local, as @H2CO3 points out, so that the new borrow can last for the same lifetime as the borrow it borrows from, but it can also last for a shorter lifetime1.

Luckily, reborrows can be implicit for most Rust builtins, so that the ergonomics are great and we don't have to think about this. When dealing with custom structs, however, offering a reborrowing API becomes mandatory.

A good example of this is the Pin<&'a mut T> type, which needs explicit reborrowing with the .as_mut() method.


1 Here is a convoluted example showing the difference between reborrowing and moving (using &mut* rather than &* for the example):

fn main ()
{
    let x = &mut 42;
    drop(&mut* x);
    dbg!(*x);
}
3 Likes

The lifetime side of things and why the compiler isn't getting mad at you for using a local is as follows:

fn foo(x: &usize) -> &usize {
    //...
}

Will be elided to be the following:

fn foo<'a>(x: &'a usize) -> &'a usize {
    //...
}

Therefore, when analyzing any use of foo, the compiler will only look at its signature, which says that the 'a lifetime will also be bound to its return value. It does not look at the implementation.

When you own a value: &'a X, there are two lifetimes here 'a, the obvious one which is actually in the name, and then there's 'value (Or whatever name you give your variable) which is akin to saying the scope of the variable value itself. Therefore 'a: 'value because 'a must be bigger than 'value to be able to create a reference, if not then we'd have a dangling reference at some point.

When you say value_2 = &*value then you take a reference to an item with lifetime 'a, which is in this case unrelated to the lifetime of value. In other words, what 'a says in this context is how long the variable under value will live, which is the essence of lifetimes, parameters which will describe the scope or context a variable will live at a certain location. Another way of looking at it is saying that the lifetime is part of the object's name because we're describing the lifetime of the name in the scope where it's declared.

I apologize if I may have ranted, but I hope that can clear something up.

4 Likes

On the contrary, a precise lifetime-based approach is eventually the most helpful one :slight_smile:

1 Like

Note that in an unsafe block, you can also use &* to turn a raw pointer into a reference.

1 Like

I like @OptimisticPeach explanation; here is a different take.
&var you are constructing a reference.
&* your selecting the Deref trait, as opposed to the DerefMut or DerefMove traits.
The same applies to index.

p.s. the really doing you find from reading the compiler source/specification.

Thanks, all of you!
You gave me different point of views and now I have a more clear picture of deferencing. :+1: