[Solved]How to comprehend "Deref for &'a T "?

There is an impl in libcore::ops:

impl<'a, T: ?Sized> Deref for &'a T {
    type Target = T;

    fn deref(&self) -> &T { *self }  //What is the type of self here???
}

Does it mean that use * on &T we will still get a &T???

1 Like

Well, &self is short for self: &Self, which means it's &&'a T. Well, &'b &'a T to be more precise: every borrowed pointer has a lifetime, but in some cases, the compiler lets you leave it out so you don't go completely mad having to write explicit lifetimes absolutely everywhere.

No, when you use * on a &T, you get a T. Well, actually, you get something which is of type T, but the compiler knows it's still really behind a pointer. Really, *expr is actually *((&expr).deref()) *Deref::deref(&expr): notice that the compiler automatically dereferences the thing it gets back from deref.

Oh, * on &T and &mut T and *const T and *mut T are special in that the compiler already knows how to do it. It doesn't actually use the Deref thing for those.

Because it's useful for generic code to tell a teeny white lie about how it's implemented. It means code can accept T: Deref<Target=U> without having to special-case the built in pointer types.

Yeah, but I figured they would come up so I kinda put words in your mouth. Sorry about that. Here, have a sherbet allsort.

8 Likes

@DanielKeep Thank you!
But there are some points I don't understand.
I find some explainations for "*" at Rust reference:

* : Dereference. When applied to a pointer it denotes the pointed-to location. For pointers to mutable locations, the resulting lvalue can be assigned to. On non-pointer types, it calls the deref method of the std::ops::Deref trait, or the deref_mut method of the std::ops::DerefMut trait (if implemented by the type and required for an outer expression that will or could mutate the dereference), and produces the result of dereferencing the & or &mut borrowed pointer returned from the overload method.

Q1:
So,becase &&T is a pointer, *self just get a &T not call deref(). In this way,&T is a pointer, **self won't call deref() either?

Q2:

When will compiler automatically dereference something for us? always?
In fact, fn deref(&self) -> &T :&T means that we want a borrowed value not a owned one.

Let's get one thing clear:

  • The type returned by the deref trait method (Deref::deref) is a reference: &T
  • The type "returned" by the deref operator (*my_borrow) is a value: T.

Why the distinction? Well, the reason why the operator returns a value is to allow moving a value out of the reference. With DerefMut, it also allows replacing the original object with an expression like *ptr = value;

The reason why deref returns a reference is to allow &*ptr to reborrow an object without ever moving it: (this might seem silly, but wait until the next example after this one)

fn main() {
    struct Value;

    let value = Value;
    let borrow = &value;
    
    // This is invalid
    let tmp      = *borrow; // cannot move out of borrowed content
    let reborrow = &tmp;
    
    // This is okay because the move never occurs
    let reborrow = &(*borrow); // note: parentheses not necessary
}

Aside: are you familiar with the distinction between lvalues and rvalues in c++? If so, it might help to draw the connection that the deref operator produces an lvalue. (if not, it probably isn't worth looking into :stuck_out_tongue:)


There is another similar operator for which it is far more obvious why one might want to be able to reborrow things. Consider the Index trait:

  • Index::index returns a &T...
  • ...but array[i] returns a T, moving the value out of array. (usually an error for non-Copy types)
  • If you instead write &array[i], the move never happens.
fn main() {
    struct Value;

    let vec = vec![Value];

    // This is invalid
    let tmp      = vec[0]; // cannot move out of borrowed content
    let reborrow = &tmp;
    
    // This is okay because the move never occurs
    let reborrow = &(vec[0]); // note: parentheses not necessary
}

'But this is terrible,' you say! 'In both of these examples, all you did was replace a temporary with the code that was used to produce it! The very fact that this changes the meaning of the program means rust as a language is fundamentally broken!'

Is it? Well, perhaps it is. But if things didn't work like this, I can only imagine the language would either be (a) far more painful to use, or (b) far more complicated! Consider what happens when you attempt to access a member of a borrowed struct:

fn example_struct(object: &Object) {
    // This is invalid
    let tmp      = object.member; // cannot move out of borrowed content
    let reborrow = &tmp;
    
    // This is okay because the move never occurs
    let reborrow = &(object.member); // note: parentheses not necessary
}

So in a perhaps unusual way, indexing, dereferencing, and member access are all sides of the same coin. After all, they're all things you can assign to:

fn set_a_bunch_of_values(object: &mut Object) {
    // set up things we want to mutate
    let mut value_to_borrow = Value;
    let borrow = &mut value_to_borrow;
    let mut value = Value;
    let mut vec = vec![Value];

    // in all of the examples below, the expression on the LHS of an assignment
    // is identical to the expression you would write in an RHS to "move out"
    // of the value

    value         = Value;
    object.member = Value;
    vec[0]        = Value; // uses IndexMut
    *borrow       = Value; // uses DerefMut
}

Playpen link with the above examples: Rust Playground

4 Likes

If the compiler knows that it's dealing with a built-in pointer, then *ptr won't call Deref::deref, because the compiler already knows how to dereference a built-in pointer. Deref is implemented for built-in pointers anyway for the sake of generic code.

Well, technically it's not "automatically" dereferencing, since we explicitly asked it to: *expr is explicitly asking for a dereference. The "automatic" part is that it has to call Deref::deref and dereference the result of that.

The compiler will deref things in order to find methods (called deref chaining), or to coerce one kind of pointer to another, simpler kind (this is why you can pass a &String to a function expecting a &str; String implements Deref<Target=str>).

I'm not sure what (or if) you're asking here, but Deref cannot be used to transfer ownership out of a pointer-like thing. In fact, the only thing that can transfer ownership through a dereference is Box, and that's because Box is just straight-up magic.

Thank you! Nice guys!