Why is the return type of Deref::deref itself a reference?
Asked Answered
M

3

27

I was reading the docs for Rust's Deref trait:

pub trait Deref {
    type Target: ?Sized;
    fn deref(&self) -> &Self::Target;
}

The type signature for the deref function seems counter-intuitive to me; why is the return type a reference? If references implement this trait so they can be dereferenced, what effect would this have at all?

The only explanation that I can come up with is that references don't implement Deref, but are considered "primitively dereferenceable". However, how would a polymorphic function which would work for any dereferenceable type, including both Deref<T> and &T, be written then?

Masochism answered 25/7, 2015 at 8:52 Comment(12)
This might interest you reddit.com/r/rust/comments/2umad5/…Conditioning
@JonasTepe So if I'm understanding this correctly, the reason is that we cannot specify functions which return lvalues, so we do it "manually" by returning a reference to an lvalue we already have?Masochism
You would want to return the "correct" l-value. Meaning the value which is owned by self (the value implementor of Deref). You wouldn't want to return some short-lived lvalue. Then you could just as well return an rvalue. And the only way to do so is returning a reference into self.Conditioning
@JonasTepe How could you not return the correct lvalue?Masochism
I just wanted to make a point why Deref needs to return a reference. As for references themselves, you can have a reference to a reference. So why wouldn't you want to dereference them. They in fact all implement Deref.Conditioning
@JonasTepe But if the goal is to dereference the reference, how does that make sense? Wouldn't you just receive another reference to the same object to which the original one was pointing to?Masochism
The Deref trait enables the "*ptr" operation. If it wouldn't be implemented for all references, you were not able to dereference them. The Deref trait exists, so more types (besides references) can be used with this syntax (e.g. smart pointers like Rc or Arc). It is a generalisation of the dereference operation.Conditioning
@JonasTepe Say you have a reference x pointing to some object y. You write *x, and deref gets called. All it does, is return yet another reference to y, because that's what is in its sigunature (and implementation). Can you access the object now? No. Because it's still a reference. You're back where you started. This cannot be how it's implemented for references?Masochism
For normal references that is exactly what it does. They just return themselves in the implementation. However, there is a difference in how you call the deref method. Every trait in std::ops is an operator and the compiler handles those specially. Playpen: play.rust-lang.org/…Conditioning
@jco *x isn't what calls deref, so to speak. *x dereferences an &-ptr. Deref allows an object that isn't a reference to pretend to be a reference, aka. Box<T> can act like an &T. See github.com/rust-lang/rfcs/blob/master/text/….Aeriform
This was cross posted to RedditBecnel
This might be less confusing if the trait were called AsRef instead. Unfortunately we already have a trait with that name...Sunburn
B
19

that references don't implement Deref

You can see all the types that implement Deref, and &T is in that list:

impl<'a, T> Deref for &'a T where T: ?Sized

The non-obvious thing is that there is syntactical sugar being applied when you use the * operator with something that implements Deref. Check out this small example:

use std::ops::Deref;

fn main() {
    let s: String = "hello".into();
    let _: () = Deref::deref(&s);
    let _: () = *s;
}
error[E0308]: mismatched types
 --> src/main.rs:5:17
  |
5 |     let _: () = Deref::deref(&s);
  |                 ^^^^^^^^^^^^^^^^ expected (), found &str
  |
  = note: expected type `()`
             found type `&str`

error[E0308]: mismatched types
 --> src/main.rs:6:17
  |
6 |     let _: () = *s;
  |                 ^^ expected (), found str
  |
  = note: expected type `()`
             found type `str`

The explicit call to deref returns a &str, but the operator * returns a str. It's more like you are calling *Deref::deref(&s), ignoring the implied infinite recursion (see docs).

Xirdus is correct in saying

If deref returned a value, it would either be useless because it would always move out, or have semantics that drastically differ from every other function

Although "useless" is a bit strong; it would still be useful for types that implement Copy.

See also:

Note that all of the above is effectively true for Index and IndexMut as well.

Becnel answered 25/7, 2015 at 13:35 Comment(0)
S
10

The compiler knows only how to dereference &-pointers - but it also knows that types that implement Deref trait have a deref() method that can be used to get an appropriate reference to something inside given object. If you dereference an object, what you actually do is first obtain the reference and only then dereference it.

If deref() returned a value, it would either be useless because it would always move out, or have semantics that drastically differ from every other function which is not nice.

Sudatory answered 25/7, 2015 at 13:17 Comment(0)
M
0

Derefercing is two parts operation. One is taking a reference to data and another is accessing the data behind the reference--which is done with the help of the dereferencing operator *. The Deref trait only accomplishes the first part--taking a reference and then giving it to us. Then the dereferencing operator * follows the reference(memory address) and lets us access that data on that memory address--this part is indeed the compiler's built-in.

fn main()
{
    let age: i32 = 90;
    let r_age = &age;       // first part: taking reference
    println!("{}", *r_age); // second part: accessing the reference
}

Now if we do not have a reference how can we get access to data using the dereferencing operator *?

fn main()
{
   let age: i32 = 90;
   //println!("{}", *age);   // will not work, we did not take reference of age.
   println!("{}", *(&age));
}

Now the Deref trait can be implemented for our own type.

use std::ops::Deref; 

fn main()
{
   let d = OurOwnType(77);
   println!("{}", *d);
   println!("{}", *(d.deref()));
   println!("{}", *(Deref::deref(&d)));
   println!("{}", *(&d.0));
}

struct OurOwnType(i32);

impl std::ops::Deref for OurOwnType
{
    type Target = i32;
    fn deref(&self) -> &Self::Target
    {
        &self.0
    }
}

Behind the scenes of *d compiler invokes the deref method from Deref trait like d.deref() which gives a reference, then the dereferencing operation with the help of the dereferencing operator * lets us access the data. The Deref trait here accomplishes the first part of giving us a reference.

Maher answered 25/11, 2023 at 6:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.