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

From: Gary Guo

Date: Sat Mar 07 2026 - 16:10:27 EST


On Sat Mar 7, 2026 at 12:05 AM GMT, Alexandre Courbot wrote:
> On Sat Mar 7, 2026 at 12:35 AM JST, Gary Guo wrote:
>> On Fri Mar 6, 2026 at 2:32 PM GMT, Alexandre Courbot wrote:
>>> On Fri Mar 6, 2026 at 10:20 PM JST, Gary Guo wrote:
>>>> I mean not sure `at` gives me that impression at all. It would just let me know
>>>> that I am accessing it at a different location. If you omit the `MyRegArray`
>>>> part then there's no real indication that this is an array to me.
>>>
>>> `at` is a function name, we can change it - I picked it because it is
>>> short and reasonably descriptive. The point being: we have a unique
>>> function that indicates unambigously that we are using a location for
>>> an array of registers.
>>
>> Okay, maybe `at` is not the biggest issue. I just instinctively feel the usage
>> example being awkward.
>>
>> Perhaps the fact that `Reg` exist itself is awkward to me. It looks like a type
>> that exists only for things to typecheck, and not itself represent a meaningful
>> concept.
>
> After partially implementing your two-args proposal it really turns out
> the two alternatives are very similar in essence, with most of the
> differences being details like naming and scope.
>
> The one fundamental difference is that it didn't occur to me that we
> could resolve a generic argument for a function in first position of
> write using another argument, hence I went for:
>
> bar.write(location_builder(location_info, value));
>
> but it is also possible to do
>
> bar.write(location_builder(location_info), value);
>
> And that's really the main difference. All the rest is mostly details
> about whether `location_builder` is an associated function of a type, a
> method of a ZST, or just a module-level function. From the point of view
> of `Io`, none of this matters as long as it gets an `IoLoc` that is
> compatible with the value. IOW, each Io submodule can provide the
> location builders that make sense for it.
>
> The `Reg` thing in my previous email was just a way to provide a scope
> for these location builders, but in retrospect a module-level function
> seems better to me if we want to keep things concise. So if we turn the
> location builders into module-level methods we could have:
>
> use kernel::io::register as reg;
>
> bar.write(reg::at(10), SOME_ARRAY_SCRATCH::foo());
>
> Or does
>
> bar.write(SOME_ARRAY_SCRATCH::foo(), reg::at(10));
>
> Flow better now?

I'm still generally inclined to have location at front, as it's also going to be
how projection syntax will work for structs:

io_write!(bar, .scratch[10], foo);

or just assignment in general

my.scratch[10] = foo;

I should also say that, my desire of having the explicit locaction is also
partly driven by the desire to eventually unify projections with the register
macro. If we would generate a struct where fields inside are registers at there
correct offset, then `io_write!(bar, .register_name, foo)` would indeed be a
canonical way of accessing such registers using I/O projection. There's no way
to simplify `my_struct.foo = Foo {}` without repeating `foo` twice :)

For context, Benno suggested in rust-lang zulip in a discussion about how it
might be possible to use field projections to replace register macro in the
future:
https://rust-lang.zulipchat.com/#narrow/channel/522311-t-lang.2Fcustom-refs/topic/custom.20virtual.20fields/near/573703808
Don't worry, the language features don't exist yet, there's no change needed to
your series :)

Probably I should mention this earlier so you see the aspect I'm coming from,
but none of this is directly related to the `register!` work you're having
today, so I was avoiding to argue with unrelated features.

>
>>
>>>
>>> You seem to reject this design because the syntax isn't obvious and
>>> natural to you at first read. We are trying to build something new, so
>>> of course if will look a bit alien at the first encounter. That's why we
>>> have documentation, and I think it is not very difficult to wrap your
>>> head around it after seeing a few examples.
>>>
>>>>
>>>> If `at` is only for array, how would you represent the case where the same type
>>>> is being used in multiple registers?
>>>
>>> That's not something that is supported by the register macro currently
>>> (probably not a big change, but not something I will do in this series).
>>>
>>> But to try and answer your question, such register types would not have
>>> an `IoLoc` implementation of their own and would need to have their
>>> location constructed explicitly. In accordance, the construction of
>>> their value would not bear any location information; thus there would be
>>> no redundancy.
>>>
>>> We do have a case that is pretty close to this with relative registers.
>>> These are accessed like this (real examples):
>>>
>>> bar.write(Reg::of::<Gsp>(regs::NV_PFALCON_FALCON_DMACTL::zeroed()));
>>>
>>> and
>>>
>>> bar.write(Reg::of::<Sec2>(regs::NV_PFALCON_FALCON_DMACTL::zeroed()));
>>
>> Relative registers are something that on my list to eliminate and replace with
>> projections, so I don't particular care about how it looks like.
>
> That's interesting, I thought register arrays would be easier to replace
> than relative registers, I really need to take a closer look.

Register array with stride might be tricky, although I have to say I haven't put
much thought into this yet.

