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 are simply aliased from FFI
- has_foo/foo members are mapped to Option<T>
- lists are represented as Vec<T>
- structures have Rust versions, with To/From FFI conversions
- alternate are represented as Rust enum
- unions are represented in a similar way as in C: a struct S with a "u"
member (since S may have extra 'base' fields). However, the discriminant
isn't a member of S, since Rust enum already include it.
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Link: https://lore.kernel.org/r/20210907121943.3498701-21-marcandre.lureau@redhat.com
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
meson.build | 4 +-
scripts/qapi/backend.py | 27 ++-
scripts/qapi/main.py | 4 +-
scripts/qapi/rs.py | 181 +++++++++++++++++++
scripts/qapi/rs_types.py | 365 +++++++++++++++++++++++++++++++++++++++
5 files changed, 577 insertions(+), 4 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 afaefa01722..ce914217c52 100644
--- a/meson.build
+++ b/meson.build
@@ -3571,12 +3571,14 @@ 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/features.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..305b62b514c 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
@@ -36,7 +37,7 @@ def generate(self,
"""
-class QAPICBackend(QAPIBackend):
+class QAPICodeBackend(QAPIBackend):
# pylint: disable=too-few-public-methods
def generate(self,
@@ -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, builtins)
diff --git a/scripts/qapi/main.py b/scripts/qapi/main.py
index 0e2a6ae3f07..4ad75e213f5 100644
--- a/scripts/qapi/main.py
+++ b/scripts/qapi/main.py
@@ -12,7 +12,7 @@
import sys
from typing import Optional
-from .backend import QAPIBackend, QAPICBackend
+from .backend import QAPIBackend, QAPICodeBackend
from .common import must_match
from .error import QAPIError
from .schema import QAPISchema
@@ -27,7 +27,7 @@ def invalid_prefix_char(prefix: str) -> Optional[str]:
def create_backend(path: str) -> QAPIBackend:
if path is None:
- return QAPICBackend()
+ return QAPICodeBackend()
module_path, dot, class_name = path.rpartition('.')
if not dot:
diff --git a/scripts/qapi/rs.py b/scripts/qapi/rs.py
new file mode 100644
index 00000000000..2a9bbcb9f54
--- /dev/null
+++ b/scripts/qapi/rs.py
@@ -0,0 +1,181 @@
+# This work is licensed under the terms of the GNU GPL, version 2.
+# See the COPYING file in the top-level directory.
+"""
+QAPI Rust generator
+"""
+
+import os
+import re
+import subprocess
+from typing import NamedTuple, Optional
+
+from .common import POINTER_SUFFIX
+from .gen import QAPIGen
+from .schema import QAPISchemaModule, QAPISchemaVisitor
+
+
+# see to_upper_case()/to_lower_case() below
+snake_case = re.compile(r'((?<=[a-z0-9])[A-Z]|(?!^)[A-Z](?=[a-z]))')
+
+
+rs_name_trans = str.maketrans('.-', '__')
+
+
+# Map @name to a valid Rust identifier.
+# If @protect, avoid returning certain ticklish identifiers (like
+# keywords) by prepending raw identifier prefix 'r#'.
+def rs_name(name: str, protect: bool = True) -> str:
+ name = name.translate(rs_name_trans)
+ if name[0].isnumeric():
+ name = '_' + name
+ if not protect:
+ return 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
+ # avoid some clashes with the standard library
+ if name in ('String',):
+ name = 'Qapi' + name
+
+ return name
+
+
+def rs_type(c_type: str,
+ qapi_ns: str = 'qapi::',
+ optional: bool = False,
+ box: bool = False) -> str:
+ (is_pointer, _, is_list, c_type) = rs_ctype_parse(c_type)
+ to_rs = {
+ 'QNull': '()',
+ 'QObject': 'QObject',
+ 'any': 'QObject',
+ 'bool': 'bool',
+ 'char': 'i8',
+ 'double': 'f64',
+ 'int': 'i64',
+ 'int16': 'i16',
+ 'int16_t': 'i16',
+ 'int32': 'i32',
+ 'int32_t': 'i32',
+ 'int64': 'i64',
+ 'int64_t': 'i64',
+ 'int8': 'i8',
+ 'int8_t': 'i8',
+ 'number': 'f64',
+ 'size': 'u64',
+ 'str': 'String',
+ 'uint16': 'u16',
+ 'uint16_t': 'u16',
+ 'uint32': 'u32',
+ 'uint32_t': 'u32',
+ 'uint64': 'u64',
+ 'uint64_t': 'u64',
+ 'uint8': 'u8',
+ 'uint8_t': 'u8',
+ 'String': 'QapiString',
+ }
+ if is_pointer:
+ to_rs.update({
+ 'char': 'String',
+ })
+
+ if is_list:
+ c_type = c_type[:-4]
+
+ ret = to_rs.get(c_type, qapi_ns + c_type)
+ if is_list:
+ ret = 'Vec<%s>' % ret
+ elif is_pointer and c_type not in to_rs and box:
+ ret = 'Box<%s>' % ret
+ if optional:
+ ret = 'Option<%s>' % ret
+ return ret
+
+
+class CType(NamedTuple):
+ is_pointer: bool
+ is_const: bool
+ is_list: bool
+ c_type: str
+
+
+def rs_ctype_parse(c_type: str) -> CType:
+ is_pointer = False
+ if c_type.endswith(POINTER_SUFFIX):
+ is_pointer = True
+ c_type = c_type[:-len(POINTER_SUFFIX)]
+ is_list = c_type.endswith('List')
+ is_const = False
+ if c_type.startswith('const '):
+ is_const = True
+ c_type = c_type[6:]
+
+ c_type = rs_name(c_type)
+ return CType(is_pointer, is_const, is_list, c_type)
+
+
+def to_camel_case(value: str) -> str:
+ # special case for last enum value
+ if value == '_MAX':
+ return value
+ raw_id = False
+ if value.startswith('r#'):
+ raw_id = True
+ value = value[2:]
+ value = ''.join('_' + word if word[0].isdigit()
+ else word[:1].upper() + word[1:]
+ for word in filter(None, re.split("[-_]+", value)))
+ if raw_id:
+ return 'r#' + value
+ return value
+
+
+def to_upper_case(value: str) -> str:
+ return snake_case.sub(r'_\1', value).upper()
+
+
+def to_lower_case(value: str) -> str:
+ return snake_case.sub(r'_\1', value).lower()
+
+
+class QAPIGenRs(QAPIGen):
+ pass
+
+
+class QAPISchemaRsVisitor(QAPISchemaVisitor):
+
+ def __init__(self, prefix: str, what: str):
+ super().__init__()
+ self._prefix = prefix
+ self._what = what
+ self._gen = QAPIGenRs(self._prefix + self._what + '.rs')
+ self._main_module: Optional[str] = None
+
+ def visit_module(self, name: Optional[str]) -> None:
+ if name is None:
+ return
+ if QAPISchemaModule.is_user_module(name):
+ if self._main_module is None:
+ self._main_module = name
+
+ def write(self, output_dir: str) -> None:
+ self._gen.write(output_dir)
+
+ pathname = os.path.join(output_dir, self._gen.fname)
+ try:
+ subprocess.check_call(['rustfmt', pathname])
+ except FileNotFoundError:
+ pass
diff --git a/scripts/qapi/rs_types.py b/scripts/qapi/rs_types.py
new file mode 100644
index 00000000000..436adcf5be6
--- /dev/null
+++ b/scripts/qapi/rs_types.py
@@ -0,0 +1,365 @@
+# This work is licensed under the terms of the GNU GPL, version 2.
+# See the COPYING file in the top-level directory.
+"""
+QAPI Rust types generator
+"""
+
+from typing import List, Optional, Set
+
+from .common import mcgen
+from .rs import (
+ QAPISchemaRsVisitor,
+ rs_name,
+ rs_type,
+ to_camel_case,
+ to_lower_case,
+ to_upper_case,
+)
+from .schema import (
+ QAPISchema,
+ QAPISchemaAlternateType,
+ QAPISchemaArrayType,
+ QAPISchemaEnumMember,
+ QAPISchemaFeature,
+ QAPISchemaIfCond,
+ QAPISchemaObjectType,
+ QAPISchemaObjectTypeMember,
+ QAPISchemaType,
+ QAPISchemaVariants,
+)
+from .source import QAPISourceInfo
+
+
+objects_seen = set()
+
+
+def gen_rs_variants_to_tag(name: str,
+ ifcond: QAPISchemaIfCond,
+ variants: QAPISchemaVariants) -> str:
+ ret = mcgen('''
+
+%(cfg)s
+impl From<&%(rs_name)sVariant> for %(tag)s {
+ fn from(e: &%(rs_name)sVariant) -> Self {
+ match e {
+ ''',
+ cfg=ifcond.rsgen(),
+ rs_name=rs_name(name),
+ tag=rs_type(variants.tag_member.type.c_type(), ''))
+
+ for var in variants.variants:
+ type_name = var.type.name
+ tag_name = var.name
+ patt = '(_)'
+ if type_name == 'q_empty':
+ patt = ''
+ ret += mcgen('''
+ %(cfg)s
+ %(rs_name)sVariant::%(var_name)s%(patt)s => Self::%(tag_name)s,
+''',
+ cfg=var.ifcond.rsgen(),
+ rs_name=rs_name(name),
+ tag_name=rs_name(to_upper_case(tag_name)),
+ var_name=rs_name(to_camel_case(tag_name)),
+ patt=patt)
+
+ ret += mcgen('''
+ }
+ }
+}
+''')
+ return ret
+
+
+def gen_rs_variants(name: str,
+ ifcond: QAPISchemaIfCond,
+ variants: QAPISchemaVariants) -> str:
+ ret = mcgen('''
+
+%(cfg)s
+#[derive(Clone, Debug, PartialEq)]
+pub enum %(rs_name)sVariant {
+''',
+ cfg=ifcond.rsgen(),
+ rs_name=rs_name(name))
+
+ for var in variants.variants:
+ type_name = var.type.name
+ var_name = rs_name(to_camel_case(var.name), False)
+ if type_name == 'q_empty':
+ ret += mcgen('''
+ %(cfg)s
+ %(var_name)s,
+''',
+ cfg=var.ifcond.rsgen(),
+ var_name=var_name)
+ else:
+ c_type = var.type.c_unboxed_type()
+ if c_type.endswith('_wrapper'):
+ c_type = c_type[6:-8] # remove q_obj*-wrapper
+ ret += mcgen('''
+ %(cfg)s
+ %(var_name)s(%(rs_type)s),
+''',
+ cfg=var.ifcond.rsgen(),
+ var_name=var_name,
+ rs_type=rs_type(c_type, ''))
+
+ ret += mcgen('''
+}
+''')
+
+ ret += gen_rs_variants_to_tag(name, ifcond, variants)
+
+ return ret
+
+
+def gen_rs_members(members: List[QAPISchemaObjectTypeMember],
+ exclude: Optional[List[str]] = None) -> List[str]:
+ exclude = exclude or []
+ return [f"{m.ifcond.rsgen()} {rs_name(to_lower_case(m.name))}"
+ for m in members if m.name not in exclude]
+
+
+def has_recursive_type(memb: QAPISchemaType,
+ name: str,
+ visited: Set[str]) -> bool:
+ # pylint: disable=too-many-return-statements
+ if name == memb.name:
+ return True
+ if memb.name in visited:
+ return False
+ visited.add(memb.name)
+ if isinstance(memb, QAPISchemaObjectType):
+ if memb.base and has_recursive_type(memb.base, name, visited):
+ return True
+ if memb.branches and \
+ any(has_recursive_type(m.type, name, visited)
+ for m in memb.branches.variants):
+ return True
+ if any(has_recursive_type(m.type, name, visited)
+ for m in memb.members):
+ return True
+ return any(has_recursive_type(m.type, name, visited)
+ for m in memb.local_members)
+ if isinstance(memb, QAPISchemaAlternateType):
+ return any(has_recursive_type(m.type, name, visited)
+ for m in memb.alternatives.variants)
+ if isinstance(memb, QAPISchemaArrayType):
+ return has_recursive_type(memb.element_type, name, visited)
+ return False
+
+
+def gen_struct_members(members: List[QAPISchemaObjectTypeMember],
+ name: str) -> str:
+ ret = ''
+ for memb in members:
+ is_recursive = has_recursive_type(memb.type, name, set())
+ typ = rs_type(memb.type.c_type(), '',
+ optional=memb.optional, box=is_recursive)
+ ret += mcgen('''
+ %(cfg)s
+ pub %(rs_name)s: %(rs_type)s,
+''',
+ cfg=memb.ifcond.rsgen(),
+ rs_type=typ,
+ rs_name=rs_name(to_lower_case(memb.name)))
+ return ret
+
+
+def gen_rs_object(name: str,
+ ifcond: QAPISchemaIfCond,
+ base: Optional[QAPISchemaObjectType],
+ members: List[QAPISchemaObjectTypeMember],
+ variants: Optional[QAPISchemaVariants]) -> str:
+ if name in objects_seen:
+ return ''
+
+ if variants:
+ members = [m for m in members
+ if m.name != variants.tag_member.name]
+
+ ret = ''
+ objects_seen.add(name)
+
+ if variants:
+ ret += gen_rs_variants(name, ifcond, variants)
+
+ ret += mcgen('''
+
+%(cfg)s
+#[derive(Clone, Debug, PartialEq)]
+pub struct %(rs_name)s {
+''',
+ cfg=ifcond.rsgen(),
+ rs_name=rs_name(name))
+
+ if base:
+ if not base.is_implicit():
+ ret += mcgen('''
+ // Members inherited:
+''',
+ c_name=base.c_name())
+ base_members = base.members
+ if variants:
+ base_members = [m for m in base.members
+ if m.name != variants.tag_member.name]
+ ret += gen_struct_members(base_members, name)
+ if not base.is_implicit():
+ ret += mcgen('''
+ // Own members:
+''')
+
+ ret += gen_struct_members(members, name)
+
+ if variants:
+ ret += mcgen('''
+ pub u: %(rs_type)sVariant,
+''', rs_type=rs_name(name))
+ ret += mcgen('''
+}
+''')
+ return ret
+
+
+def gen_rs_enum(name: str,
+ ifcond: QAPISchemaIfCond,
+ members: List[QAPISchemaEnumMember]) -> str:
+ # append automatically generated _max value
+ enum_members = members + [QAPISchemaEnumMember('_MAX', None)]
+
+ ret = mcgen('''
+
+%(cfg)s
+#[repr(u32)]
+#[derive(Copy, Clone, Debug, PartialEq, common::TryInto)]
+pub enum %(rs_name)s {
+''',
+ cfg=ifcond.rsgen(),
+ rs_name=rs_name(name))
+
+ for member in enum_members:
+ ret += mcgen('''
+ %(cfg)s
+ %(c_enum)s,
+''',
+ cfg=member.ifcond.rsgen(),
+ c_enum=rs_name(to_upper_case(member.name)))
+ # picked the first, since that's what malloc0 does
+ default = rs_name(to_upper_case(enum_members[0].name))
+ ret += mcgen('''
+}
+
+%(cfg)s
+impl Default for %(rs_name)s {
+ #[inline]
+ fn default() -> %(rs_name)s {
+ Self::%(default)s
+ }
+}
+''',
+ cfg=ifcond.rsgen(),
+ rs_name=rs_name(name),
+ default=default)
+ return ret
+
+
+def gen_rs_alternate(name: str,
+ ifcond: QAPISchemaIfCond,
+ variants: QAPISchemaVariants) -> str:
+ if name in objects_seen:
+ return ''
+
+ ret = ''
+ objects_seen.add(name)
+
+ ret += mcgen('''
+%(cfg)s
+#[derive(Clone, Debug, PartialEq)]
+pub enum %(rs_name)s {
+''',
+ cfg=ifcond.rsgen(),
+ rs_name=rs_name(name))
+
+ for var in variants.variants:
+ if var.type.name == 'q_empty':
+ continue
+ is_recursive = has_recursive_type(var.type, name, set())
+ ret += mcgen('''
+ %(cfg)s
+ %(mem_name)s(%(rs_type)s),
+''',
+ cfg=var.ifcond.rsgen(),
+ rs_type=rs_type(var.type.c_unboxed_type(), '',
+ box=is_recursive),
+ mem_name=rs_name(to_camel_case(var.name)))
+
+ ret += mcgen('''
+}
+''')
+ return ret
+
+
+class QAPISchemaGenRsTypeVisitor(QAPISchemaRsVisitor):
+
+ def __init__(self, prefix: str) -> None:
+ super().__init__(prefix, 'qapi-types')
+
+ def visit_begin(self, schema: QAPISchema) -> None:
+ # don't visit the empty type
+ objects_seen.add(schema.the_empty_object_type.name)
+ self._gen.preamble_add(
+ mcgen('''
+// @generated by qapi-gen, DO NOT EDIT
+
+#![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;
+'''))
+
+ 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:
+ if name.startswith('q_'):
+ return
+ self._gen.add(gen_rs_object(name, ifcond, base, members, branches))
+
+ def visit_enum_type(self,
+ name: str,
+ info: Optional[QAPISourceInfo],
+ ifcond: QAPISchemaIfCond,
+ features: List[QAPISchemaFeature],
+ members: List[QAPISchemaEnumMember],
+ prefix: Optional[str]) -> None:
+ self._gen.add(gen_rs_enum(name, ifcond, members))
+
+ def visit_alternate_type(self,
+ name: str,
+ info: Optional[QAPISourceInfo],
+ ifcond: QAPISchemaIfCond,
+ features: List[QAPISchemaFeature],
+ alternatives: QAPISchemaVariants) -> None:
+ self._gen.add(gen_rs_alternate(name, ifcond, alternatives))
+
+
+def gen_rs_types(schema: QAPISchema, output_dir: str, prefix: str,
+ builtins: bool) -> None:
+ # pylint: disable=unused-argument
+ # TODO: builtins?
+ vis = QAPISchemaGenRsTypeVisitor(prefix)
+ schema.visit(vis)
+ vis.write(output_dir)
--
2.51.0
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 are simply aliased from FFI
>
> - has_foo/foo members are mapped to Option<T>
>
> - lists are represented as Vec<T>
>
> - structures have Rust versions, with To/From FFI conversions
>
> - alternate are represented as Rust enum
>
> - unions are represented in a similar way as in C: a struct S with a "u"
> member (since S may have extra 'base' fields). However, the discriminant
> isn't a member of S, since Rust enum already include it.
>
> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
> Link: https://lore.kernel.org/r/20210907121943.3498701-21-marcandre.lureau@redhat.com
> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
> ---
> meson.build | 4 +-
> scripts/qapi/backend.py | 27 ++-
> scripts/qapi/main.py | 4 +-
> scripts/qapi/rs.py | 181 +++++++++++++++++++
> scripts/qapi/rs_types.py | 365 +++++++++++++++++++++++++++++++++++++++
> 5 files changed, 577 insertions(+), 4 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 afaefa01722..ce914217c52 100644
> --- a/meson.build
> +++ b/meson.build
> @@ -3571,12 +3571,14 @@ 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/features.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..305b62b514c 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
> @@ -36,7 +37,7 @@ def generate(self,
> """
>
>
> -class QAPICBackend(QAPIBackend):
> +class QAPICodeBackend(QAPIBackend):
Why this rename?
If we want it, separate commit, please.
> # pylint: disable=too-few-public-methods
>
> def generate(self,
> @@ -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, builtins)
As discussed in reply to the cover letter, this series uses the -B
plumbing for out-of-tree backends for generating Rust. Fine for a
prototype. This class is the glue between -B and Rust generation.
> diff --git a/scripts/qapi/main.py b/scripts/qapi/main.py
> index 0e2a6ae3f07..4ad75e213f5 100644
> --- a/scripts/qapi/main.py
> +++ b/scripts/qapi/main.py
> @@ -12,7 +12,7 @@
> import sys
> from typing import Optional
>
> -from .backend import QAPIBackend, QAPICBackend
> +from .backend import QAPIBackend, QAPICodeBackend
> from .common import must_match
> from .error import QAPIError
> from .schema import QAPISchema
> @@ -27,7 +27,7 @@ def invalid_prefix_char(prefix: str) -> Optional[str]:
>
> def create_backend(path: str) -> QAPIBackend:
> if path is None:
> - return QAPICBackend()
> + return QAPICodeBackend()
>
> module_path, dot, class_name = path.rpartition('.')
> if not dot:
> diff --git a/scripts/qapi/rs.py b/scripts/qapi/rs.py
> new file mode 100644
> index 00000000000..2a9bbcb9f54
> --- /dev/null
> +++ b/scripts/qapi/rs.py
> @@ -0,0 +1,181 @@
> +# This work is licensed under the terms of the GNU GPL, version 2.
> +# See the COPYING file in the top-level directory.
> +"""
> +QAPI Rust generator
> +"""
> +
> +import os
> +import re
> +import subprocess
> +from typing import NamedTuple, Optional
> +
> +from .common import POINTER_SUFFIX
> +from .gen import QAPIGen
> +from .schema import QAPISchemaModule, QAPISchemaVisitor
> +
> +
> +# see to_upper_case()/to_lower_case() below
> +snake_case = re.compile(r'((?<=[a-z0-9])[A-Z]|(?!^)[A-Z](?=[a-z]))')
> +
> +
> +rs_name_trans = str.maketrans('.-', '__')
> +
> +
> +# Map @name to a valid Rust identifier.
> +# If @protect, avoid returning certain ticklish identifiers (like
> +# keywords) by prepending raw identifier prefix 'r#'.
> +def rs_name(name: str, protect: bool = True) -> str:
> + name = name.translate(rs_name_trans)
> + if name[0].isnumeric():
> + name = '_' + name
> + if not protect:
> + return 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
> + # avoid some clashes with the standard library
> + if name in ('String',):
> + name = 'Qapi' + name
> +
> + return name
This is like common.c_name(). Differences:
1. Funny input characters
c_name() returns a valid C identifier for any non-empty input.
rs_name() requires its argument to contain only characters valid in
Rust identifiers plus '.' and '-'.
I think we better avoid this difference.
2. "Protected" identifiers
When @protect, then certain "protected" identifiers are prefixed with
'q_'. We typically pass False to @protect when the output is used as
part of an identifier.
c_name() and rs_name() protect different identifiers. Makes sense.
3. Input starting with a digit
c_name() treats them just like protected identifiers, i.e. prefix
with 'q_' when @protect.
rs_name() prefixes with '_'. Is this a good idea? Hmm... "The Rust
Reference:
Note
Identifiers starting with an underscore are typically used to
indicate an identifier that is intentionally unused, and will
silence the unused warning in rustc.
https://doc.rust-lang.org/reference/identifiers.html
rs_name() prefixes always, not just when @protect. Is this a good
idea? Remember, @protect is typically false when the output is used
as part of an identifier. Or do we use it differently for Rust?
4. Name clash avoidance
c_name() treats names that are prone to clash as protected,
i.e. prefix 'q_' unless @protect.
rs_name() prefixes 'Qapi' instead. Why?
> +
> +
> +def rs_type(c_type: str,
> + qapi_ns: str = 'qapi::',
> + optional: bool = False,
> + box: bool = False) -> str:
> + (is_pointer, _, is_list, c_type) = rs_ctype_parse(c_type)
> + to_rs = {
> + 'QNull': '()',
> + 'QObject': 'QObject',
> + 'any': 'QObject',
> + 'bool': 'bool',
> + 'char': 'i8',
> + 'double': 'f64',
> + 'int': 'i64',
> + 'int16': 'i16',
> + 'int16_t': 'i16',
> + 'int32': 'i32',
> + 'int32_t': 'i32',
> + 'int64': 'i64',
> + 'int64_t': 'i64',
> + 'int8': 'i8',
> + 'int8_t': 'i8',
> + 'number': 'f64',
> + 'size': 'u64',
> + 'str': 'String',
> + 'uint16': 'u16',
> + 'uint16_t': 'u16',
> + 'uint32': 'u32',
> + 'uint32_t': 'u32',
> + 'uint64': 'u64',
> + 'uint64_t': 'u64',
> + 'uint8': 'u8',
> + 'uint8_t': 'u8',
> + 'String': 'QapiString',
> + }
The argument name @c_type suggests it is a C type, but this map contains
a mix of C types, QAPI built-in types, and even a user-defined QAPI type
(String). How come?
Why do we even have to map from C type to Rust type? Why can't we map
from QAPI type to Rust type, like we map from QAPI type to C type?
> + if is_pointer:
> + to_rs.update({
> + 'char': 'String',
> + })
> +
> + if is_list:
> + c_type = c_type[:-4]
> +
> + ret = to_rs.get(c_type, qapi_ns + c_type)
> + if is_list:
> + ret = 'Vec<%s>' % ret
> + elif is_pointer and c_type not in to_rs and box:
> + ret = 'Box<%s>' % ret
> + if optional:
> + ret = 'Option<%s>' % ret
> + return ret
> +
> +
> +class CType(NamedTuple):
> + is_pointer: bool
> + is_const: bool
> + is_list: bool
> + c_type: str
> +
> +
> +def rs_ctype_parse(c_type: str) -> CType:
> + is_pointer = False
> + if c_type.endswith(POINTER_SUFFIX):
> + is_pointer = True
> + c_type = c_type[:-len(POINTER_SUFFIX)]
> + is_list = c_type.endswith('List')
> + is_const = False
> + if c_type.startswith('const '):
> + is_const = True
> + c_type = c_type[6:]
> +
> + c_type = rs_name(c_type)
> + return CType(is_pointer, is_const, is_list, c_type)
This feels a bit brittle.
> +
> +
> +def to_camel_case(value: str) -> str:
> + # special case for last enum value
> + if value == '_MAX':
> + return value
> + raw_id = False
> + if value.startswith('r#'):
> + raw_id = True
> + value = value[2:]
> + value = ''.join('_' + word if word[0].isdigit()
> + else word[:1].upper() + word[1:]
> + for word in filter(None, re.split("[-_]+", value)))
> + if raw_id:
> + return 'r#' + value
> + return value
> +
> +
> +def to_upper_case(value: str) -> str:
> + return snake_case.sub(r'_\1', value).upper()
This tackles the same problem as common.camel_to_upper(). Your code is
much simpler. However, the two produce different output, e.g.
input output
QType QTYPE
Q_TYPE
XDbgBlockGraphNodeType XDBG_BLOCK_GRAPH_NODE_TYPE
X_DBG_BLOCK_GRAPH_NODE_TYPE
QCryptoTLSCredsEndpoint QCRYPTO_TLS_CREDS_ENDPOINT
Q_CRYPTO_TLS_CREDS_ENDPOINT
I doubt having two different mappings from CamelCase make sense.
See also commit 7b29353fdd9 (qapi: Smarter camel_to_upper() to reduce
need for 'prefix').
Aside: the examples in camel_to_upper()'s function comment are out of
date. I'll take care of that.
> +
> +
> +def to_lower_case(value: str) -> str:
> + return snake_case.sub(r'_\1', value).lower()
> +
> +
> +class QAPIGenRs(QAPIGen):
> + pass
In my initial review of the generated code, I suggested a file comment.
Code for that would go here. See QAPIGenC for an example.
> +
> +
> +class QAPISchemaRsVisitor(QAPISchemaVisitor):
> +
> + def __init__(self, prefix: str, what: str):
> + super().__init__()
> + self._prefix = prefix
> + self._what = what
> + self._gen = QAPIGenRs(self._prefix + self._what + '.rs')
> + self._main_module: Optional[str] = None
> +
> + def visit_module(self, name: Optional[str]) -> None:
> + if name is None:
> + return
> + if QAPISchemaModule.is_user_module(name):
> + if self._main_module is None:
> + self._main_module = name
._main_module appears to be unused.
> +
> + def write(self, output_dir: str) -> None:
> + self._gen.write(output_dir)
> +
> + pathname = os.path.join(output_dir, self._gen.fname)
This duplicates ._gen.write()'s file name construction. I think we
better make it a available from ._gen.
> + try:
> + subprocess.check_call(['rustfmt', pathname])
Interesting. Worth mentioning in the commit message.
> + except FileNotFoundError:
> + pass
Huh?
Gotta run, rest left for later.
[...]
On 12/17/25 14:32, Markus Armbruster wrote:
> This is like common.c_name(). Differences:
>
> 1. Funny input characters
>
> c_name() returns a valid C identifier for any non-empty input.
>
> rs_name() requires its argument to contain only characters valid in
> Rust identifiers plus '.' and '-'.
>
> I think we better avoid this difference.
Done.
> 3. Input starting with a digit
>
> c_name() treats them just like protected identifiers, i.e. prefix
> with 'q_' when @protect.
>
> rs_name() prefixes with '_'. Is this a good idea? Hmm... "The Rust
> Reference:
>
> Note
>
> Identifiers starting with an underscore are typically used to
> indicate an identifier that is intentionally unused, and will
> silence the unused warning in rustc.
>
> https://doc.rust-lang.org/reference/identifiers.html
In this case it doesn't really matter: public items (such as QAPI enum
entries, or struct fields) do not raise the unused warning anyway.
> rs_name() prefixes always, not just when @protect. Is this a good
> idea? Remember, @protect is typically false when the output is used
> as part of an identifier. Or do we use it differently for Rust?
Removed @protect, as it was unused for Rust. Makes sense, because Rust
uses Enum::Ident instead of ENUM_IDENT.
> 4. Name clash avoidance
>
> c_name() treats names that are prone to clash as protected,
> i.e. prefix 'q_' unless @protect.
>
> rs_name() prefixes 'Qapi' instead. Why?
Because Rust tools are a bit more fussy about the shape of the
identifiers; in particular they want types and enum names to start with
an uppercase letter and use camel case. Using 'q_' as the prefix makes
them complain.
Fortunately this is limited to String, which has very limited uses... I
don't remember why String exists, probably strList wasn't a thing yet?
>> +
>> +
>> +def rs_type(c_type: str,
>> + qapi_ns: str = 'qapi::',
>> + optional: bool = False,
>> + box: bool = False) -> str:
>> + (is_pointer, _, is_list, c_type) = rs_ctype_parse(c_type)
>> + to_rs = {
>> + 'QNull': '()',
>> + 'QObject': 'QObject',
>> + 'any': 'QObject',
>> + 'bool': 'bool',
>> + 'char': 'i8',
>> + 'double': 'f64',
>> + 'int': 'i64',
>> + 'int16': 'i16',
>> + 'int16_t': 'i16',
>> + 'int32': 'i32',
>> + 'int32_t': 'i32',
>> + 'int64': 'i64',
>> + 'int64_t': 'i64',
>> + 'int8': 'i8',
>> + 'int8_t': 'i8',
>> + 'number': 'f64',
>> + 'size': 'u64',
>> + 'str': 'String',
>> + 'uint16': 'u16',
>> + 'uint16_t': 'u16',
>> + 'uint32': 'u32',
>> + 'uint32_t': 'u32',
>> + 'uint64': 'u64',
>> + 'uint64_t': 'u64',
>> + 'uint8': 'u8',
>> + 'uint8_t': 'u8',
>> + 'String': 'QapiString',
>> + }
>
> The argument name @c_type suggests it is a C type, but this map contains
> a mix of C types, QAPI built-in types, and even a user-defined QAPI type
> (String). How come?
>
> Why do we even have to map from C type to Rust type? Why can't we map
> from QAPI type to Rust type, like we map from QAPI type to C type?
I'll look into it.
All other comments addressed, thanks for the review!
Paolo
On 1/7/26 10:06, Paolo Bonzini wrote:
>> The argument name @c_type suggests it is a C type, but this map contains
>> a mix of C types, QAPI built-in types, and even a user-defined QAPI type
>> (String). How come?
>>
>> Why do we even have to map from C type to Rust type? Why can't we map
>> from QAPI type to Rust type, like we map from QAPI type to C type?
>
> I'll look into it.
Ok, the reason for this is to have a single "def rs_type()" function
instead of multiple methods ("def rs_type(self)" and "def
rs_boxed_type(self)" on each of the schema types).
I'll change this to use methods.
Paolo
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 are simply aliased from FFI
>
> - has_foo/foo members are mapped to Option<T>
>
> - lists are represented as Vec<T>
>
> - structures have Rust versions, with To/From FFI conversions
>
> - alternate are represented as Rust enum
>
> - unions are represented in a similar way as in C: a struct S with a "u"
> member (since S may have extra 'base' fields). However, the discriminant
> isn't a member of S, since Rust enum already include it.
>
> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
> Link: https://lore.kernel.org/r/20210907121943.3498701-21-marcandre.lureau@redhat.com
> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
I like to look at the generated code before I look at the code
generator. My go-to schema for a first look is example-schema.json from
docs/devel/qapi-code-gen.rst:
{ 'struct': 'UserDefOne',
'data': { 'integer': 'int', '*string': 'str', '*flag': 'bool' } }
{ 'command': 'my-command',
'data': { 'arg1': ['UserDefOne'] },
'returns': 'UserDefOne' }
{ 'event': 'MY_EVENT' }
Generated example-qapi-types.rs:
// @generated by qapi-gen, DO NOT EDIT
#![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;
#[repr(u32)]
#[derive(Copy, Clone, Debug, PartialEq, common::TryInto)]
pub enum QType {
NONE,
QNULL,
QNUM,
QSTRING,
QDICT,
QLIST,
QBOOL,
_MAX,
}
impl Default for QType {
#[inline]
fn default() -> QType {
Self::NONE
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct UserDefOne {
pub integer: i64,
pub string: Option<String>,
pub flag: Option<bool>,
}
Questions / observations:
* Why is util::qobject::QObject needed?
* NONE is an error value, not a valid QType. Having such error values
in enums isn't unusual in C. What about idiomatic Rust? Even if it's
unusual there, we may elect to do it anyway, just to keep generated
Rust closer to C. But it should be a conscious decision, not a blind
port from C to Rust.
* "Default for QType" is NONE. In C, it's zero bytes, which boils down
to QTYPE_NONE.
* QTYPE__MAX is a bit of a headache in C. It's not a valid enum value.
We make it one only because we need to know the largest valid enum
value, e.g. to size arrays, and the easiest way to get that value is
adding an invalid one to the enum. Same for all the other generated
enums. Could we avoid it in Rust?
* Blank lines before the values of enum QType and the members of struct
UserDefOne contain spaces. PATCH 16 will remove the spaces.
* Definitions are separated by two blank lines. PATCH 16 will collapse
them into one.
Compare to example-qapi-types.h:
/* AUTOMATICALLY GENERATED by qapi-gen.py DO NOT MODIFY */
/*
* Schema-defined QAPI types
*
* Copyright IBM, Corp. 2011
* Copyright (c) 2013-2018 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.
*/
#ifndef EXAMPLE_QAPI_TYPES_H
#define EXAMPLE_QAPI_TYPES_H
#include "qapi/qapi-builtin-types.h"
typedef struct UserDefOne UserDefOne;
typedef struct UserDefOneList UserDefOneList;
typedef struct q_obj_my_command_arg q_obj_my_command_arg;
struct UserDefOne {
int64_t integer;
char *string;
bool has_flag;
bool flag;
};
void qapi_free_UserDefOne(UserDefOne *obj);
G_DEFINE_AUTOPTR_CLEANUP_FUNC(UserDefOne, qapi_free_UserDefOne)
struct UserDefOneList {
UserDefOneList *next;
UserDefOne *value;
};
void qapi_free_UserDefOneList(UserDefOneList *obj);
G_DEFINE_AUTOPTR_CLEANUP_FUNC(UserDefOneList, qapi_free_UserDefOneList)
struct q_obj_my_command_arg {
UserDefOneList *arg1;
};
#endif /* EXAMPLE_QAPI_TYPES_H */
Observations:
* C has a file comment of the form
/*
* One-line description of the file's purpose
*
* Copyright lines
*
* License blurb
*/
I think Rust could use such a comment, too.
* C has built-in types like QType in qapi-builtin-types.h, generated
only with -b. This is a somewhat crude way to let code generated for
multiple schemas coexist: pass -b for exactly one of them. If we
generated code for built-in types unconditionally into qapi-types.h,
the C compiler would choke on duplicate definitions. Why is this not
a problem with Rust?
* The Rust version doesn't have deallocation boilerplate. Deallocation
just works there, I guess.
* The Rust version doesn't have the List type. Lists just work there, I
guess.
* The Rust version doesn't have the implicit type q_obj_my_command_arg,
which is the arguments of my-command as a struct type. C needs it for
marshaling / unmarshaling with visitors. Rust doesn't, because we use
serde. Correct?
On 12/9/25 11:03, Markus Armbruster wrote:
> * Why is util::qobject::QObject needed?
>
> * NONE is an error value, not a valid QType. Having such error values
> in enums isn't unusual in C. What about idiomatic Rust? Even if it's
> unusual there, we may elect to do it anyway, just to keep generated
> Rust closer to C. But it should be a conscious decision, not a blind
> port from C to Rust.
For QType we don't need to keep it closer, but actually ABI-compatible:
QType is defined by QAPI but is used (almost exclusively) by QObject.
We use the C version in the QObject bindings, for example:
$($crate::bindings::QTYPE_QNULL => break $unit,)?
> * "Default for QType" is NONE. In C, it's zero bytes, which boils down
> to QTYPE_NONE.
>
> * QTYPE__MAX is a bit of a headache in C. It's not a valid enum value.
> We make it one only because we need to know the largest valid enum
> value, e.g. to size arrays, and the easiest way to get that value is
> adding an invalid one to the enum. Same for all the other generated
> enums. Could we avoid it in Rust?
Yes, I think so.
> * C has a file comment of the form
>
> /*
> * One-line description of the file's purpose
> *
> * Copyright lines
> *
> * License blurb
> */
>
> I think Rust could use such a comment, too.
Ok.
> * C has built-in types like QType in qapi-builtin-types.h, generated
> only with -b. This is a somewhat crude way to let code generated for
> multiple schemas coexist: pass -b for exactly one of them. If we
> generated code for built-in types unconditionally into qapi-types.h,
> the C compiler would choke on duplicate definitions. Why is this not
> a problem with Rust?
Because there's better namespacing, so it's okay to define the builtin
types in more than one place. However, do we need at all the builtin
types in Rust? QType is only defined in QAPI to have the nice enum
lookup tables, and we can get it via FFI bindings. Lists, as you say
below, are not needed, and they are also a part of qapi-builtin-types.h.
So I think Rust does not need built-in types at all, which I think
solves all your problems here (other than _MAX which can be removed).
>
> * The Rust version doesn't have deallocation boilerplate. Deallocation
> just works there, I guess.
>
> * The Rust version doesn't have the List type. Lists just work there, I
> guess.
Yep.
> * The Rust version doesn't have the implicit type q_obj_my_command_arg,
> which is the arguments of my-command as a struct type. C needs it for
> marshaling / unmarshaling with visitors. Rust doesn't, because we use
> serde. Correct?
Commands are not supported at all yet.
Paolo
Paolo Bonzini <pbonzini@redhat.com> writes: > On 12/9/25 11:03, Markus Armbruster wrote: >> * Why is util::qobject::QObject needed? >> >> * NONE is an error value, not a valid QType. Having such error values >> in enums isn't unusual in C. What about idiomatic Rust? Even if it's >> unusual there, we may elect to do it anyway, just to keep generated >> Rust closer to C. But it should be a conscious decision, not a blind >> port from C to Rust. > > For QType we don't need to keep it closer, but actually ABI-compatible: > QType is defined by QAPI but is used (almost exclusively) by QObject. > We use the C version in the QObject bindings, for example: > > $($crate::bindings::QTYPE_QNULL => break $unit,)? I see. Worth a comment. >> * "Default for QType" is NONE. In C, it's zero bytes, which boils down >> to QTYPE_NONE. >> >> * QTYPE__MAX is a bit of a headache in C. It's not a valid enum value. >> We make it one only because we need to know the largest valid enum >> value, e.g. to size arrays, and the easiest way to get that value is >> adding an invalid one to the enum. Same for all the other generated >> enums. Could we avoid it in Rust? > > Yes, I think so. > >> * C has a file comment of the form >> >> /* >> * One-line description of the file's purpose >> * >> * Copyright lines >> * >> * License blurb >> */ >> >> I think Rust could use such a comment, too. > > Ok. > >> * C has built-in types like QType in qapi-builtin-types.h, generated >> only with -b. This is a somewhat crude way to let code generated for >> multiple schemas coexist: pass -b for exactly one of them. If we >> generated code for built-in types unconditionally into qapi-types.h, >> the C compiler would choke on duplicate definitions. Why is this not >> a problem with Rust? > > Because there's better namespacing, so it's okay to define the builtin > types in more than one place. However, do we need at all the builtin > types in Rust? QType is only defined in QAPI to have the nice enum > lookup tables, and we can get it via FFI bindings. Lists, as you say > below, are not needed, and they are also a part of qapi-builtin-types.h. > > So I think Rust does not need built-in types at all, which I think > solves all your problems here (other than _MAX which can be removed). Let's try this. >> * The Rust version doesn't have deallocation boilerplate. Deallocation >> just works there, I guess. >> >> * The Rust version doesn't have the List type. Lists just work there, I >> guess. > > Yep. > >> * The Rust version doesn't have the implicit type q_obj_my_command_arg, >> which is the arguments of my-command as a struct type. C needs it for >> marshaling / unmarshaling with visitors. Rust doesn't, because we use >> serde. Correct? > > Commands are not supported at all yet. > > Paolo Thanks!
© 2016 - 2026 Red Hat, Inc.