You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Should deriving FromBytes imply deriving FromZeros and TryFromBytes? Either possible decision on this question has distinct advantages, disadvantages, and precedence. After reviewing the below design document and soliciting feedback from key customers, we've tentatively concluded yes.
That is, should deriving a zerocopy trait implicitly derive its super traits?
Evaluation
Pedagogy
How does this decision impact our documentation? The zerocopy documentation makes heavy use of examples to demonstrate its constructs.
Option 1
Under Option 1, our examples must include derives for the full set of super traits, even when those traits are not germane to the example.
This has the unfortunate effect of making simple examples appear more complicated than they otherwise would be. However, the repeated exposure to the trait hierarchy this provides may reinforce the hierarchy in users' minds.
Option 2
Under Option 2, our examples need only include derives for traits germane to the example. This simplifies examples, at the expense of reducing the visibility of zerocopy's trait hierarchy.
Pathological User Experience
How does this decision impact our users? We'll consider the stories of Asher and Willow. Asher is using a version of zerocopy that does not imply the derives, but their user experience would likely be smoother if it did. Willow is using a version of zerocopy that does imply the derives, but their user experience would likely be smoother if it did not.
Option 1
Asher needs to call an API that requires its parameter implement FromBytes. Dutifully, they derive the trait:
#[derive(FromBytes)]structFoo{
...
}
...and compile. They receive the error message:
error[E0277]: the trait bound `Foo: FromZeroes` is not satisfied
--> src/lib.rs:3:10
|
3 | #[derive(FromBytes)]
| ^^^^^^^^^ the trait `FromZeroes` is not implemented for `Foo`
|
= help: the following other types implement trait `FromZeroes`:
bool
char
isize
i8
i16
i32
i64
i128
and 67 others
note: required by a bound in `zerocopy::FromBytes`
--> /playground/.cargo/registry/src/index.crates.io-6f17d22bba15001f/zerocopy-0.7.32/src/lib.rs:1803:29
|
1803 | pub unsafe trait FromBytes: FromZeroes {
| ^^^^^^^^^^ required by this bound in `FromBytes`
= note: this error originates in the derive macro `FromBytes` (in Nightly builds, run with -Z macro-backtrace for more info)
For more information about this error, try `rustc --explain E0277`.
...and infer that they need to also implement FromZeros. So they add that to the derive(...):
#[derive(FromZeros,FromBytes)]structFoo{
...
}
...and compile. They receive the error message:
error[E0277]: the trait bound `Foo: TryFromBytes` is not satisfied
--> src/lib.rs:3:10
|
3 | #[derive(FromZeros, FromBytes)]
| ^^^^^^^^^ the trait `TryFromBytes` is not implemented for `Foo`
|
= help: the following other types implement trait `TryFromBytes`:
bool
char
isize
i8
i16
i32
i64
i128
and 67 others
note: required by a bound in `zerocopy::FromZeros`
--> /playground/.cargo/registry/src/index.crates.io-6f17d22bba15001f/zerocopy-0.7.32/src/lib.rs:1803:29
|
1803 | pub unsafe trait FromZeros: TryFromBytes {
| ^^^^^^^^^^^^ required by this bound in `FromZeros`
= note: this error originates in the derive macro `FromBytes` (in Nightly builds, run with -Z macro-backtrace for more info)
For more information about this error, try `rustc --explain E0277`.
...and infer that they need to also implement TryFromBytes. So they add that to the derive(...):
...and compile. This compilation succeeds without errors.
Option 2
Willow has already derived FromBytes on their type:
#[derive(FromBytes)]structFoo{
...
}
They want to pass their Foo to a function that requires a FromZeros bound. They add the function call, but before trying to compile (which would succeed), they infer that they also need to derive FromZeros:
#[derive(FromZeros,FromBytes)]structFoo{
...
}
When they compile, they encounter an error message:
error[E0119]: conflicting implementations of trait `zerocopy::FromZeros` for type `Foo`
--> src/lib.rs:3:22
|
3 | #[derive(FromZeros, FromBytes)]
| --------- ^^^^^^^^^ conflicting implementation for `Foo`
| |
| first implementation here
|
= note: this error originates in the derive macro `FromZeros` (in Nightly builds, run with -Z macro-backtrace for more info)
For more information about this error, try `rustc --explain E0119`.
They delete their added derive of FromZeros and then compile without errors.
Precedent
What precedent does either option have that might impact the expectations of our users?
Option 1
In the Rust standard libary, there is no direct analogy for the situation we are considering. At first glance, the Copy: Clone hierarchy in the Rust seems analogous. If Rust could have #[derive(Copy)] imply #[derive(Copy, Clone)], then why doesn't it?
The premise, unfortunately, does not hold. If deriving Copy implied deriving Clone, then Clone could not be manually implementable:
Compiling this would (in our hypothetical alternate derive(Copy)) produce an error:
error[E0119]: conflicting implementations of trait `Clone` for type `Foo`
--> src/lib.rs:1:10
|
1 | #[derive(Copy)]
| ^^^^ conflicting implementation for `Foo`
...
6 | impl Clone for Foo {
| ------------------ first implementation here
|
= note: this error originates in the derive macro `Clone` (in Nightly builds, run with -Z macro-backtrace for more info)
For more information about this error, try `rustc --explain E0119`.
A difference between this and our scenario is that our traits are not manually implementable; the backwards compatibility issues do not exist.
Nonetheless, it is reasonable to consider that end-users potentially have an expectation that traits must always be explicitly derived (which could lead to confusion).
Option 2
Unstable Rust
The StructuralEq marker indicates that constants of a type are suitable for use in patterns. It cannot be manually implemented; rather it is implied by #[derive(Eq)].
There are a few differences between this example and ours:
Eq is not a formal supertrait of StructuralEq
StructuralEq trait is not intended to ever be stabilized.
Third-Party Crates
Diesel
The Diesel ORM makes frequent use of this pattern. Nearly every derive it provides also derives other related traits. In some cases, these are super-traits. For instance, derive(Identifiable)implies a derivation of HasTable.
Bytemuck
Bytemuck's derives imply their supertraits. For example, derive(AnyBitPattern) implies derive(Zeroable):
use bytemuck::{AnyBitPattern,Zeroable};#[derive(AnyBitPattern,Copy,Clone)]#[repr(C)]structFoo;fnis_also_zeroable() -> implZeroable{Foo}
Implied Traits at Use-Sites
Rust provides a similar ergonomic device at trait-use sites: Bringing a trait into scope implicitly brings the methods of its supertraits into scope. For example:
mod upstream {pubtraitSuper{fnsup(&self){}}pubtraitSub:Super{}impl<T>SuperforT{}impl<T>SubforT{}}fntest<T>(t:T)whereT: upstream::Sub{
t.sup();}
This compiles successfully.
Conclusion
Option 2 offers a tighter development cycle in its pathological case than Option 1, and has precedent in the crates ecosystem.
The text was updated successfully, but these errors were encountered:
Should deriving
FromBytes
imply derivingFromZeros
andTryFromBytes
? Either possible decision on this question has distinct advantages, disadvantages, and precedence. After reviewing the below design document and soliciting feedback from key customers, we've tentatively concluded yes.Background
Zerocopy defines the following trait hierarchy:
Implementing these traits manually is an explicitly unsupported workflow; they must be derived.
Question
To derive
FromBytes
, should a user write:That is, should deriving a zerocopy trait implicitly derive its super traits?
Evaluation
Pedagogy
How does this decision impact our documentation? The zerocopy documentation makes heavy use of examples to demonstrate its constructs.
Option 1
Under Option 1, our examples must include derives for the full set of super traits, even when those traits are not germane to the example.
This has the unfortunate effect of making simple examples appear more complicated than they otherwise would be. However, the repeated exposure to the trait hierarchy this provides may reinforce the hierarchy in users' minds.
Option 2
Under Option 2, our examples need only include derives for traits germane to the example. This simplifies examples, at the expense of reducing the visibility of zerocopy's trait hierarchy.
Pathological User Experience
How does this decision impact our users? We'll consider the stories of Asher and Willow. Asher is using a version of zerocopy that does not imply the derives, but their user experience would likely be smoother if it did. Willow is using a version of zerocopy that does imply the derives, but their user experience would likely be smoother if it did not.
Option 1
Asher needs to call an API that requires its parameter implement
FromBytes
. Dutifully, theyderive
the trait:...and compile. They receive the error message:
...and infer that they need to also implement
FromZeros
. So they add that to thederive(...)
:...and compile. They receive the error message:
...and infer that they need to also implement
TryFromBytes
. So they add that to thederive(...)
:...and compile. This compilation succeeds without errors.
Option 2
Willow has already derived
FromBytes
on their type:They want to pass their
Foo
to a function that requires aFromZeros
bound. They add the function call, but before trying to compile (which would succeed), they infer that they also need to deriveFromZeros
:When they compile, they encounter an error message:
They delete their added derive of
FromZeros
and then compile without errors.Precedent
What precedent does either option have that might impact the expectations of our users?
Option 1
In the Rust standard libary, there is no direct analogy for the situation we are considering. At first glance, the
Copy: Clone
hierarchy in the Rust seems analogous. If Rust could have#[derive(Copy)]
imply#[derive(Copy, Clone)]
, then why doesn't it?The premise, unfortunately, does not hold. If deriving
Copy
implied derivingClone
, thenClone
could not be manually implementable:Compiling this would (in our hypothetical alternate
derive(Copy)
) produce an error:An RFC proposing exactly this sort of mechanism for standard library traits was postponed because of exactly these sorts of backwards compatibility challenges.
A difference between this and our scenario is that our traits are not manually implementable; the backwards compatibility issues do not exist.
Nonetheless, it is reasonable to consider that end-users potentially have an expectation that traits must always be explicitly derived (which could lead to confusion).
Option 2
Unstable Rust
The
StructuralEq
marker indicates that constants of a type are suitable for use in patterns. It cannot be manually implemented; rather it is implied by#[derive(Eq)]
.There are a few differences between this example and ours:
Eq
is not a formal supertrait ofStructuralEq
StructuralEq
trait is not intended to ever be stabilized.Third-Party Crates
Diesel
The Diesel ORM makes frequent use of this pattern. Nearly every
derive
it provides also derives other related traits. In some cases, these are super-traits. For instance,derive(Identifiable)
implies a derivation ofHasTable
.Bytemuck
Bytemuck's derives imply their supertraits. For example,
derive(AnyBitPattern)
impliesderive(Zeroable)
:Implied Traits at Use-Sites
Rust provides a similar ergonomic device at trait-use sites: Bringing a trait into scope implicitly brings the methods of its supertraits into scope. For example:
This compiles successfully.
Conclusion
Option 2 offers a tighter development cycle in its pathological case than Option 1, and has precedent in the crates ecosystem.
The text was updated successfully, but these errors were encountered: