Re: [PATCH v2 1/6] rust: io: turn IoCapable into a functional trait

Alexandre Courbot
2026-02-17T01:37:03Z

On Tue Feb 17, 2026 at 2:04 AM JST, Danilo Krummrich wrote:
> On Mon Feb 16, 2026 at 2:27 PM CET, Alexandre Courbot wrote:
>> It doesn't - here is the implementation of Io for Mmio:
>>
>> impl<const SIZE: usize> Io for Mmio<SIZE> {
>> /// Returns the base address of this mapping.
>> #[inline]
>> fn addr(&self) -> usize {
>> self.0.addr()
>> }
>>
>> /// Returns the maximum size of this mapping.
>> #[inline]
>> fn maxsize(&self) -> usize {
>> self.0.maxsize()
>> }
>> }
>>
>> Now what prevents me from doing this:
>>
>> impl<const SIZE: usize> Io for YoloMmio<SIZE> {
>> fn addr(&self) -> usize {
>> self.0.addr()
>> }
>>
>> fn maxsize(&self) -> usize {
>> self.0.maxsize() + 0x10000
>> }
>> }
>>
>> With that, I have allowed callers to invoke the unsafe methods of
>> `IoCapable` on an extra 0x10000 bytes of I/O I don't own, without any
>> unsafe code.
>
> I don't think you did, as you only present half of your counter example; you
> left out the IoCapable part.
>
> I.e. with what you have above cannot uphold the safety justification in the
> corresponding IoCapable implementation:
>
> This is the invariant on struct Mmio:
>
> /// # Invariant
> ///
> /// `addr` is the start and `maxsize` the length of valid I/O mapped memory region of size
> /// `maxsize`.
>
> And in impl_mmio_io_capable!() you refer to this invariant:
>
> macro_rules! impl_mmio_io_capable {
> ($mmio:ident, $(#[$attr:meta])* $ty:ty, $read_fn:ident, $write_fn:ident) => {
> $(#[$attr])*
> impl<const SIZE: usize> IoCapable<$ty> for $mmio<SIZE> {
> unsafe fn io_read(&self, address: usize) -> $ty {
> // SAFETY: By the trait invariant `address` is a valid address for MMIO operations.
> unsafe { bindings::$read_fn(address as *const c_void) }
> }
>
> unsafe fn io_write(&self, value: $ty, address: usize) {
> // SAFETY: By the trait invariant `address` is a valid address for MMIO operations.
> unsafe { bindings::$write_fn(value, address as *mut c_void) }
> }
> }
> };
> }
>
> But your YoloMmio implementation doesn't provide this invariant (because it
> can't).
>
> So, how do you justify the unsafe call to bindings::$write_fn and
> bindings::$read_fn now that you have to call impl_mmio_io_capable!() for
> YoloMmio?
>
> Again, you can't justify it, which proves that it doesn't matter what YoloMmio
> returns, it matters how you can justify it in io_read() and io_write().

Ah, I think I finally get it now. `Io` and `IoKnownSize` can piggyback
on the `IoCapable` safety requirement, because all the unsafe code
requires an `IoCapable` implementation anyway. I guess that's sound
indeed.

I don't think the same can apply to `io_read` and `io_write` though -
`IoCapable` is public, and so anyone could call them with an arbitrary
argument, e.g.

let mmio: &Mmio<0x100>;
...
IoCapable::<u8>::io_read(mmio, usize::MAX);

Here we have no way of making the callers enforce the internal safety
requirement of these methods, thus we need to keep them unsafe.