How does Rust standard implements `Deref` trait for i32?

I am trying to learn the Deref trait. I think I understood what the Deref trait is and what it does.

I am interested to see the codes of how Rust implemented the Deref trait for the i32 type. I googled about it, however, I did not find anything that was relevance to my asking.

Thanks,
Yousuf.

It doesn't.

use std::ops::Deref;

fn main() {
    try_to_deref(&42i32);
}

fn try_to_deref<T: Deref>(v: &T) -> &T::Target {
    v.deref()
}

Produces this error:

error[E0277]: the trait bound `i32: Deref` is not satisfied
 --> src/main.rs:4:18
  |
4 |     try_to_deref(&42i32);
  |     ------------ ^^^^^^ the trait `Deref` is not implemented for `i32`
  |     |
  |     required by a bound introduced by this call
  |
note: required by a bound in `try_to_deref`
 --> src/main.rs:7:20
  |
7 | fn try_to_deref<T: Deref>(v: &T) -> &T::Target {
  |                    ^^^^^ required by this bound in `try_to_deref`
6 Likes

Deref is not implemented for i32, as dereferencing is something you do on references and smart pointers, not on primitives or other non-pointer types. You can see a list of types from the standard library that implement Deref in the Implementors section.

3 Likes

If Deref is not implemented for the i32 type then how does the dereferencing operator * work for the type &i32?

For instance:

let a = &10;
println!("{}",*a);

How does the * operator work on a in println!("{}",*a); this line?

That's because &i32 implements Deref<Target=i32>.

5 Likes

i32 and &i32 are two, distinct types.

8 Likes

I feel a few nontrivial parts of the language need to be understood to understand how Deref and the derefence operator (prefix *) work in Rust:

  • How *i notionally desugars into the Deref operator
    • And what place expressions are
  • The fact that * is built-in for some types and doesn't use the Deref trait

Running example

I'll use this (and some built-in types) as a basis for the examples.

struct MyFieldOne;
struct MyFieldTwo;
struct MyStruct {
    a: MyFieldOne,
    b: MyFieldTwo,
}

// You generally don't want to do this unless the fields is the "only" thing
// you contain in some sense, but for the sake of illustration...
impl Deref for MyStruct {
    type Target = MyFieldOne;
    fn deref(&self) -> &Self::Target {
        &self.a
    }
}

// In contrast this is fine for any field... and for `Self`
impl AsRef<MyFieldTwo> for MyStruct {
    fn as_ref(&self) -> &MyFieldTwo {
        &self.b
    }
}

How *my_struct notionally desugars

The signatures of deref and as_ref look the same -- they look like types of shared reference conversion. And that basically is the pont of AsRef; it's the Into of shared references:

fn example(my_struct: MyStruct) {
    // This compiles
    let b = my_struct.as_ref();
    // You can uncomment this to confirm `b` is a `&MyFieldTwo`
    // let _: () = b;
}
// Side note: You would need an annotation for `b` if there is more than
// one `AsRef<_>` implementation for `MyStruct` due to ambiguity

And if you call deref, which is just a trait method, it works the same way:

fn example(my_struct: MyStruct) {
    // This compiles
    let a = my_struct.deref();
    // You can uncomment this to confirm `a` is a `&MyFieldOne`
    // let _: () = a;
}
// Side note: You can only implement `Deref` once so there can't be
// an ambiguity like there can be with `AsRef<_>`

However, this isn't the case for the dereference operator, *. When * corresponds to a Deref implementation, it looks like

    *my_struct // notionally becomes
    *<MyStruct as Deref>::deref(&my_struct)
//  ^

Note how there's still a * on the front! So when we apply * to my_struct, the type of the expression is going to be MyFieldOne, and not &MyFieldOne. So here:

fn example(my_struct: MyStruct) {
    let a = *my_struct;
}

We actually get an error:

error[E0507]: cannot move out of dereference of `MyStruct`
 --> src/lib.rs:4:13
  |
4 |     let a = *my_struct;
  |             ^^^^^^^^^^ move occurs because value has type `MyFieldOne`, which does not implement the `Copy` trait

Because MyFieldOne has to be moved, not copied, and you can't move through a dereference similar to how you can't move from underneath a reference.

That would be quite limiting for the * operator if it weren't for...

Place expressions

We can change the last example to compile by doing this instead:

fn example(my_struct: MyStruct) {
    // let a = *my_struct; // cannot move out of dereference of `MyStruct`
    let a = &*my_struct;
    // You can uncomment this to confirm `a` is a `&MyFieldOne`
    // let _: () = a;
}

Just by immediately taking a reference again, we've prevented the move, and now the example acts like my_struct.deref() did. Why did it prevent the move?

Rust has place and value expressions, and place and value contexts. Place expressions represent some actual memory location; in the example, *my_struct represents the memory location of the MyFieldOne field. With let a = *my_struct, this place expression was bound to a, which results in moving from the memory to the bound variable. Or rather it would if you were allowed to move out of that location.

But when we write &*my_struct, the *my_struct place expression is now in a place context, and no move occurs.

This also related to why Deref::deref looks like a reference conversion method: we can't write a method that looks like this:

impl DirectDeref for MyStruct {
    type Target = MyFieldOne;
    fn direct_deref(&self) -> Self::Target {
        self.a
        // error[E0507]: cannot move out of `self.a` which is behind a shared reference
    }
}

So instead we return a reference... and that's also why the notional desugaring puts a * on the results of the method call! That way one layer of indirection actually is removed.

But doesn't desugaring to yourself seem like it might cause problems...?

Built-in * operations

Now let's try to walk through a dereference of an &i32 using the notional desugaring from above. Here's the implementation signature that @jofas linked to in non-generic form:

impl<'a> Deref for &'a i32 {
    type Target = i32;
    fn deref(self: &&'a i32) -> &i32 { /* ... */ }
}

And looking at a dereference:

fn hmm(ri: &i32) {
    //       vv This is an `&i32`
    let i = *ri;
    let i = *<&i32 as Deref>::deref(&ri);
    //       ^^^^^^^^^^^^^^^^^^^^^^^^^^^ This is also an `&i32`...

    // Apply the notional desugarring again?!??
    let i = *
        <&i32 as Deref>::deref( <&i32 as Deref>::deref(&ri) )
    //  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    //  Still an `&i32`...
}

That's clearly going nowhere. We could also figure out things can't work this way if we had written out the body of deref:

impl<'a> Deref for &'a i32 {
    type Target = i32;
    fn deref(self: &&'a i32) -> &i32 {
        *self
    }
}

If that *self called Deref::deref, it would be infinite recursion.

So when applied to references, * is not the notional desugaring, it's a built-in operation.

Why have the blanket implementation then? For generics! If you want to use * on a generic variable T, which may or may not be a reference, you need to require that ability with a trait bound (T: Deref<Target = ...>).[1]

Addendum


  1. It also prevents implementing Deref for &YourOwnType, which would be confusing, and probably has some other coherence-related reasons for existing. ↩︎

  2. Though to be honest they never formally define it, and other official documentation calls anything that implements Deref a "smart pointer". ↩︎

13 Likes

And this is not that much of a special case – all the operators for builtin types are handled by the compiler and the trait impls are only ceremony to make the types formally satisfy the trait bounds.

4 Likes

I understand that Rust implements the function of the dereferencing operator * in two ways: through compiler built-in and through the Deref trait. The compiler's built-in way works for the reference type, and users/programmers can customize the dereference operator’s * function for their own type(not on reference) using the Deref trait.

However, in your hm(ri: &i32) function you showed that let i = <&i32 as Deref>::deref(&ri); works. From my understanding — as Deref trait is not implemented on &i32 type because dereferencing an &i32 is built-in by the compiler— the deref method from the Deref trait should not work on &i32 type. This is supposed to throw an error, but did not, why?

I still do not understand why the blanket implementation exists. I thought that this implementation existed for the dereferencing operator’s function on reference type but this is not the case as dereferencing on reference is compiler built-in. I also reasoned that the presence of blanket implementation is that it prevents the implementation of the Deref trait for &MyOwnType but this — as you hinted—is not the case. As the dereferencing a reference type is compiler built-in dereferencing any reference types(primitive and &myOwnType) is also compiler built-in. Hence the blanket implementation has nothing to do with it.

So it is for generics. Could you please give an elaborate example with code(and explanation) of how this blanket implementation could be useful for generic data variables?

You also hinted that blanket implementation may be here for some coherence-related reasons. What exactly coherence is in a programming language?

Because it is implemented.

The compiler has built-in support for dereferencing references, and it implements Deref for references. If the trait implementation did not exist, you would not be able to use references in generic contexts where you want dereferencing to work.

No need to be elaborate.

use std::ops::Deref;

pub fn maybe_deref<T>(opt: &Option<T>)  -> Option<&T::Target>
where T: Deref {
    match opt {
        Some(ptr) => Some(ptr.deref()),
        None => None,
    }
}

If &T didn't implement Deref, then this function would work for (say) Rc<T>, and Arc<T>, but not &T. That would be an inconvenient restriction.

I'm not good with this, so take this with a pinch of salt.

"Coherence" in this context relates to the compiler's ability to determine what code to run for an operation. Something is "coherent" if there is one and only one possible implementation. Rust has various rules around impl blocks to prevent this principle from being violated. The key one in this case is that an implementation already exists.

Let's pretend impl Deref for &T didn't exist, and Rust didn't care about coherence. It would be possible for someone to come along and implement something like this:

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

Now, depending on whether you directly dereference an &Evil, or do so via the Deref trait, you get different results. This means you can't take code that includes dereferences and make it generic without it potentially breaking. It also makes the code harder to understand.

5 Likes

Like @DanielKeep said, it is.[1] After this sentence you immediately refer to "the blanket implementation" -- well, that's the implementation of the Deref trait for &i32 and all other references!

Note that when it comes types for which * is a built-in operation, * and the trait implementation are different things. Both can exist.

* on a &i32 or any other reference is built-in. But <&i32 as Deref>::deref just acts like any other trait method.[2] This is more important for &mut _ / DerefMut due to things like borrow splitting. Calling deref or deref_mut requires a borrow of the entire shared/exclusive reference. Using * does not.

So that trait bounds and other direct invocations of the trait method work.

fn foo<D: Deref<Target = i32>>(i: D) { }
fn main() {
    // This works...
    foo(&0);
}
// So does this...
fn main() {
    let i: &i32 = Deref::deref(&&0);
}
// So does this...
fn foo(opt: &Option<&i32>) {
    let opt: Option<&i32> = opt.as_deref();
}

(That last one is due to a trait bound on Deref.)

Not to mention simple consistency / principal of least surprise. Every other type to which the * operator applies has a Deref implementation.

Later you ask for an elaborate example, in contrast to these contrived ones. I don't have one off hand, but I'd be surprised if they don't exist in the ecosystem. That said, are elaborate examples more important than more fundamental functionality?

The blanket implementation does prevent you providing a different implementation for Deref and thus for the Deref::deref function (which again, is not the same as the compiler built-in).

impl Deref for &MyOwnType {
    type Target = MyOwnType;
    fn deref(&self) -> &Self::Target {
        self
    }
}
error[E0119]: conflicting implementations of trait `Deref` for type `&MyOwnType`
 --> src/lib.rs:3:1
  |
3 | impl Deref for &MyOwnType {
  | ^^^^^^^^^^^^^^^^^^^^^^^^^
  |
  = note: conflicting implementation in crate `core`:
          - impl<T> Deref for &T
            where T: ?Sized;

It doesn't prevent the existence of an implementation (i.e. itself), it prevents you from providing (an alternative) implementation.

RFC 2451 introduction - What is coherence and why do we care?

As covered elsewhere in that RFC, references are "fundamental" -- you can implement traits for &YourType even if they're foreign traits, like you can for YourType. But not Deref due to the existing blanket implementation.

I suspect that just too many things know and rely on the specific behavior of references to allow the trait definition to be overridden in general, which is (as best as I can recall) why I mentioned coherence in that footnote.


  1. And Discourse is letting me know @jofas linked to this definition before too. ↩︎

  2. Presumably almost always inlined and optimized away, but still. ↩︎

5 Likes

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.