Why does adding an already-satisfied trait bound cause my code to break?

I have the following code:

use core::mem::MaybeUninit;

pub unsafe trait AsMaybeUninit {
    type MaybeUninit: ?Sized;
}

unsafe impl<T: Sized> AsMaybeUninit for T {
    type MaybeUninit = MaybeUninit<T>;
}

unsafe impl<T: Sized> AsMaybeUninit for [T] {
    type MaybeUninit = [MaybeUninit<T>];
}

unsafe impl AsMaybeUninit for str {
    type MaybeUninit = <[u8] as AsMaybeUninit>::MaybeUninit;
}

unsafe trait FromBytes {
    unsafe fn read_from_bytes(bytes: &[u8]) -> Option<Self>
    where
        Self: Sized,
    {
        fn read_from_bytes_inner<T: AsMaybeUninit<MaybeUninit = MaybeUninit<T>>>(
            bytes: &[u8],
        ) -> Option<T> {
            todo!()
        }

        read_from_bytes_inner(bytes)
    }
}

This compiles fine. However, if I change unsafe trait FromBytes to unsafe trait FromBytes: AsMaybeUninit, it fails:

error[E0271]: type mismatch resolving `<Self as AsMaybeUninit>::MaybeUninit == MaybeUninit<Self>`
  --> src/lib.rs:32:9
   |
32 |         read_from_bytes_inner(bytes)
   |         ^^^^^^^^^^^^^^^^^^^^^ type mismatch resolving `<Self as AsMaybeUninit>::MaybeUninit == MaybeUninit<Self>`
   |
note: expected this to be `MaybeUninit<Self>`
  --> src/lib.rs:10:24
   |
10 |     type MaybeUninit = MaybeUninit<T>;
   |                        ^^^^^^^^^^^^^^
   = note:        expected union `MaybeUninit<Self>`
           found associated type `<Self as AsMaybeUninit>::MaybeUninit`
   = help: consider constraining the associated type `<Self as AsMaybeUninit>::MaybeUninit` to `MaybeUninit<Self>`
   = note: for more information, visit https://doc.rust-lang.org/book/ch19-03-advanced-traits.html
note: required by a bound in `read_from_bytes_inner`
  --> src/lib.rs:26:51
   |
26 |         fn read_from_bytes_inner<T: AsMaybeUninit<MaybeUninit = MaybeUninit<T>>>(
   |                                                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `read_from_bytes_inner`

Why does adding a bound which was already satisfied cause this to break, and is there a way I can fix it? In the use case that this is a minimized version of, I unfortunately need the FromBytes: AsMaybeUninit bound, so I can't just get rid of it.

I believe this is an insufficient normalization issue. Without the Self: AsMaybeUninit bound, calling read_from_bytes_inner sees that Self: Sized and that impl<T: Sized> AsMaybeUninit for T applies, so it can use that impl. When Self: AsMaybeUninit, calling read_from_bytes_inner sees that bound and uses that, and never looks for the fact that the Self: Sized allows it to know which impl applies.

Adding a redundant where Self: AsMaybeUninit<MaybeUninit = MaybeUninit<Self>> bound to read_from_bytes (not the inner one) should allow the code to compile.

3 Likes

It does

Do you think this should be considered a compiler bug? I filed this issue: Adding trait bound causes unrelated trait resolution to fail · Issue #115080 · rust-lang/rust · GitHub

Yeah, I tried that too. It works, but unfortunately it bubbles up into other generic uses of FromBytes (given only T: FromBytes + Sized, you can't call read_from_bytes). Ideally I'd figure out a way to avoid changing the signature of read_from_bytes.

You could move the method to a sub-trait.

I figured out a way around the issue. There's no real analogue in this stripped-down example, so here's the fix in the actual commit I'm working with:

fn try_read_from(bytes: &[u8]) -> Option<Self>
where
    Self: Sized,
{
    // TODO(https://github.com/rust-lang/rust/issues/115080): Inline this
    // function once #115080 is resolved.
    #[inline(always)]
    fn try_read_from_inner<T: Sized, F: FnOnce(&MaybeValid<T>) -> bool>(
        bytes: &[u8],
        is_bit_valid: F,
    ) -> Option<T> {
        let maybe_valid = MaybeValid::<T>::read_from(bytes)?;

        if is_bit_valid(&maybe_valid) {
            Some(unsafe { maybe_valid.assume_valid() })
        } else {
            None
        }
    }

    try_read_from_inner(bytes, Self::is_bit_valid)
}

The trick is that I actually throw away the AsMaybeUninit bound in try_read_from_inner, and that seems to solve the problem that @CAD97 identified (note that T only has a Sized bound).

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.