Re: [PATCH v7 05/10] rust: io: add IoLoc and IoWrite types

From: Alexandre Courbot

Date: Fri Feb 27 2026 - 19:33:27 EST


On Sat Feb 28, 2026 at 3:02 AM JST, Gary Guo wrote:
<snip>
>> +/// A pending I/O write operation, bundling a value with the [`IoLoc`] it should be written to.
>> +///
>> +/// Created by [`IoLoc::set`], [`IoLoc::zeroed`], [`IoLoc::default`], [`IoLoc::init`], or
>> +/// [`IoLoc::init_default`], and consumed by [`Io::write`] or [`Io::try_write`] to perform the
>> +/// actual write.
>> +///
>> +/// The value can be modified before writing using [`IoWrite::update`] or [`IoWrite::try_update`],
>> +/// enabling a builder pattern:
>> +///
>> +/// ```ignore
>> +/// io.write(REGISTER.init(|v| v.with_field(x)));
>> +/// ```
>
> Thinking about this again, I think we might still want to write
>
> io.write(REGISTER, value)

This was the original design, but as you point out below this makes the
very common case of writing a register value built from scratch more
verbose than it needs. Real-world examples are significantly worse, e.g:

bar.write(
regs::NV_PFALCON_FALCON_DMATRFMOFFS::of::<E>()
.try_init(|r| r.try_with_offs(load_offsets.dst_start + pos))?,
);

turns into

bar.write(
regs::NV_PFALCON_FALCON_DMATRFMOFFS::of::<E>(),
regs::NV_PFALCON_FALCON_DMATRFMOFFS::zeroed()
.try_with_offs(load_offsets.dst_start + pos)?,
);

>
> Granted, this does mean that we will write `REGISTER` twice in some cases:
>
> io.write(REGISTER, REGISTER::default().with_field(foo));
>
> But, we have no redundancy for the update case:
>
> io.update(REGISTER, |v| v.with_field(foo));

In nova-core, `update` is less common than writing a register value
built from scratch. That's really the only thing that holds us from
two arguments in `write`.

>
> The reason for this thought is that conceptually, the type of a register is not
> necessarily coupled with the register itself. This is the case currently for
> register arrays, but also the case when we think in the long term where

For these cases I was thinking we could rely on From/Into
implementations, as it gives us the option to make the conversion
explicit and thus less error-prone.

> bitfields become decoupled from `register!`. People also might just want to
> define a register of u32 without any bitfields at all, and in this case writing
>
> io.write(REGISTER, u32_value)
>
> looks nicer than
>
> io.write(REGISTER.set(u32_value))
>
> Spelling out the "offset" explictly, arguably, is also more natural for a C
> programmer, and also simpler on the implementation side (all the helper methods
> on `IoLoc` would go away).

That's true that it would be less `(try_)init` and friends helpers
(`IoWrite` would also go away I guess?). I'm not a huge fan of these
because they only cover specific initial values (zeroed() and
default()).

Maybe we can macro our way out of it?

It would look a little bit weird to resort to a macro just for the write
case, but I don't think we will be able to work around this without any
kind of compromise. FWIW, I think the single-argument `write` is a
reasonable one that might look a bit surprising at first encounter, but
has the benefit of reading naturally.