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

From: Alexandre Courbot

Date: Mon Feb 16 2026 - 08:27:40 EST


On Mon Feb 16, 2026 at 9:08 PM JST, Danilo Krummrich wrote:
> On Mon Feb 16, 2026 at 12:51 PM CET, Alexandre Courbot wrote:
>> On Thu Feb 12, 2026 at 11:52 PM JST, Danilo Krummrich wrote:
>>> On Thu Feb 12, 2026 at 3:11 PM CET, Gary Guo wrote:
>>>> They can, but the `Io` trait just passes the wrong address to the `IoCapable`
>>>> trait, and nothing horrible can happen without doing things unsafely inside
>>>> `IoCapable` impl, which is controlled by the user who implements `Io`. It looks
>>>> to me that unsafe code is still needed to do bogus things.
>>>
>>> I think what you mean is that the invariant of `addr` and `maxsize` being valid
>>> is on the implementing type of `Io`, e.g. `MmioRaw` and `Mmio`. The same applies
>>> to IoKnownSize::MIN_SIZE.
>>>
>>> To me this seems like a valid way of arguing.
>>
>> But still, using only safe code an implementor of `Io` can lie about
>> this safety statement:
>>
>> // SAFETY: `address` has been validated by `io_addr`.
>> Ok(unsafe { self.io_read(address) })
>>
>> Granted, the same person will likely have written the `IoCapable`
>> implementations, but its safety requirements cannot be fulfilled unless
>> the caller also guarantees that the offsets it passes are valid, which
>> the type system alone cannot guarantee - thus the need to make `Io` and
>> `IoKnownSize` unsafe IMHO.
>
> Hm...the implementor of Io and IoCapable has to justify in the implementation of
> IoCapable, i.e. in io_read() and io_write() that the address is in fact correct.

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.

> So, considering that, it looks to me that we don't even need io_read() and
> io_write() to be unsafe in the first place?
>
> I.e. we are only passing through values in generic implementation.

Possibly? Although `io_read` and `io_write` typically call unsafe C
methods though, so they would have to always maintain *their* safety
requirements if we go that way (instead of delegating that to `Io`
through the `IoCapable` implementation's safety statement).