Re: [PATCH v3 15/19] scripts/qapi: generate high-level Rust bindings
On Thu, Jun 11, 2026 at 5:56 PM Zhao Liu wrote: > > On Tue, May 26, 2026 at 07:56:14PM +0200, Paolo Bonzini wrote: > > Date: Tue, 26 May 2026 19:56:14 +0200 > > From: Paolo Bonzini > > Subject: [PATCH v3 15/19] scripts/qapi: generate high-level Rust bindings > > X-Mailer: git-send-email 2.54.0 > > > > From: Marc-André Lureau > > > > Generate high-level native Rust declarations for the QAPI types. > > > > - char* is mapped to String, scalars to there corresponding Rust types > > > > - enums use #[repr(u32)] and can be transmuted to their C counterparts > > > > - has_foo/foo members are mapped to Option > > > > - lists are represented as Vec > > > > - structures map fields 1:1 to Rust > > > > - alternate are represented as Rust enum, each variant being a 1-element > > tuple > > > > - unions are represented in a similar way as in C: a struct S with a "u" > > member (since S may have extra 'base' fields). The discriminant > > isn't a member of S, since Rust enum already include it, but it can be > > recovered with "mystruct.u.into()" > > > > Anything that includes a recursive struct puts it in a Box. Lists are > > not considered recursive, because Vec breaks the recursion (it's possible > > to construct an object containing an empty Vec of its own type). > > > > Given the experimental nature of Rust, and the incompleteness of the > > backend (it lacks commands and events), QAPIRsBackend is not modular > > and is not built together with the C and trace-event files. It can > > be used by specifying "-B qapi.backend.QAPIRsBackend" on the qapi-gen > > command line. > > > > Signed-off-by: Marc-André Lureau > > Link: > > https://lore.kernel.org/r/[email protected] > > [Paolo: rewrite conversion of leaf types] > > Signed-off-by: Paolo Bonzini > > --- > > meson.build | 4 +- > > scripts/qapi/backend.py | 25 +++ > > scripts/qapi/common.py | 49 ++ > > scripts/qapi/rs.py | 50 ++ > > scripts/qapi/rs_types.py | 372 +++ > > scripts/qapi/schema.py | 59 +-- > > 6 files changed, 540 insertions(+), 19 deletions(-) > > create mode 100644 scripts/qapi/rs.py > > create mode 100644 scripts/qapi/rs_types.py > > [snip] > > > +def rs_name(name: str) -> str: > > +""" > > +Map @name to a valid, possibly raw Rust identifier. > > +""" > > +name = re.sub(r'[^A-Za-z0-9_]', '_', name) > > +if name[0].isnumeric(): > > +name = '_' + name > > +# based from the list: > > +# https://doc.rust-lang.org/reference/keywords.html > > +if name in ('Self', 'abstract', 'as', 'async', > > > +'await', 'become', 'box', 'break', > > +'const', 'continue', 'crate', 'do', > ^ > > +'dyn', 'else', 'enum', 'extern', > > +'false', 'final', 'fn', 'for', > > +'if', 'impl', 'in', 'let', > > +'loop', 'macro', 'match', 'mod', > > +'move', 'mut', 'override', 'priv', > > +'pub', 'ref', 'return', 'self', > > > +'static', 'struct', 'super', 'trait', > ^ > > +'true', 'try', 'type', 'typeof', > > +'union', 'unsafe', 'unsized', 'use', > > +'virtual', 'where', 'while', 'yield'): > > +name = 'r#' + name > > EMM, r#self, r#Self, r#crate and r#super are "RESERVED_RAW_IDENTIFIER", > and "It is an error to use the RESERVED_RAW_IDENTIFIER token". [*] > > [*]: > https://doc.rust-lang.org/reference/identifiers.html#grammar-RESERVED_RAW_IDENTIFIER > > We can't use 'q_' prefix for these special case, or what about > name + '_'? This is what bindgen does also: https://github.com/rust-lang/rust-bindgen/blob/2048231f45c87cd65d7891f55090aac81cc0d66b/bindgen-tests/tests/headers/keywords.h becomes: https://github.com/rust-lang/rust-bindgen/blob/2048231f45c87cd65d7891f55090aac81cc0d66b/bindgen-tests/tests/expectations/tests/keywords.rs > > e.g., > > if name in ('self', 'Self', 'crate', 'super'): >name = name + '_' > elif name in (...) >name = 'r#' + name > > > + > > +return name > > + > > [snip] > > > @@ -502,6 +515,9 @@ def is_implicit(self) -> bool: > > def c_type(self) -> str: > > return c_name(self.name) + POINTER_SUFFIX > > > > +def rs_type(self) -> str: > > Only a nit, it's better to check _checked before accessing element_type > (just refer Markus' suggestion in v2) > > assert self._checked > > > +return 'Vec<%s>' % self.element_type.rs_type() > > + > > Regards, > Zhao > > -- Manos Pitsidianakis Emulation and Virtualization Engineer at Linaro Ltd
Re: [PATCH v3 15/19] scripts/qapi: generate high-level Rust bindings
On Tue, May 26, 2026 at 07:56:14PM +0200, Paolo Bonzini wrote: > Date: Tue, 26 May 2026 19:56:14 +0200 > From: Paolo Bonzini > Subject: [PATCH v3 15/19] scripts/qapi: generate high-level Rust bindings > X-Mailer: git-send-email 2.54.0 > > From: Marc-André Lureau > > Generate high-level native Rust declarations for the QAPI types. > > - char* is mapped to String, scalars to there corresponding Rust types > > - enums use #[repr(u32)] and can be transmuted to their C counterparts > > - has_foo/foo members are mapped to Option > > - lists are represented as Vec > > - structures map fields 1:1 to Rust > > - alternate are represented as Rust enum, each variant being a 1-element > tuple > > - unions are represented in a similar way as in C: a struct S with a "u" > member (since S may have extra 'base' fields). The discriminant > isn't a member of S, since Rust enum already include it, but it can be > recovered with "mystruct.u.into()" > > Anything that includes a recursive struct puts it in a Box. Lists are > not considered recursive, because Vec breaks the recursion (it's possible > to construct an object containing an empty Vec of its own type). > > Given the experimental nature of Rust, and the incompleteness of the > backend (it lacks commands and events), QAPIRsBackend is not modular > and is not built together with the C and trace-event files. It can > be used by specifying "-B qapi.backend.QAPIRsBackend" on the qapi-gen > command line. > > Signed-off-by: Marc-André Lureau > Link: > https://lore.kernel.org/r/[email protected] > [Paolo: rewrite conversion of leaf types] > Signed-off-by: Paolo Bonzini > --- > meson.build | 4 +- > scripts/qapi/backend.py | 25 +++ > scripts/qapi/common.py | 49 ++ > scripts/qapi/rs.py | 50 ++ > scripts/qapi/rs_types.py | 372 +++ > scripts/qapi/schema.py | 59 +-- > 6 files changed, 540 insertions(+), 19 deletions(-) > create mode 100644 scripts/qapi/rs.py > create mode 100644 scripts/qapi/rs_types.py [snip] > +def rs_name(name: str) -> str: > +""" > +Map @name to a valid, possibly raw Rust identifier. > +""" > +name = re.sub(r'[^A-Za-z0-9_]', '_', name) > +if name[0].isnumeric(): > +name = '_' + name > +# based from the list: > +# https://doc.rust-lang.org/reference/keywords.html > +if name in ('Self', 'abstract', 'as', 'async', > +'await', 'become', 'box', 'break', > +'const', 'continue', 'crate', 'do', ^ > +'dyn', 'else', 'enum', 'extern', > +'false', 'final', 'fn', 'for', > +'if', 'impl', 'in', 'let', > +'loop', 'macro', 'match', 'mod', > +'move', 'mut', 'override', 'priv', > +'pub', 'ref', 'return', 'self', > +'static', 'struct', 'super', 'trait', ^ > +'true', 'try', 'type', 'typeof', > +'union', 'unsafe', 'unsized', 'use', > +'virtual', 'where', 'while', 'yield'): > +name = 'r#' + name EMM, r#self, r#Self, r#crate and r#super are "RESERVED_RAW_IDENTIFIER", and "It is an error to use the RESERVED_RAW_IDENTIFIER token". [*] [*]: https://doc.rust-lang.org/reference/identifiers.html#grammar-RESERVED_RAW_IDENTIFIER We can't use 'q_' prefix for these special case, or what about name + '_'? e.g., if name in ('self', 'Self', 'crate', 'super'): name = name + '_' elif name in (...) name = 'r#' + name > + > +return name > + [snip] > @@ -502,6 +515,9 @@ def is_implicit(self) -> bool: > def c_type(self) -> str: > return c_name(self.name) + POINTER_SUFFIX > > +def rs_type(self) -> str: Only a nit, it's better to check _checked before accessing element_type (just refer Markus' suggestion in v2) assert self._checked > +return 'Vec<%s>' % self.element_type.rs_type() > + Regards, Zhao
Re: [PATCH v3 15/19] scripts/qapi: generate high-level Rust bindings
On Tue, May 26, 2026 at 07:56:14PM +0200, Paolo Bonzini wrote: > Date: Tue, 26 May 2026 19:56:14 +0200 > From: Paolo Bonzini > Subject: [PATCH v3 15/19] scripts/qapi: generate high-level Rust bindings > X-Mailer: git-send-email 2.54.0 > > From: Marc-André Lureau > > Generate high-level native Rust declarations for the QAPI types. > > - char* is mapped to String, scalars to there corresponding Rust types > > - enums use #[repr(u32)] and can be transmuted to their C counterparts > > - has_foo/foo members are mapped to Option > > - lists are represented as Vec > > - structures map fields 1:1 to Rust > > - alternate are represented as Rust enum, each variant being a 1-element > tuple > > - unions are represented in a similar way as in C: a struct S with a "u" > member (since S may have extra 'base' fields). The discriminant > isn't a member of S, since Rust enum already include it, but it can be > recovered with "mystruct.u.into()" > > Anything that includes a recursive struct puts it in a Box. Lists are > not considered recursive, because Vec breaks the recursion (it's possible > to construct an object containing an empty Vec of its own type). > > Given the experimental nature of Rust, and the incompleteness of the > backend (it lacks commands and events), QAPIRsBackend is not modular > and is not built together with the C and trace-event files. It can > be used by specifying "-B qapi.backend.QAPIRsBackend" on the qapi-gen > command line. > > Signed-off-by: Marc-André Lureau > Link: > https://lore.kernel.org/r/[email protected] > [Paolo: rewrite conversion of leaf types] > Signed-off-by: Paolo Bonzini > --- > meson.build | 4 +- > scripts/qapi/backend.py | 25 +++ > scripts/qapi/common.py | 49 ++ > scripts/qapi/rs.py | 50 ++ > scripts/qapi/rs_types.py | 372 +++ > scripts/qapi/schema.py | 59 +-- > 6 files changed, 540 insertions(+), 19 deletions(-) > create mode 100644 scripts/qapi/rs.py > create mode 100644 scripts/qapi/rs_types.py Revisiting the v2 discussion, I think maybe it's still possible to use visit_module to identify predefined cases (in schema builtn module) instead of is_predefined? (I guess that's what Markus wanted in v2, hopefully... and apologies if this is a bit too verbose. I'm learning QAPI code so I want to explain my undersatnding in as much details as possible (to ensure "IIUC") :). ) > +class QAPISchemaGenRsTypeVisitor(QAPISchemaRsVisitor): > +_schema: Optional[QAPISchema] > + > +def __init__(self, prefix: str) -> None: > +super().__init__(prefix, 'qapi-types', > + 'Schema-defined QAPI types', __doc__) > + > +def visit_begin(self, schema: QAPISchema) -> None: > +self._schema = schema > +objects_seen.add(schema.the_empty_object_type.name) > + > +self._gen.preamble_add( > +mcgen(''' > +#![allow(unexpected_cfgs)] > +#![allow(non_camel_case_types)] > +#![allow(clippy::empty_structs_with_brackets)] > +#![allow(clippy::large_enum_variant)] > +#![allow(clippy::pub_underscore_fields)] > + > +// Because QAPI structs can contain float, for simplicity we never > +// derive Eq. Clippy however would complain for those structs > +// that *could* be Eq too. > +#![allow(clippy::derive_partial_eq_without_eq)] > + > +use util::qobject::QObject; > +''')) IIUC, though there's only a single QAPIGenRs, but we can just use visit_module to cache a flag and don't need to skip something like C did: def visit_module(self, name: str) -> None: self._in_builtin_module = QAPISchemaModule.is_builtin_module(name) builtin module is created by schema and is maintained in _module_dict (in QAPISchema.__init__()). When QAPISchema iterates through _module_dict, it will always visit he builtin module and once it enters builtin module, visit_module() will be called (QAPISchemaModule.visit()). With _in_builtin_module flag, then we just need to "proof" it has the same effect as is_predefined() method. > +def visit_object_type(self, > + name: str, > + info: Optional[QAPISourceInfo], > + ifcond: QAPISchemaIfCond, > + features: List[QAPISchemaFeature], > + base: Optional[QAPISchemaObjectType], > + members: List[QAPISchemaObjectTypeMember], > + branches: Optional[QAPISchemaVariants]) -> None: > +assert self._schema is not None (if we use self._in_builtin_module, this assertion can go away.) > +if self._schema.is_predefined(name) or name.startswith('q_'): This is called by QAPISchemaObjectType not QAPISchemaBuiltinType, and QType is QPAISchemaEnumType. Only q_empty could hit is_predefined(), and q_empty has been included into builtin module (in QAPISchema._def_predefineds()). And a good thing is
Re: [PATCH v3 15/19] scripts/qapi: generate high-level Rust bindings
Hi Paolo
On Tue, May 26, 2026 at 9:58 PM Paolo Bonzini wrote:
> +def gen_rs_members(members: List[QAPISchemaObjectTypeMember],
> + exclude: Optional[List[str]] = None) -> List[str]:
> +exclude = exclude or []
> +return [f"{m.ifcond.rsgen()} {rs_name(camel_to_lower(m.name))}"
> +for m in members if m.name not in exclude]
> +
> +
Actually dead code now.
