Replies: 2 comments
-
|
Notes from a synchronous conversation with @jswrenn. NamingGoing with option (1). I've updated #5 to list renaming the InputReborrowing is very low-cost from a syntax perspective, and is often performed implicitly. Given that reborrowing is already possible, the API complexity cost of adding PanickingKeeping the status quo: Our API does not panic. Users can easily write OutputReturn the Remaining Bytes?We lean towards returning the remaining bytes, but we don't commit to it yet. It's easier to discard extra information that isn't needed than to manually recompute the remaining bytes if they are needed but our API does not return them. Users who have stricter requirements regarding what code is effectively optimized can use a technique like that described in the "Performance" sub-section. Note that this is not a firm decision: There are still discussions to resolve in #884, #1051, and #1059. Return the Original Buffer?We will return the original buffer in the |
Beta Was this translation helpful? Give feedback.
-
|
The discussion itself is complete, and the work is now either done or tracked (in #871), so I'm going to close this. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
The 0.7 edition of zerocopy defines a large set of constructors on the
FromBytestrait. These methods constructSelf,&Self,&mut Self,&[Self]and&mut [Self]from input byte slices of the appropriate mutability.As we prepare for the 0.8 release, we would like to reconsider the naming and semantics of these methods. In 0.8, we will additionally be introducing a
TryFromBytestrait, which should adhere to the same conventions adopted forFromBytes.Naming
Decision: Option (1). See #1095 (comment) for notes.
We are considering these naming conventions:
try_){mut,ref,read,slice,mut_slice}_from(_{prefix,suffix})Produces:
mut_frommut_from_prefixmut_from_suffixmut_slice_frommut_slice_from_prefixmut_slice_from_suffixread_fromread_from_prefixread_from_suffixref_fromref_from_prefixref_from_suffixslice_fromslice_from_prefixslice_from_suffixtry_mut_fromtry_mut_from_prefixtry_mut_from_suffixtry_mut_slice_fromtry_mut_slice_from_prefixtry_mut_slice_from_suffixtry_read_fromtry_read_from_prefixtry_read_from_suffixtry_ref_fromtry_ref_from_prefixtry_ref_from_suffixtry_slice_fromtry_slice_from_prefixtry_slice_from_suffixtry)_from_{mut,ref,read,slice,mut_slice}(_{prefix,suffix})Produces:
from_mutfrom_mut_prefixfrom_mut_slice← Misleading (Special-case?)from_mut_slice_prefix← Misleading (Special-case?)from_mut_slice_suffix← Misleading (Special-case?)from_mut_suffixfrom_read← Ungrammatical (Special-case?)from_read_prefix← Ungrammatical (Special-case?)from_read_suffix← Ungrammatical (Special-case?)from_reffrom_ref_prefixfrom_ref_suffixfrom_slice← Misleading (Special-case?)from_slice_prefix← Misleading (Special-case?)from_slice_suffix← Misleading (Special-case?)try_from_muttry_from_mut_prefixtry_from_mut_slice← Misleading (Special-case?)try_from_mut_slice_prefix← Misleading (Special-case?)try_from_mut_slice_suffix← Misleading (Special-case?)try_from_mut_suffixtry_from_read← Ungrammatical (Special-case?)try_from_read_prefix← Ungrammatical (Special-case?)try_from_read_suffix← Ungrammatical (Special-case?)try_from_reftry_from_ref_prefixtry_from_ref_suffixtry_from_slice← Misleading (Special-case?)try_from_slice_prefix← Misleading (Special-case?)try_from_slice_suffix← Misleading (Special-case?)Observations:
readdoes not fit neatly into the latter convention. Do we special-case this convention so these are instead in the formread_fromrather thanfrom_read?slicedoes not fit neatly into the latter convention. Do we special case this sosliceappears early in the name, as it does in the first convention?readis the only verb here. We need something because we don't want to just having a method namedfrom. Usingvalinstead ofreadmight be more consistent, but, OTOH,readclearly signposts that this method performs a copy (which is valuable!).Input
Decision: APIs will continue to take concrete slices rather than generic parameters. See #1095 (comment) for notes.
To preserve object safety, these methods consume
&[u8]and&mut [u8]rather thanimpl ByteSliceandimpl ByteSliceMut. This continues to seem sensible.We do not know whether our users rely on
FromBytesbeing object safe. Defensively, we might want to consider preemtively violating object safety, so that we have the SemVer freedom to either restore it later, or generalize these methods toByteSliceandByteSliceMut.Panicking
Decision: APIs will continue to return explicit failure values rather than panicking, although we leave open the possibility of adding panicking methods in addition as a convenience. See #1095 (comment) for notes.
To leave the decision of panicing up to customers, these methods do not panic; they either return
OptionorResult.Output
Presently, most of these methods only return the deserialized value in the success case; e.g.:
But is this all that these methods should return?
Return the Remaining Bytes?
Decision: Lean towards returning remaining bytes, but this is not yet committed to. See #1095 (comment) for notes.
In many use cases, the underlying buffer must be advanced after parsing. With the above API, this can be done manually:
The commonality of this pattern suggests we modify our API to additionally return the excess bytes:
Return the Original Buffer?
Decision: Return the original buffer in the
Errvariant of aResult. See #1095 (comment) for notes.In the event of failure, neither the minimal nor the above augmented API permits using the original buffer in error handling. For instance, attempting this:
...produces this error message:
We can rememdy this by returning a
Resultinstead, that provides the original buffer upon failure; permitting, e.g.:Output Considerations
Ergonomics
How much complexity does each API add for users who only need the deserialized value? Under each output API, such a user would write:
How much complexity does each API add for users who need the excess bytes?
Performance
Presently, zerocopy computes the deserialization–remainder split point at its very lowest levels of abstraction. Under the minimal API, this split is discarded. A user (as seen above) can easily recompute the remainder with
size_of_val, but doing so may generate additional runtime bounds checks. The remainder-returning option might offer a slight performance improvement for users requiring the excess bytes.Optimization Misses
It is plausible that under some circumstances that these three conditions are all true:
For these circumstances, zerocopy provides the
Unalignwrapper type andtransmutemacros. Thetransmutemacros are as close to truly zero-cost of an API that zerocopy provides: they perform no runtime checks and they do not invoke any functions besidesmem::transmute.For example, to replicate
Foo::ref_fromwith full control over runtime checks, one could write something like:Precise control over the semantics and runtime checks is achieved by changing the code before and after the
transmute_ref!invocation.Conclusions
To be determined.
Appendix: Higher Fidelity Errors
None of the return types evaluated in this proposal provide detailed information about why failures occured. Did a
FromBytesdeserialization fail because of misalignment? Because of insufficient bytes? Or, in the case ofTryFromBytes, because of invalid data?In the
Result-returning API, the failure reason can, at least, be re-computed. However, perhaps we should provide the failure reason explicitly; e.g.:See #528 for further discussion.
A point of comparison: bytemuck provides this information with its
PodCastErrortype. A cursory search of Github suggests that this fidelity is useful to some customers.Related Discussions
FromBytes::(mut_)?slice_from_[prefix|suffix]also returns the prefix/suffix instead of just the read slice #884FromBytesconversion methods #1059Beta Was this translation helpful? Give feedback.
All reactions