slint-ui/slint · error · proc_macro::Error

The structure does not ends in 'VTable'

Error message

The structure does not ends in 'VTable'

What it means

The #[vtable] macro derives the trait name by stripping a literal 'VTable' suffix from the struct's identifier (e.g. FooVTable -> trait Foo). A struct whose name does not end in 'VTable' breaks this derivation, so the macro rejects it at compile time.

Solutions

  1. Rename the struct so it ends in 'VTable' (e.g. MyTraitVTable for trait MyTrait).
  2. Ensure the part before 'VTable' is exactly the trait name you want, since it is used verbatim to generate the trait.

Example fix

// before
#[vtable]
struct MyTraitVT { init: fn(VRef<MyTraitVT>) }
// after
#[vtable]
struct MyTraitVTable { init: fn(VRef<MyTraitVTable>) }
Defensive patterns

Strategy: validation

Validate before calling

assert!(struct_name.ends_with("VTable"));

Prevention

When it happens

Trigger: Annotating a named-field struct named e.g. `FooVt`, `FooTable`, or `Foo` with #[vtable].

Common situations: Renaming a vtable struct during refactoring and accidentally removing or altering the required 'VTable' suffix; inventing a new trait interface without following the naming convention.

Understand the failure class

Background: "invalid id" errors: invalid identifier format — why libraries reject IDs before lookup, and how to fix them — this error's family across 37 libraries.

Related errors


AI-assisted analysis of slint-ui/slint@bb937076de (2026-09-16). Data as JSON: /api/errors/9c772148d47551fe. Report an issue: GitHub.

Appendix: source

Thrown at helper_crates/vtable/macro/macro.rs:183

*/
#[proc_macro_attribute]
pub fn vtable(_attr: TokenStream, item: TokenStream) -> TokenStream {
    let mut input = parse_macro_input!(item as ItemStruct);

    let fields = if let Fields::Named(fields) = &mut input.fields {
        fields
    } else {
        return Error::new(
            proc_macro2::Span::call_site(),
            "Only supported for structure with named fields",
        )
        .to_compile_error()
        .into();
    };

    let vtable_name = input.ident.to_string();
    if !vtable_name.ends_with("VTable") {
        return Error::new(input.ident.span(), "The structure does not ends in 'VTable'")
            .to_compile_error()
            .into();
    }

    let trait_name = Ident::new(&vtable_name[..vtable_name.len() - 6], input.ident.span());
    let to_name = quote::format_ident!("{}TO", trait_name);
    let module_name = quote::format_ident!("{}_vtable_mod", trait_name);
    let static_vtable_macro_name = quote::format_ident!("{}_static", vtable_name);

    let vtable_name = input.ident.clone();

    let mut drop_impls = Vec::new();

    let mut generated_trait = ItemTrait {
        attrs: input
            .attrs
            .iter()
            .filter(|a| a.path().get_ident().as_ref().map(|i| *i == "doc").unwrap_or(false))

View on GitHub (pinned to bb937076de)