>
>>
>>>
>>> But for register types that can be used at several arbitrary locations,
>>> I think this would be even simpler. The different locations would just
>>> need to implement `IoLoc<T>`, where `T` is the shared register type.
>>> Then, considering that the two-arguments version is called `write_at`,
>>> you can simply do:
>>>
>>> bar.write_at(REG_LOCATION, reg_value);
>>
>> Having `write_at` as the name of the two-argument version is okay to me.
>
> I picked that for illustrative purposes, but `write` should be the
> version that is going to be the most used. And if we remove the value
> from the location builder, then the most used version is clearly going
> to be the two arguments one.
>
> The one argument variant would then become a shortcut - `put` sounds
> adequate to me, but `write_val` also works.

`put` sounds good. It was also the name that Gemini suggests to me when I asked
it to come up with a short and concise name for this :)

>
>>
>>>
>>> ... but this design also makes this possible:
>>>
>>> bar.write((REG_LOCATION, reg_value));
>>
>> I considered about this, but IMO this looks awkward.
>
> Funny how you spell "elegant". :)

It's indeed quite elegant from a FP and type theorist lens. I'm not personally
objecting this, although I feel that this may looks strange to people with C
background (not sure if that's actually true, though).

>
>>
>>>
>>> Tuples of (location, value) do implement `IoLoc` themselves, so we can
>>> use this little trick to support a 2-arguments syntax with a single
>>> method.
>>>
>>>>
>>>>>
>>>>>>
>>>>>> If you want to make things more explicit you could also have
>>>>>> `bar.write(at_array(10), ...)` or something similar.
>>>>>
>>>>> Is it possible to generate an `IoLoc<T>` without having `T` mentioned
>>>>> anywhere in the call to `at_array`?
>>>>
>>>> Exactly same as the `impl IoLoc<REG> for usize`:
>>>>
>>>> struct AtArray(usize);
>>>>
>>>> impl IoLoc<REG> for AtArray {
>>>> ...
>>>> }
>>>
>>> Right, but can the correct `REG` be inferred when the call to `at_array`
>>> doesn't bear that information? The type inferred by the second argument
>>> would have to be propagated to the first. Guess I'll try and see.
>>
>> RE: inference and bounds checking issue that you mentioned in another email, I
>> think you can have
>>
>> fn at_array(i: usize) -> Result<AtArray<T>> { .. }
>>
>> and
>>
>> impl IoReg<REG> for AtArray<REG> {}
>>
>> The type inference here is no different to `Reg::at`.
>
> Yes, this clearly works and then it actually becomes `RegisterArrayLoc`,
> which already exists and also implements `IoLoc`. This convergence looks
> like positive sign to me.
>
>>
>>>
>>>>
>>>>>
>>>>>>
>>>>>> For the array case I really think trying to shove everything into a single
>>>>>> argument is a footgun. The type of value in this case *doesn't* tell us the
>>>>>> location, and the location needs to be explicit.
>>>>>
>>>>> bar.write(Reg::at(10, regs::MyRegArray::foo()))
>>>>>
>>>>> "write the constructed value at the 10th position of the `MyRegArray`
>>>>> register array"
>>>>>
>>>>> What is missing here?
>>>>
>>>> This is completely un-natural if I try to read it with fresh mind (try to forget
>>>> about implementation details for a second).
>>>
>>> That's what documentation is for. Please give it a fair chance and ask
>>> yourself: would it still look unnatural after working with it for
>>> 20 minutes?
>>>
>>>>
>>>> `MyRegArray` here is a type name that is a bitfield and not an array. `foo` returns a
>>>> single value and not an array. "at" here is saying that the register is at a
>>>> specific location and doesn't really indicate the array nature.
>>>>
>>>> This is why I insist that I would prefer an explicit location
>>>>
>>>> bar.write(REG_ARRAY.at(10), Reg::foo())
>>>>
>>>> would have no ambiguity whatsoever about user's intent.
>>>
>>> IIUC `REG_ARRAY` would be a const ZST and `at` a method returning an
>>> `AtArray(usize)`? I still have doubts that its generic type could be
>>> inferred automatically but it's worth giving it a go.
>>>
>>> If that works, then I assume fixed register writes would look like
>>>
>>> bar.write(FIXED, Reg::foo());
>>>
>>> Unless we have a specialized `write` variant for them.
>>
>> That, or `()` as I mentioned.
>
> Wouldn't it look... awkward? :)
>
> But if we can agree on
>
> bar.put(Reg::foo());
>
> or even
>
> bar.write_val(Reg::foo());

I'm okay with either.

>
> Then I believe we are getting close to something that can make everyone
> happy (pending Danilo's blessing). The `()` syntax can also be supported
> for generic code.

Yep, if we want to support both the `put` can just be a sugar

fn put<T>(&self, value: T) where (): IoLoc<T> + ... {
self.write((), value)
}

Best,
Gary

>
> I'll try to speedrun the remainder of the implementation and send a new
> revision for review, but AFAICT this ticks all the boxes.