Automatic dereferencing

How would you characterize the set of built-in types for which dereferencing happens automatically?
I see the list of Deref trait implementors at std::ops::Deref - Rust.
It includes types like String and Vec.
It also says this:

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

I'm assuming that covers references to types like bool, char, str, tuples, and arrays.
Are there any built-in types that do not implement the Deref trait?

The list of Deref implementers is exactly what you're looking for.

The blanket impls in your code block are for references to any other type. All references implement the Deref trait. bool is not a reference, for example.

4 Likes

There are really two questions here:

What types can be dereferenced?

A type can be “dereferenced” if it acts like a pointer to some other type. For example, &T and Box<T> and *mut T and Rc<T> are all different types of “pointer to a T.” (For example, dereferencing an &bool produces a bool).

In addition to these generic pointer types, there are a few specialized “pointer-like” types for managing slices: String is a specialized pointer to a str slice, and Vec<T> is a specialized pointer to a [T] slice.

All dereferenceable types implement the Deref and/or DerefMut traits, and can be deferenced with the * operator.

/// Convert from `&bool` to `bool` by dereferencing.
fn foo(b: &bool) -> bool { *b }

Non-pointer types like bool or char or (u8, u8) cannot be dereferenced: They do not implement the Deref trait and do not act like pointers to some other type.

When does dereferencing happen automatically?

Whenever you use the . (“dot”) operator to access fields or methods of a value, the expression on the left-hand side of the dot is auto-referenced and/or auto-dereferenced automatically.

For example, if x has type &i32, then writing x.count_ones() is shorthand for (*x).count_ones(), because the count_ones method requires an i32.

Rust will also insert automatic dereferencing as part of deref coercion. This is a special coercion that can only convert from one reference type to another. For example, this is what lets you convert from String to &str by writing &x instead of &*x.

These auto-ref/deref rules exist just to make Rust code less noisy to read and write. Without them, Rust would often require you to write things like (&mut *foo).send(&**bar) instead of foo.send(&bar).

7 Likes

I can think of one other instance: Standard operations involving primitives using a reference to another (thanks impl<'_> Add<&'_ f64> for f64)

But there are others that I wonder if they would be possible:

  • Comparisons, such as 6 < &7, don't work (in spite of the fact that add/sub/mul/etc. do).
  • Function arguments and returns are (rightly) picky about ref/deref being applied correctly.

Though I did not fully realize it was self.x becoming (*self).x that made returns from impls for a struct such as fn x(&self) work. Good to know!

To the follow-up question about when dereferencing happens automatically: Was there not some relatively heavy, if not useful, lifting by the Rust team to make the pattern matching behavior more intuitive?

If memory serves, it involved a combination of dereferencing, reborrows and ultimately, introducing the ref keyword to disambiguate the intent and the preferred dereferencing and/or reborrow behavior? All in all, I vaguely recall 2-3 changes to better align user-intent with what needs to happen to make the match pattern work.

Can anyone confirm and perhaps, if on topic, where the dereferencing occurs in a pattern match?

Another time: does automatic dereferencing also occur when the trait is implemented on a type using "newtype" pattern?

There isn't general-purpose automatic dereferencing in pattern matching. You can use pattern matching to explicitly destructure a reference (but not other pointer types). And match ergonomics lets you implicitly destructure references and bind their contents by-reference, without explicit ref or & sigils (but again, can't dereference arbitrary types).

Dereferencing non-reference types in patterns will likely be supported sometime in the future.

Only if you implement Deref and/or DerefMut for your newtype. Then you can dereference it in all the ways listed above. Otherwise, it is not dereferenceable.

Agreed. The topic seemed to veer to a follow-up taking inventory of when dereferencing is occurring without our explicitly doing so (said differently, occurring on our behalf to facilitate the intent). That's the scope in which my thinking was inspired.

Nice description (your coherent use of the terminology is really helpful). When you say "without explicit..." does that fall into a time when dereferencing is happening on our behalf?

Yes, once the trait is implemented; that's explicit intent. However, once implemented, I was impressed with how Rust seems to perform dereferencing "on demand" to access all/most(?) of the functionality (methods and traits) associated with the underlying type. So, it is what I believe was described for Vec and String.

This said, the behavior seems a bit in a category all its own to the degree dereferencing a "newtype" is more Wrap(Inner) -> Inner used to access functionality, vs how I might describe Vec<T> -> &[T] that strips away functionality leaving behind more of a "purely data" view of the entity.