Understanding the perils of `Deref`

As I put in the other thread, the core thing is that -- unlike every other trait -- implementing Deref changes name resolution for method calls. This is a huge deal.

So the core property that smart pointers have is that when you're using a Box<T> you almost always are actually using the T inside it, not the box-ness of it. As such, Box is careful not to add methods that could conflict with yours. When it wants to offer more functionality, it does so with associated functions like Box in std::boxed - Rust that aren't picked up in method name resolution.

And places where this doesn't happen -- trait methods being the important one -- it can be a trap. For example, because of ambiguity between foo.clone() and foo.deref().clone() the docs for Arc used to say

The Arc::clone(&from) syntax is the most idiomatic because it conveys more explicitly the meaning of the code. In the example above, this syntax makes it easier to see that this code is creating a new reference rather than copying the whole content of foo.
~ std::sync::Arc - Rust

This is also why things like Debug on Arc just delegate to the implementation for the inner type -- it's trying to behave like it's not there.

And I'll throw in NonZeroU32 as another example of a newtype that's applying a restriction to the wrapped value but still doesn't Deref to it.

4 Likes