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: > +a
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.
[PATCH v3 15/19] scripts/qapi: generate high-level Rust bindings
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 diff --git a/meson.build b/meson.build index eb074918193..bda5180c436 100644 --- a/meson.build +++ b/meson.build @@ -3485,11 +3485,13 @@ qapi_gen_depends = [ meson.current_source_dir() / 'scripts/qapi/__init__.py', meson.current_source_dir() / 'scripts/qapi/introspect.py', meson.current_source_dir() / 'scripts/qapi/main.py', meson.current_source_dir() / 'scripts/qapi/parser.py', + meson.current_source_dir() / 'scripts/qapi/rs_types.py', meson.current_source_dir() / 'scripts/qapi/schema.py', meson.current_source_dir() / 'scripts/qapi/source.py', meson.current_source_dir() / 'scripts/qapi/types.py', meson.current_source_dir() / 'scripts/qapi/visit.py', - meson.current_source_dir() / 'scripts/qapi-gen.py' + meson.current_source_dir() / 'scripts/qapi-gen.py', + meson.current_source_dir() / 'scripts/qapi/rs.py', ] tracetool = [ diff --git a/scripts/qapi/backend.py b/scripts/qapi/backend.py index 49ae6ecdd33..8023acce0d6 100644 --- a/scripts/qapi/backend.py +++ b/scripts/qapi/backend.py @@ -7,6 +7,7 @@ from .events import gen_events from .features import gen_features from .introspect import gen_introspect +from .rs_types import gen_rs_types from .schema import QAPISchema from .types import gen_types from .visit import gen_visit @@ -63,3 +64,27 @@ def generate(self, gen_commands(schema, output_dir, prefix, gen_tracing) gen_events(schema, output_dir, prefix) gen_introspect(schema, output_dir, prefix, unmask) + + +class QAPIRsBackend(QAPIBackend): +# pylint: disable=too-few-public-methods + +def generate(self, + schema: QAPISchema, + output_dir: str, + prefix: str, + unmask: bool, + builtins: bool, + gen_tracing: bool) -> None: +""" +Generate Rust code for the given schema into the target directory. + +:param schema_file: The primary QAPI schema file. +:param output_dir: The output directory to store generated code. +:param prefix: Optional C-code prefix for symbol names. +:param unmask: Expose non-ABI names through introspection? +:param builtins: Generate code for built-in types? + +:raise QAPIError: On failures. +""" +gen_rs_types(schema, output_dir, prefix) diff --git a/scripts/qapi/common.py b/scripts/qapi/common.py index 44229c2e366..67a4b11e4f3 100644 --- a/scripts/qapi/common.py +++ b/scripts/qapi/common.py @@ -64,6 +64,13 @@ def camel_to_upper(value: str) -> str: return ret.upper() +def camel_to_lower(value: str) -> str: +""" +Converts CamelCase to camel_case. +""" +return camel_to_upper(value).lower() + + def c_enum_const(type_name: str, const_name: str, prefix: Optional[str] = None) -> str: @@ -129,6 +136,48 @@ def c_name(name: str, protect: bool = True) -> str: return name +def rs_name(name: str) -> str: +""" +Map @name to a valid,
