-
Notifications
You must be signed in to change notification settings - Fork 105
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support TryFromBytes
- conditional conversion analogous to FromBytes
#5
Closed
23 of 26 tasks
Tracked by
#671
Labels
compatibility-nonbreaking
Changes that are (likely to be) non-breaking
Comments
This was referenced Aug 10, 2023
joshlf
added
the
compatibility-nonbreaking
Changes that are (likely to be) non-breaking
label
Aug 12, 2023
joshlf
changed the title
Support conditional conversion analogous to FromBytes
Support Aug 14, 2023
TryFromBytes
- conditional conversion analogous to FromBytes
joshlf
added a commit
that referenced
this issue
Aug 16, 2023
TODO: How to support `project!` when `Projectable` is implemented multiple times for `ByteArray`, `Align`, and `AlignedByteArray`? If we only emit an impl for `AlignedByteArray`, everything is fine, but once we emit the other two, type inference fails. Makes progress on #5
joshlf
added a commit
that referenced
this issue
Aug 17, 2023
TODO: How to support `project!` when `Projectable` is implemented multiple times for `ByteArray`, `Align`, and `AlignedByteArray`? If we only emit an impl for `AlignedByteArray`, everything is fine, but once we emit the other two, type inference fails. Makes progress on #5
joshlf
added a commit
that referenced
this issue
Aug 17, 2023
TODO: How to support `project!` when `Projectable` is implemented multiple times for `ByteArray`, `Align`, and `AlignedByteArray`? If we only emit an impl for `AlignedByteArray`, everything is fine, but once we emit the other two, type inference fails. Makes progress on #5
joshlf
added a commit
that referenced
this issue
Aug 17, 2023
TODO: How to support `project!` when `Projectable` is implemented multiple times for `ByteArray`, `Align`, and `AlignedByteArray`? If we only emit an impl for `AlignedByteArray`, everything is fine, but once we emit the other two, type inference fails. Makes progress on #5
joshlf
added a commit
that referenced
this issue
Aug 17, 2023
TODO: How to support `project!` when `Projectable` is implemented multiple times for `ByteArray`, `Align`, and `AlignedByteArray`? If we only emit an impl for `AlignedByteArray`, everything is fine, but once we emit the other two, type inference fails. Makes progress on #5
joshlf
added a commit
that referenced
this issue
Aug 17, 2023
TODO: How to support `project!` when `Projectable` is implemented multiple times for `ByteArray`, `Align`, and `AlignedByteArray`? If we only emit an impl for `AlignedByteArray`, everything is fine, but once we emit the other two, type inference fails. Makes progress on #5
Closed
joshlf
added a commit
that referenced
this issue
Aug 22, 2023
TODO: - Should we require `FromBytes: FromZeroes + TryFromBytes` or `FromZeroes: TryFromBytes`? We obviously only need one of these, but which one? Makes progress on #5
joshlf
added a commit
that referenced
this issue
Aug 22, 2023
TODO: - Should we require `FromBytes: FromZeroes + TryFromBytes` or `FromZeroes: TryFromBytes`? We obviously only need one of these, but which one? Makes progress on #5
joshlf
added a commit
that referenced
this issue
Aug 22, 2023
TODO: - Should we require `FromBytes: FromZeroes + TryFromBytes` or `FromZeroes: TryFromBytes`? We obviously only need one of these, but which one? Makes progress on #5
joshlf
added a commit
that referenced
this issue
Aug 22, 2023
TODO: - Should we require `FromBytes: FromZeroes + TryFromBytes` or `FromZeroes: TryFromBytes`? We obviously only need one of these, but which one? Makes progress on #5
joshlf
added a commit
that referenced
this issue
Aug 22, 2023
TODO: - Should we require `FromBytes: FromZeroes + TryFromBytes` or `FromZeroes: TryFromBytes`? We obviously only need one of these, but which one? Makes progress on #5
joshlf
added a commit
that referenced
this issue
Aug 22, 2023
TODO: - Finish writing safety comments - Finish writing documentation - Add tests Makes progress on #5
joshlf
added a commit
that referenced
this issue
Aug 22, 2023
TODO: - Finish writing safety comments - Finish writing documentation - Add tests Makes progress on #5
joshlf
added a commit
that referenced
this issue
Aug 22, 2023
TODO: - Finish writing safety comments - Finish writing documentation - Add tests Makes progress on #5
joshlf
added a commit
that referenced
this issue
Aug 23, 2023
TODO: - Finish writing safety comments - Finish writing documentation - Add tests Makes progress on #5
joshlf
added a commit
that referenced
this issue
Aug 23, 2023
TODO: - Finish writing safety comments - Finish writing documentation - Add tests Makes progress on #5
joshlf
added a commit
that referenced
this issue
Aug 23, 2023
TODO: - Finish writing safety comments - Finish writing documentation - Add tests Makes progress on #5
joshlf
added a commit
that referenced
this issue
Aug 23, 2023
TODO: - Finish writing safety comments - Finish writing documentation - Add tests Makes progress on #5
joshlf
added a commit
that referenced
this issue
Aug 23, 2023
TODO: - Finish writing safety comments - Finish writing documentation - Add tests Makes progress on #5
joshlf
added
the
blocking-next-release
This issue should be resolved before we release on crates.io
label
May 30, 2024
@kupiakos I discussed this with @jswrenn and @djkoloski and decided to handle this as part of #1289 (comment), when we're doing more work to consider the broader design space. We likely won't support this for 0.8, but may support it for 0.9 depending on how our design for #1289 goes. |
joshlf
removed
the
blocking-next-release
This issue should be resolved before we release on crates.io
label
Jul 1, 2024
joshlf
added a commit
that referenced
this issue
Oct 1, 2024
When deriving `FromBytes` on a type with no generic parameters, the implied `TryFromBytes` derive's `is_bit_valid` impl is generated as always returning `true`. This is faster to codegen, is faster to compile, and is friendlier on the optimizer. Makes progress on #5
joshlf
added a commit
that referenced
this issue
Oct 2, 2024
When deriving `FromBytes` on a type with no generic parameters, the implied `TryFromBytes` derive's `is_bit_valid` impl is generated as always returning `true`. This is faster to codegen, is faster to compile, and is friendlier on the optimizer. Makes progress on #5
joshlf
added a commit
that referenced
this issue
Oct 2, 2024
When deriving `FromBytes` on a type with no generic parameters, the implied `TryFromBytes` derive's `is_bit_valid` impl is generated as always returning `true`. This is faster to codegen, is faster to compile, and is friendlier on the optimizer. Makes progress on #5
joshlf
added a commit
that referenced
this issue
Oct 2, 2024
When deriving `FromBytes` on a type with no generic parameters, the implied `TryFromBytes` derive's `is_bit_valid` impl is generated as always returning `true`. This is faster to codegen, is faster to compile, and is friendlier on the optimizer. Makes progress on #5
github-merge-queue bot
pushed a commit
that referenced
this issue
Oct 2, 2024
When deriving `FromBytes` on a type with no generic parameters, the implied `TryFromBytes` derive's `is_bit_valid` impl is generated as always returning `true`. This is faster to codegen, is faster to compile, and is friendlier on the optimizer. Makes progress on #5
The remaining TODOs have been spun out into their own issues: |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Co-authored with @jswrenn.
Overview
Add a
TryFromBytes
trait, which supports byte-to-type conversions for non-FromBytes
types by performing runtime validation. Add a custom derive which generates this validation code automatically.Many thanks to @kupiakos and @djkoloski for providing invaluable feedback and input on this design.
Progress
TryFromBytes
trait definitionTryFromBytes
trait #641TryFromBytes
for existingFromBytes
typestry_from_ref
method; impl forbool
derive(TryFromBytes)
for structs #665TryFromBytes
for[T]
#666TryFromBytes
for[T; N]
#799repr(packed)
structsderive(TryFromBytes)
onpacked
structs #794u8
,i16
, etc)repr(C)
by treating the discriminant type as[u8; size_of::<Self>()]
Ptr
type should reason aboutUnsafeCell
overlap #873TryFromBytes
forfn()
andextern "C" fn()
typesTryFromBytes
forUnsafeCell<T>
TryFromBytes
forUnsafeCell
#905TryFromBytes
a super-trait ofFromZeros
TryFromBytes
a super-trait ofFromZeros
#962#[doc(hidden)]
from all items which are intended to be publicTryFromBytes
docs to explain that you can't always round tripT -> [u8] -> T
(notably for pointer types), which could be confusing given that, forTryFromBytes
, the failure would show up at runtimeTryFromBytes
methods to be consistent withFromBytes
#1119TryFromBtyes
doc comment currently incorrectly says: "zerocopy does not permit implementingTryFromBytes
for any union type"TryFromBytes
docs w.r.t unions #1259try_from_mut
andtry_read_from
methodsUnsafeCell
T: Sized
(described in #251) if we use the design in #905Self: NoCell
bound fromtry_read_from
Immutable
bound#[derive(TryFromBytes)]
on unions without requiringSelf: Immutable
#1832is_bit_valid
should promise not to mutate its argument's referentTryFromBytes::is_bit_valid
should promise not to mutate its referent #1831TryFromBytes
#1330Motivation
Many use cases involve types whose layout is well-defined, but which cannot implement
FromBytes
because there exist bit patterns which are invalid (either they are unsound in terms of language semantics or they are unsafe in the sense of violating a library invariant).Consider, for example, parsing an RPC message format. It would be desirable for performance reasons to be able to read a message into local memory, validate its structure, and if validation succeeds, treat that memory as containing a parsed message rather than needing to copy the message in order to transform it into a native Rust representation.
Here's a simple, hypothetical example of an RPC to request log messages from a process:
None of these types can be
FromBytes
. ForLogLevel
, only theu8
values 0 through 4 correspond to enum variants, and constructing aLogLevel
from any otheru8
would be unsound. ForLogTime
, any sequence of the appropriate number of bytes would constitute a valid instance ofLogTime
from Rust's perspective - it would not cause unsoundness - but some such sequences would violate the invariant that thensecs
field is in the range[0, 10^9)
.While these types can't be
FromBytes
, we'd still like to be able to conditionally reinterpret a sequence of bytes as aRequestLogsArgs
- it's just that we need to perform runtime validation first. Ideally, we'd be able to write code like:The
TryFromBytes
trait - the subject of this design - provides the ability to fallibly convert a byte sequence to a type, performing validation at runtime. At a minimum, the validation code simply ensures soundness - for example, in the case ofLogLevel
, validating that byte values are in the range [0, 4]. The custom derive also supports user-defined validation like theLogTime::is_valid
method (note thevalidator
annotation onLogTime
), which can be used to enforce safety invariants that go above and beyond soundness.Given these derives of
TryFromBytes
, an implementation of this RPC could be as simple as:The design proposed in this issue implements this API.
Design
TODO
This design builds on the following features:
KnownLayout
trait and custom DSTs #29#[repr(transparent)]
wrapper type #196Here's an example usage:
Unions
See for a discussion of how to support unions in
TryFromBytes
: #696Relationship with other traits
There are obvious relationships between
TryFromBytes
and the existingFromZeroes
andFromBytes
traits:FromZeroes
, then it should probably beTryFromBytes
(at a minimum, we must know something about the type's layout and bit validity to determine that it is genuinelyFromZeroes
)FromZeroes
to beFromZeroes: TryFromBytes
FromBytes
, then it is triviallyTryFromBytes
(whereis_bit_valid
unconditionally returnstrue
)impl<T: FromBytes> TryFromBytes for T
Unfortunately, neither of these are possible today.
FromZeroes: TryFromBytes
The reason this bound doesn't work has to do with unsized types. As described in the previous section, working with unsized types is difficult. Luckily for
FromZeroes
, it doesn't have to do anything with the types it's implemented for - it's just a marker trait. It can happily represent a claim about the bit validity of a type even if that type isn't constructible in practice (over time,FromZeroes
will become more useful as more unsized types become constructible). By contrast,TryFromBytes
is only useful if we can emit validation code (namely,is_bit_valid
). For that reason, we require thatTryFromBytes: AsMaybeUninit
since that bound is required in order to support theMaybeValid
type required byis_bit_valid
.This means that we have two options if we want
FromZeroes: TryFromBytes
:TryFromBytes: AsMaybeUninit
. As a result, some types which areFromZeroes
today can no longer beFromZeroes
, and some blanket impls ofFromZeroes
would require more complex bounds (e.g., today we writeimpl<T: FromZeroes> FromZeroes for Wrapping<T>
; under this system, we'd need to writeimpl<T: FromZeroes> FromZeroes for Wrapping<T> where <T as AsMaybeUninit>::MaybeUninit: Sized
, or alternatively we'd need to write one impl forT
and a different one for[T]
).AsMaybeUninit
bound out of definition ofTryFromBytes
and intois_bit_valid
(and callers). As a result, we can keep existing impls ofFromZeroes
, but nowT: TryFromBytes
is essentially useless - to do anything useful, you need to specifyT: TryFromBytes + AsMaybeUninit
.Neither option seems preferable to just omitting
FromZeroes: TryFromBytes
. Callers who require both can simply writeT: FromZeroes + TryFromBytes
.(Note that the same points apply if we consider
FromBytes: TryFromBytes
)impl<T: FromBytes> TryFromBytes for T
This conflicts with other blanket impls which we need for completeness:
impl<T: TryFromBytes> TryFromBytes for [T]
impl<const N: usize, T: TryFromBytes> TryFromBytes for [T; N]
As a result, we have to leave
TryFromBytes
andFromBytes
as orthogonal. We may want to make it so thatderive(FromBytes)
automatically emits an impl ofTryFromBytes
, although in the general case that may require custom DST support.Open questions
TryFromBytes
forT: FromBytes
? UnlikeFromZeroes: TryFromBytes
, where you may need to perform runtime validation, if you know thatT: FromBytes
, then in principle you know thatis_bit_valid
can unconditionally returntrue
without inspecting its argument, and so in principle it shouldn't matter whether you can construct aMaybeValid<Self>
. Is there some way that we could allowFromBytes
types to specify<Self as AsMaybeUninit>::MaybeUninit = ()
or similar in order to bypass the "only sized types or slices can implementAsMaybeUninit
" problem?KnownLayout
trait lands. There's a good chance that, under that design, we'd end up withFromZeroes: KnownLayout
. IfKnownLayout: AsMaybeUninit
(or just absorbs the current definition ofAsMaybeUninit
into itself), it'd solve this problem since all zerocopy traits would imply support forMaybeValid
.Self: Sized
bounds ontry_from_ref
andtry_from_mut
(without needing full custom-DST support)?derive(FromBytes)
emit an impl ofTryFromBytes
? What about custom DSTs?T
when implementingTryFromBytes
forUnalign<T>
(#320)?Future directions
UnsafeCell
intry_from_ref
, then the user could obtain an&UnsafeCell
and a&[u8]
view of the same memory, which is unsound (it's unsound to even exist under Stacked Borrows, and unsound to expose to safe code in all cases). For values (i.e.,try_read_from
), we'd like to be able to support this - as long as we have some way of performing validation, it should be fine to return anUnsafeCell
by value even if its bytes were copied from a&[u8]
. Actually supporting this in practice is complicated for a number of reasons, but perhaps a future extension could support it. Reasons it's complicated:is_bit_valid
operates on aNonNull<Self>
, so interior mutability isn't inherently a problem. However, it needs to be able to call a user's custom validator, which instead operates on a&Self
, which is a problem.is_bit_valid
require that it's argument not be either experiencing interior mutation or, under Stacked Borrows, contain anyUnsafeCell
s at all. When theNonNull<Self>
is synthesized from a&[u8]
, this isn't a problem, but if in the future we want to support type-to-type conditional transmutation, it might be a problem. If, in the future, merely containingUnsafeCell
s is fine, then we could potentially design a wrapper type which "disables" interior mutation and supports field projection. This might allow us to solve this problem.Prior art
The bytemuck crate defines a
CheckedBitPattern
trait which serves a similar role to the proposedTryFromBytes
.Unlike
TryFromBytes
,CheckedBitPattern
introduces a separate associatedBits
type which is a type with the same layout asSelf
except that all bit patterns are valid. This serves the same role asMaybeValid<Self>
in our design. One advantage for theBits
type is that it may be more ergonomic to write validation code for it, which is important for manual implementations ofCheckedBitPattern
. However, our design expects that manual implementations ofTryFromBytes
will be very rare. SinceCheckedBitPattern
's derive doesn't support custom validation, any type with safety invariants would need a manual implementation. By contrast, theTryFromBytes
derive's support for a custom validation function means that, from a completeness standpoint, it should never be necessary to implementTryFromBytes
manually. The only case in which a manual implementation might be warranted would be for performance reasons.The text was updated successfully, but these errors were encountered: