On 3/3/26 10:19, Markus Armbruster wrote:
Paolo Bonzini <[email protected]> writes:

From: Marc-AndrĂ© Lureau <[email protected]>

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 <[email protected]>
Link: 
https://lore.kernel.org/r/[email protected]
[Paolo: rewrite conversion of schema types to Rust types]
Signed-off-by: Paolo Bonzini <[email protected]>

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?

Nice catch. I can make it produce the first element (as in C) with a tiny bit of cheating

    unsafe { ::std::mem::transmute_copy(&0u32) }

or some reliance on the optimizer

    0.try_into().unwrap()


However, I'm inclined to handle this generically. For example, an enum where the first element is configuration dependent could be usable only if optional. And then the Rust code can skip the "impl Default". Let me check how often it would trigger.

Paolo


Reply via email to