Skip to content
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

Make errors witnesses to a cast's source type #1863

Open
joshlf opened this issue Oct 10, 2024 · 0 comments
Open

Make errors witnesses to a cast's source type #1863

joshlf opened this issue Oct 10, 2024 · 0 comments

Comments

@joshlf
Copy link
Member

joshlf commented Oct 10, 2024

Design

Problem statement

Our errors currently store the source value of any cast or conversion. We also permit the user to discard this value via .map_src methods, which is sometimes necessary in order to play nicely with APIs that require errors to be 'static, Send, or Sync.

However, we've also discussed supporting #280 by doing the opposite: by preventing the user from discarding the source, we can use an error object as a witness that an error was encountered with a particular source type, and thus as a witness that certain failure modes are impossible (in particular, when casting from Src to Dst where Src is more aligned than Dst, alignment errors are impossible).

Design

These two goals are at odds, but I think we can reconcile them. We do this by storing both the original source type and a source value.

pub struct AlignmentError<Src, Dst: ?Sized, SrcVal = Src> {
    /// The source value involved in the conversion.
    src_val: SrcVal,
    /// The inner destination type inolved in the conversion.
    ///
    /// INVARIANT: An `AlignmentError` may only be constructed if `Dst`'s
    /// alignment requirement is greater `Src`'s alignment requirement.
    src_dst: SendSyncPhantomData<(Src, Dst)>,
}

impl<Src, Dst: ?Sized, SrcVal> AlignmentError<Src, Dst, SrcVal> {
    pub fn map_src<NewSrcVal>(self, f: impl Fn(SrcVal) -> NewSrcVal) -> AlignmentError<Src, Dst, NewSrcVal> {
        AlignmentError { src_val: f(self.src_val), src_dst: SendSyncPhantomData::default() }
    }
}

With this change, we can require that both Src and Dst are the actual types involved in a cast, and thus we can make the internal invariant more powerful. As it stands today, the invariant is that:

zerocopy/src/error.rs

Lines 237 to 238 in 0b8ad3e

/// INVARIANT: An `AlignmentError` may only be constructed if `Dst`'s
/// alignment requirement is greater than one.

With this change, we generalize the invariant to require that Dst's alignment is greater than Src's alignment.

Use

In theory, this should let us infallibly discard alignment errors in more circumstances, generalizing our current support:

zerocopy/src/error.rs

Lines 720 to 769 in 0b8ad3e

impl<Src, Dst: ?Sized + Unaligned> From<CastError<Src, Dst>> for SizeError<Src, Dst> {
/// Infallibly extracts the [`SizeError`] from this `CastError` since `Dst`
/// is unaligned.
///
/// Since [`Dst: Unaligned`], it is impossible to encounter an alignment
/// error, and so the only error that can be encountered at runtime is a
/// [`SizeError`]. This method permits extracting that `SizeError`
/// infallibly.
///
/// [`Dst: Unaligned`]: crate::Unaligned
///
/// # Examples
///
/// ```rust
/// use zerocopy::*;
/// # use zerocopy_derive::*;
///
/// #[derive(FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned)]
/// #[repr(C)]
/// struct UdpHeader {
/// src_port: [u8; 2],
/// dst_port: [u8; 2],
/// length: [u8; 2],
/// checksum: [u8; 2],
/// }
///
/// #[derive(FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned)]
/// #[repr(C, packed)]
/// struct UdpPacket {
/// header: UdpHeader,
/// body: [u8],
/// }
///
/// impl UdpPacket {
/// pub fn parse(bytes: &[u8]) -> Result<&UdpPacket, SizeError<&[u8], UdpPacket>> {
/// // Since `UdpPacket: Unaligned`, we can map the `CastError` to a `SizeError`.
/// UdpPacket::ref_from_bytes(bytes).map_err(Into::into)
/// }
/// }
/// ```
#[inline(always)]
fn from(err: CastError<Src, Dst>) -> SizeError<Src, Dst> {
match err {
#[allow(unreachable_code)]
CastError::Alignment(e) => match Infallible::from(e) {},
CastError::Size(e) => e,
CastError::Validity(i) => match i {},
}
}
}

However, this requires being able to express alignment inequality as a type-level bound (e.g. via #1316), which we don't yet support.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant