From: Markus Armbruster <armbru@redhat.com>
To: Paolo Bonzini <pbonzini@redhat.com>
Cc: qemu-devel@nongnu.org, armbru@redhat.com,
"Marc-André Lureau" <marcandre.lureau@redhat.com>,
qemu-rust@nongnu.org
Subject: Re: [PATCH v2 12/16] scripts/qapi: generate high-level Rust bindings
Date: Tue, 03 Mar 2026 10:19:13 +0100 [thread overview]
Message-ID: <87seahnucu.fsf@pond.sub.org> (raw)
In-Reply-To: <20260108131043.490084-13-pbonzini@redhat.com> (Paolo Bonzini's message of "Thu, 8 Jan 2026 14:10:39 +0100")
Paolo Bonzini <pbonzini@redhat.com> writes:
> From: Marc-André Lureau <marcandre.lureau@redhat.com>
>
> 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<T>
>
> - lists are represented as Vec<T>
>
> - 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).
>
> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
> Link: https://lore.kernel.org/r/20210907121943.3498701-21-marcandre.lureau@redhat.com
> [Paolo: rewrite conversion of schema types to Rust types]
> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
Let's look at the generated Rust. I'm going to ignore formatting issues
like blank lines. My test inputs are adapted from
tests/qapi-schema/qapi-schema-test.json.
= Boilerplate and built-in stuff =
The qapi-types.rs generated for an empty schema is basically empty:
// @generated by qapi-gen, DO NOT EDIT
//!
//! Schema-defined QAPI types
//!
//! Copyright (c) 2025 Red Hat, Inc.
//!
//! This work is licensed under the terms of the GNU LGPL, version 2.1 or
//! later. See the COPYING.LIB file in the top-level directory.
#![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;
Okay.
Note we generate nothing for the built-in types. For C, we generate
qapi-builtin-types.[ch], which provides lists of built-in types, and
enum QType. Rust gives us lists for free, and QType we don't need right
now. Good.
= Enum types =
Input:
{ 'enum': 'TestIfEnum',
'data': [ 'foo', { 'name' : 'bar', 'if': 'TEST_IF_ENUM_MEMBER' } ],
'if': 'TEST_IF_UNION' }
Output without the boilerplate:
#[cfg(TEST_IF_UNION)]
#[derive(Copy, Clone, Debug, PartialEq)]
Ignoring such lines for now.
#[repr(u32)]
As explained in the commit message.
#[derive(common::TryInto)]
Ignoring these, too.
pub enum TestIfEnum {
FOO,
#[cfg(TEST_IF_ENUM_MEMBER)]
BAR,
}
The obvious part :)
#[cfg(TEST_IF_UNION)]
impl Default for TestIfEnum {
#[inline]
fn default() -> TestIfEnum {
Self::FOO
}
}
This specifies the enum type's default value. Hmm... Modified input:
{ 'enum': 'TestIfEnum',
'data': [ { 'name' : 'bar', 'if': 'TEST_IF_ENUM_MEMBER' }, 'foo' ],
'if': 'TEST_IF_UNION' }
Output:
#[cfg(TEST_IF_UNION)]
#[derive(Copy, Clone, Debug, PartialEq)]
#[repr(u32)]
#[derive(common::TryInto)]
pub enum TestIfEnum {
#[cfg(TEST_IF_ENUM_MEMBER)]
BAR,
FOO,
}
#[cfg(TEST_IF_UNION)]
impl Default for TestIfEnum {
#[inline]
fn default() -> TestIfEnum {
Self::BAR
}
}
If TEST_IF_ENUM_MEMBER is off, member BAR does not exist. default()
uses it anyway. Bug?
Not tested: enum definition's optional 'prefix' member, because Rust
generation has no need for it and does not use it. Good.
= Object types =
Input:
{ 'struct': 'UserDefOne',
'base': 'UserDefZero',
'data': { 'string': 'str',
'*number': 'number' } }
{ 'struct': 'UserDefZero',
'data': { 'integer': 'int' } }
Output:
#[derive(Clone, Debug, PartialEq)]
Ignoring such lines for now.
pub struct UserDefOne {
// Members inherited:
pub integer: i64,
// Own members:
pub string: String,
pub number: Option<f64>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct UserDefZero {
pub integer: i64,
}
Obvious enough.
More input:
{ 'union': 'TestIfUnion',
'base': { 'type': 'TestIfEnum' },
'discriminator': 'type',
'data': { 'foo': 'UserDefOne',
'bar': { 'type': 'UserDefZero', 'if': 'TEST_IF_ENUM_MEMBER'} },
'if': { 'all': ['TEST_IF_UNION', 'TEST_IF_STRUCT'] } }
Output:
#[cfg(all(TEST_IF_UNION, TEST_IF_STRUCT))]
#[derive(Clone, Debug, PartialEq)]
pub enum TestIfUnionVariant {
Possible trouble if somebody defines a QAPI type name ending with
'Variant'.
Code generated for a QAPI schema name named 'Frob' typically defines a
'Frob'. Sometimes, we transform 'Frob' in (hopefully obvious) ways, say
to adhere to naming rules. For instance, QAPI enum member 'foo' of
'TestIfEnum' becomes FOO in generated Rust. This risks naming
collisions in generated code. Hasn't been much of a problem in
practice.
We also need names for helpers. These we commonly derive from QAPI
schema names, and we reserve names for the purpose:
* Names beginning with 'q_'
* Type names ending with 'List'
* Member name 'u' and names starting with 'has_'
Source: docs/devel/qapi-code-gen.rst section "Naming rules and reserved
names".
We could reserve names ending with 'Variant'. Or we use a 'q_' prefix
here.
Foo(UserDefOne),
#[cfg(TEST_IF_ENUM_MEMBER)]
Bar(UserDefZero),
}
#[cfg(all(TEST_IF_UNION, TEST_IF_STRUCT))]
impl From<&TestIfUnionVariant> for TestIfEnum {
fn from(e: &TestIfUnionVariant) -> Self {
match e {
TestIfUnionVariant::Foo(_) => Self::FOO,
#[cfg(TEST_IF_ENUM_MEMBER)]
TestIfUnionVariant::Bar(_) => Self::BAR,
}
}
}
#[cfg(all(TEST_IF_UNION, TEST_IF_STRUCT))]
#[derive(Clone, Debug, PartialEq)]
pub struct TestIfUnion {
pub u: TestIfUnionVariant,
}
Why do we need to wrap TestIfUnionVariant in a struct? Hmm, we need it
in case we have more common members. Modified input to demonstrate:
{ 'union': 'TestIfUnion',
'base': { 'type': 'TestIfEnum', 'common': 'int' },
'discriminator': 'type',
'data': { 'foo': 'UserDefOne',
'bar': { 'type': 'UserDefZero', 'if': 'TEST_IF_ENUM_MEMBER'} },
'if': { 'all': ['TEST_IF_UNION', 'TEST_IF_STRUCT'] } }
Output:
#[cfg(all(TEST_IF_UNION, TEST_IF_STRUCT))]
#[derive(Clone, Debug, PartialEq)]
pub enum TestIfUnionVariant {
Foo(UserDefOne),
#[cfg(TEST_IF_ENUM_MEMBER)]
Bar(UserDefZero),
}
#[cfg(all(TEST_IF_UNION, TEST_IF_STRUCT))]
impl From<&TestIfUnionVariant> for TestIfEnum {
fn from(e: &TestIfUnionVariant) -> Self {
match e {
TestIfUnionVariant::Foo(_) => Self::FOO,
#[cfg(TEST_IF_ENUM_MEMBER)]
TestIfUnionVariant::Bar(_) => Self::BAR,
}
}
}
#[cfg(all(TEST_IF_UNION, TEST_IF_STRUCT))]
#[derive(Clone, Debug, PartialEq)]
pub struct TestIfUnion {
pub common: i64,
pub u: TestIfUnionVariant,
}
Okay.
= Array types =
Input:
{ 'struct': 'ArrayStruct',
'data': { 'integer': ['int'],
'*any': ['any'] } }
Output:
#[derive(Clone, Debug, PartialEq)]
pub struct ArrayStruct {
pub integer: Vec<i64>,
pub any: Option<Vec<QObject>>,
}
Okay.
= Alternate types =
Input:
{ 'alternate': 'TestIfAlternate',
'data': { 'foo': 'int',
'bar': { 'type': 'UserDefZero', 'if': 'TEST_IF_ALT_MEMBER'} },
'if': { 'all': ['TEST_IF_ALT', 'TEST_IF_STRUCT'] } }
Output:
#[cfg(all(TEST_IF_ALT, TEST_IF_STRUCT))]
#[derive(Clone, Debug, PartialEq)]
pub enum TestIfAlternate {
Foo(i64),
#[cfg(TEST_IF_ALT_MEMBER)]
Bar(UserDefZero),
}
Doesn't use QType for the tag like C does. Okay.
next prev parent reply other threads:[~2026-03-03 9:39 UTC|newest]
Thread overview: 55+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-01-08 13:10 [PATCH v2 00/16] rust: QObject and QAPI bindings Paolo Bonzini
2026-01-08 13:10 ` [PATCH v2 01/16] rust/qobject: add basic bindings Paolo Bonzini
2026-02-24 10:03 ` Markus Armbruster
2026-02-24 10:35 ` Paolo Bonzini
2026-02-24 13:33 ` Markus Armbruster
2026-02-25 8:05 ` Paolo Bonzini
2026-01-08 13:10 ` [PATCH v2 02/16] subprojects: add serde Paolo Bonzini
2026-01-08 13:10 ` [PATCH v2 03/16] rust/qobject: add Serialize implementation Paolo Bonzini
2026-02-24 10:29 ` Markus Armbruster
2026-02-24 10:48 ` Paolo Bonzini
2026-02-24 13:41 ` Markus Armbruster
2026-01-08 13:10 ` [PATCH v2 04/16] rust/qobject: add Serializer (to_qobject) implementation Paolo Bonzini
2026-01-08 13:10 ` [PATCH v2 05/16] rust/qobject: add Deserialize implementation Paolo Bonzini
2026-01-08 13:10 ` [PATCH v2 06/16] rust/qobject: add Deserializer (from_qobject) implementation Paolo Bonzini
2026-01-08 13:10 ` [PATCH v2 07/16] rust/qobject: add from/to JSON bindings for QObject Paolo Bonzini
2026-01-15 13:17 ` Zhao Liu
2026-01-08 13:10 ` [PATCH v2 08/16] rust/qobject: add Display/Debug Paolo Bonzini
2026-01-15 13:19 ` Zhao Liu
2026-01-08 13:10 ` [PATCH v2 09/16] scripts/qapi: add QAPISchemaIfCond.rsgen() Paolo Bonzini
2026-01-19 6:58 ` Zhao Liu
2026-02-25 6:48 ` Markus Armbruster
2026-02-25 7:53 ` Paolo Bonzini
2026-01-08 13:10 ` [PATCH v2 10/16] scripts/qapi: add QAPISchemaType.is_predefined Paolo Bonzini
2026-02-25 7:33 ` Markus Armbruster
2026-02-25 8:01 ` Paolo Bonzini
2026-02-25 8:44 ` Markus Armbruster
2026-02-26 14:12 ` Paolo Bonzini
2026-01-08 13:10 ` [PATCH v2 11/16] scripts/qapi: pull c_name from camel_to_upper to caller Paolo Bonzini
2026-01-19 7:05 ` Zhao Liu
2026-02-25 8:32 ` Markus Armbruster
2026-01-08 13:10 ` [PATCH v2 12/16] scripts/qapi: generate high-level Rust bindings Paolo Bonzini
2026-02-23 12:36 ` Markus Armbruster
2026-02-23 16:11 ` Paolo Bonzini
2026-02-24 13:46 ` Markus Armbruster
2026-02-25 14:39 ` Markus Armbruster
2026-03-03 10:00 ` Paolo Bonzini
2026-03-03 12:31 ` Markus Armbruster
2026-03-03 15:55 ` Paolo Bonzini
2026-03-04 8:09 ` Markus Armbruster
2026-03-03 9:19 ` Markus Armbruster [this message]
2026-03-03 13:17 ` Paolo Bonzini
2026-01-08 13:10 ` [PATCH v2 13/16] scripts/rustc_args: add --no-strict-cfg Paolo Bonzini
2026-01-08 13:10 ` [PATCH v2 14/16] rust/util: build QAPI types Paolo Bonzini
2026-01-08 13:10 ` [PATCH v2 15/16] scripts/qapi: add serde attributes Paolo Bonzini
2026-01-08 13:10 ` [PATCH v2 16/16] rust/tests: QAPI integration tests Paolo Bonzini
2026-02-17 8:10 ` [PATCH v2 00/16] rust: QObject and QAPI bindings Paolo Bonzini
2026-02-19 13:39 ` Markus Armbruster
2026-02-19 16:28 ` Paolo Bonzini
2026-02-23 9:53 ` Daniel P. Berrangé
2026-02-23 15:54 ` Paolo Bonzini
2026-02-23 16:24 ` Daniel P. Berrangé
2026-02-23 19:03 ` Paolo Bonzini
2026-02-24 14:06 ` Markus Armbruster
2026-02-24 17:28 ` Paolo Bonzini
2026-02-26 12:42 ` Markus Armbruster
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=87seahnucu.fsf@pond.sub.org \
--to=armbru@redhat.com \
--cc=marcandre.lureau@redhat.com \
--cc=pbonzini@redhat.com \
--cc=qemu-devel@nongnu.org \
--cc=qemu-rust@nongnu.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox