Allow deriving an implementation of the Deref trait using #[derive(Deref)] on structs and enums.
;
// Generates:
// Generates:
// Generates:
If there is more than one field, a #[deref] attribute will be required on the field we want to "deref":
;
// Generates:
// Generates:
// Generates:
Motivation
The primary motivation is to remove one of the gaps in the Rust language which prohibit combining language features in intuitive ways. Both the #[derive(Trait)] macro and the Deref trait are pervasively used across the Rust ecosystem, but it is currently not possible to combine them, even in situations where the resulting behavior seems completely obvious.
Concretely, when you have a struct with a single field and want to implement the Deref trait to allow getting access to a field or field's method of the type without needing to do explicitly access the field first, #[derive(Deref)] seems like the most intuitive way of achieving that. Deref is a standard library trait, #[derive(Trait)] works with many other such traits (such as Hash, Eq, Clone, etc.), and there is essentially only one possible implementation that makes sense. However, when users currently try to do that, they are met with a compiler error.
Enabling this would make one more intuitive use-case in the language "just work", and would reduce boilerplate that Rust users either write over and over again or for which they have to use macros or external crates.
Newtype pattern
As a concrete use-case, #[derive(Deref)] is particularly useful in combination with the very popular newtype pattern. In this pattern, an inner type is wrapped in a new type (hence the name), typically a tuple struct, to semantically make it a separate concept in the type system and thus make it harder to mix unrelated types by accident. For example, we can wrap a number to represent things like Priority(i32), PullRequestNumber(u32) or TcpPort(u16).
When using the newtype pattern, it is common to implement standard library traits for it by delegating to the inner type. This is easily achievable with #[derive]:
;
However, not all standard library traits can be derived in this way, including the Deref trait. Currently, users have to write the boilerplate Deref implementation by hand. If there are many newtypes in a crate, this might lead users to implement a macro, which unnecessarily obfuscates the code, or use an external crate to derive the implementation, which increases code size and compile times.
Is Deref really so useful for newtypes? Newtypes are used to make it more explicit what a type is about, like PullRequestNumber(u32). Or to allow to implement traits on external types, like implementing Drop on an integer representing a system ID. However, a lot of time, the wrapping "gets in the way" so you have to implement Deref to have access to the field without doing it explicitly.
To summarize, if Deref was derive-able, it could reduce the need for using macros or external crates and increase the number of cases where #[derive] takes care of all required impls for a given newtype.
Other cases
For other cases, like struct with multiple fields or enums, the Deref trait becomes useful when one field is the "main" information and the rest is "additional" information. For example:
In this case, the id field is the actual information we want to manipulate whereas the needs_to_be_released is only useful in specific contexts (like a Drop trait implementation).
For enums, it's similar:
In case it's Owned, then the Drop implementation will release the resource, otherwise it won't. However, the u32 always represents the same data, whatever the variant.
Why does it make sense to derive Deref?
There are various "standard" traits defined in the Rust standard library that are pervasively used across the ecosystem. Currently, some of these traits can already be automatically derived, for example Hash or Debug. These traits can be derived automatically because they are composable; an implementation of the trait for a struct can be composed of the trait implementations of its fields.
One reason why we might not want to enable automatic derive for a specific trait is when the implementation would not be obvious. For example, if we allowed deriving Display, it is unclear how should the individual field implementations be composed. Should they be separated with a newline? Or a comma? That depends on the given type.
However, when deriving a Deref implementation for a struct with a single field, the implementation seems straightforward and obvious (simply wrap the inner type in the struct). It should thus be possible to automatically derive it. For other cases, the #[deref] attribute will remove any ambiguity.
How common is implementing and deriving Deref?
This GitHub Code Search query shows tens of thousands of occurrences of the Deref trait being derived, typically using the derive_more crate.
Guide-level explanation
You can use #[derive(Deref)] to automatically generate an implementation of the Deref trait for the given type, which will allow to implicitly have access to the derefed field:
;
// Will generate:
If the type is generic over the type of the inner field, the Deref implementation will be also generic:
;
// Will generate:
For enums where all variants contain one field of the same type, it will give:
// Will generate:
If there any ambiguities because there are more than one field, then the user will need to add the #[deref] attribute on the field they want to be derefed:
;
// Will generate:
// Will generate:
Reference-level explanation
Placing #[derive(Deref)] on a struct is always permissible as long as there is at least one field, otherwise it will produce a compilation error. If there are multiple fields and the #[deref] attribute is not used, it will produce a compilation error. If the #[deref] attribute is present more than once, it will produce a compilation error.
Type we deref is $t;
-
If
$sis a tuple struct with one field: -
If
$sis a tuple struct with x fields when$nis the field with the#[deref]attribute: -
If
$sis a struct with a named field$f:This is the same implementation if there are more than one field thanks to the
#[deref]attribute.
Placing #[derive(Deref)] on enums, requires for all its variants to share one common type, otherwise it'll produce a compiler error. If a variant has no field, it will produce a compilation error. If a variant has more than one field and the #[deref] attribute isn't used, it will produce a compilation error. If the #[deref] attribute is present more than once on a variant, it will produce a compilation error.
An enum $e, with a first variant named $v1 which is a tuple variant ; and a second variant named $v2 which is struct-like variant with a field named $f:
- Both variants only have one field:
- Both variants have two fields and use the
#[deref]attribute on the first field of each variant:
Using #[derive(Deref)] on unions produces a compilation error.
Drawbacks
Compilation errors
While this does enable more Rust code to "just work", it also means that we need to produce high-quality error messages in the compiler, otherwise it might be more confusing than helpful.
Smart pointers
#[derive(Deref)] as described in this RFC will do the wrong thing for smart pointer types:
;
MyPtr1 will deref to Box<T> rather than T, which is likely harmless but may reveal an implementation detail, increase the number of *s needed, or in the case of derive(DerefMut), allow breaking an invariant.
The #[deref(forward)] attribute should mitigate this case, but will require users to add it themselves.
Raw pointers
If you use #[derive(Deref)] to dereference to a raw pointer, you will get a raw pointer, which can't be safely dereferenced:
;
The intent would presumably be to dereference to T, but it's likely something to discuss if we extend #[derive(Deref)] with #[deref(forward)].
Rationale and alternatives
Based on the popularity of the derive_more crate (discussed in Prior art), which had more than 125 million downloads when this RFC was proposed, it seems that there is a lot of appetite for extending the set of use-cases where deriving standard traits is allowed. This feature was discussed in the past here.
The proposed change enables the usage of an existing feature (#[derive]) in more situations. It makes code easier to read by using an intuitive built-in feature instead of forcing users to write boilerplate code or use macros.
As always, an alternative is to just not do this, in that case users would continue implementing Deref using boilerplate code, macros or external crates.
Because the scope of the proposed change is quite minimal, it should be forward-compatible with designs that would make it work in more situations in the future (some ideas are discussed in Future possibilities). There is one potential (although unlikely) incompatibility discussed below.
Prior art
Ecosystem crates
There are several crates that offer deriving the Deref trait. The most popular one is derive_more, which allows deriving several standard traits that are normally not derivable, including Deref, Display or Add.
#[derive(derive_more::Deref) works in the same way as proposed in this RFC for structs with a single field. However, it can also be used for other kinds of structs and even enums and supports more complex use-cases. For example:
- For structs with multiple fields, you need to add the
#[deref]attribute on the field to be the target of theDereftrait:; - You can use the
#[deref(forward)]attribute to use theDereftarget of the current derefed item.] ); // generates:
The design proposed by this RFC should be forward compatible with all features of derive_more[^enums], if we decided to adopt any of them in the future.
Unresolved questions
Should we also add a new DerefMut derive, with the same conditions as described here for the Deref trait? It comes with new questions like, "what happens when you implement Deref manually but use derive(DerefMut) ; should it error when Deref::Target isn't the expected type?" Example:
// should this error or rely on silent coercion of String to str?
;
Future possibilities
#[deref(forward)] attribute
In the future, we could extend the set of supported use-cases. For example, we could allow to have a "traversal" Deref when the target already implements Deref:
] );
which would generate this impl:
In case we want to deref more than once, we could allow a positive integer value:
// generated Deref::Target = T
In this case, Deref would give us Cow<'a, Arc<T>>, but if we want T, we use to "go through" Cow and Arc, therefore dereferencing two more times, hence #[deref(forward = 2)].
This is similar to how RFC#3107 extended the deriving of the Default trait using the #[default] attribute.