Re: [PATCH v2 12/16] scripts/qapi: generate high-level Rust bindings

Paolo Bonzini
2026-03-03T15:56:20Z

On Tue, Mar 3, 2026 at 1:31 PM Markus Armbruster <armbru@redhat.com> wrote:

> >> You replied "In this case it doesn't really matter: public items (such
> >> as QAPI enum entries, or struct fields) do not raise the unused warning
> >> anyway."
> >>
> >> What gives us confidence rs_name() will only be used where it doesn't
> >> really matter?
> >
> > The fact that all QAPI type definitions are (more or less by design) public.
>
> Any particular reason not to use the same 'q_' prefix as in C?

The other Rust convention is that you usually have enums in CamelCase
(starting with an uppercase letter). "q_" or "Q_" would also trigger a
warning, and they would complicate to_camel_case a bit (because the
prefix would have to be kept including the underscore).

> >> This maps 'foo-0123-bar' to 'Foo_0123Bar'. Intentional? I'd kind of
> >> expect 'Foo0123Bar'.
> >
> > Will fix (it is meant for 0123-45). New version is:
> >
> > def to_camel_case(value: str) -> str:
> > result = ''
> > for p in re.split(r'[-_]+', value):
> > if not p:
> > pass
> > elif p[0].isalpha() or (result and result[-1].isalpha()):
> > result += p[0].upper() + p[1:]
> > else:
> > result += '_' + p
> > return result
>
> Maps '0123-45' to '_0123_45'. Is the leading '_' intentional?

Yes, because otherwise the output is not an identifier; but it doesn't
matter since the input is actually an identifier already and
to_camel_case('_0123_45') does give '_0123_45'. In neither case you
get the invalid identifier '0123_45'.

> I'm fine with not running rustfmt, and I'm fine with running it always
> (makes it a hard requirement). Running it sometimes feels like more
> trouble than it's worth.

Yeah, I will drop it. I changed mcgen to allow removing empty lines in
the middle of a declaration, like this:

def mcgen(*code: T.Sequence[str], **kwds: object) -> str:
'''
Generate ``code`` with ``kwds`` interpolated. Separate
positional arguments represent separate segments that could
expand to empty strings; empty segments are omitted and no
blank lines are introduced at their boundaries.
'''
last = len(code) - 1
result = []
for i, s in enumerate(code):
if s.startswith('\n'):
s = s[1:]
if i != last:
s = s.rstrip()
s = cgen(s, **kwds)
if s:
result.append(s)
return '\n'.join(result)

For the case of a single argument, the result is equivalent to the
existing implementation:

# already checked by current mcgen()
if s.startswith('\n'):
s = s[1:]
# skipped for a single argument
if i != last:
s = s.rstrip()
s = cgen(s, **kwds)
if s:
result.append(s)
# result has zero or a single element, no '\n' added
return '\n'.join(result)

Paolo