Re: [PATCH v3 15/19] scripts/qapi: generate high-level Rust bindings

2026-06-11 Thread Manos Pitsidianakis
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

2026-06-11 Thread Zhao Liu
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

2026-06-04 Thread Zhao Liu
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

2026-05-26 Thread Marc-André Lureau
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

2026-05-26 Thread Paolo Bonzini
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,