Is it considered a bad practice to implement Deref for newtypes?
Solution 1:
the rules regarding
Deref
andDerefMut
were designed specifically to accommodate smart pointers. Because of this,Deref
should only be implemented for smart pointers to avoid confusion.—
std::ops::Deref
I think it's a bad practice.
since I can use my newtype as if it were the underlying type in some situations
That's the problem — it can be implicitly used as the underlying type whenever a reference is. If you implement DerefMut
, then it also applies when a mutable reference is needed.
You don't have any control over what is and what is not available from the underlying type; everything is. In your example, do you want to allow people to call as_ptr
? What about sort
? I sure hope you do, because they can!
About all you can do is attempt to overwrite methods, but they still have to exist:
impl MyArray {
fn as_ptr(&self) -> *const i32 {
panic!("No, you don't!")
}
}
Even then, they can still be called explicitly (<[i32]>::as_ptr(&*my_array);
).
I consider it bad practice for the same reason I believe that using inheritance for code reuse is bad practice. In your example, you are essentially inheriting from an array. I'd never write something like the following Ruby:
class MyArray < Array
# ...
end
This comes back to the is-a and has-a concepts from object-oriented modeling. Is MyArray
an array? Should it be able to be used anywhere an array can? Does it have preconditions that the object should uphold that a consumer shouldn't be able to break?
but I am tired of writing
my_type.0.call_to_whatever(...)
Like in other languages, I believe the correct solution is composition over inheritance. If you need to forward a call, create a method on the newtype:
impl MyArray {
fn call_to_whatever(&self) { self.0.call_to_whatever() }
}
The main thing that makes this painful in Rust is the lack of delegation. A hypothetical delegation syntax could be something like
impl MyArray {
delegate call_to_whatever -> self.0;
}
While waiting for first-class delegation, we can use crates like delegate or ambassador to help fill in some of the gaps.
So when should you use Deref
/ DerefMut
? I'd advocate that the only time it makes sense is when you are implementing a smart pointer.
Speaking practically, I do use Deref
/ DerefMut
for newtypes that are not exposed publicly on projects where I am the sole or majority contributor. This is because I trust myself and have good knowledge of what I mean. If delegation syntax existed, I wouldn't.
Solution 2:
Contrary to the accepted answer, I found out that some popular crates implement Deref
for types which are newtypes and aren't smart pointers:
-
actix_web::web::Json<T>
is a tuple struct of(T,)
and it implementsDeref<Target=T>
. -
bstr::BString
has one field typedVec<u8>
and it implementsDeref<Target=Vec<u8>>
.
So, maybe it's fine as long as it's not abused, e.g. to simulate multi-level inheritance hierarchies. I also noticed that the two examples above have either zero public methods or only one into_inner
method which returns the inner value. It seems then a good idea to keep the number of methods of a wrapper type minimal.