Skip to content

[BUG/DX] bindgen silently emits opaque structs when libclang is newer than supported → confusing downstream "no field" errors #3383

@tairea

Description

@tairea

Repo (primary): rust-lang/rust-bindgen
Also affects: rusqlite/rusqlite (libsqlite3-sys), any *-sys crate using buildtime bindgen on bleeding-edge distros
Versions seen: bindgen 0.69.5 (libsqlite3-sys) and 0.71 (datachannel-sys), system libclang 22.1.3
Labels: bug, dx, libclang, diagnostics
Severity: medium (silent failure → very confusing downstream compile errors)

Summary

On a system where the default libclang is much newer than bindgen supports (here libclang 22 vs bindgen 0.69/0.71), bindgen does not error — it silently generates opaque placeholder structs and drops fields. Downstream Rust code then fails with cryptic errors far from the real cause.

Concretely, libsqlite3-sys produced:

// out/bindgen.rs
#[repr(C)] #[derive(Debug, Copy, Clone)]
pub struct sqlite3_index_info { pub _address: u8 }   // <-- opaque stub; all real fields gone

which made rusqlite fail with:

error[E0609]: no field `idxFlags` on type `sqlite3_index_info`
error[E0609]: no field `colUsed` on type `sqlite3_index_info`
error[E0425]: cannot find type `sqlite3_index_constraint` in crate `ffi`
... (170+ errors)

None of which mention libclang/bindgen — so the user has no idea the real cause is a libclang version skew.

Root cause

bindgen parses headers via libclang. When libclang is newer than the version bindgen was tested against, parsing can partially fail (e.g., can't resolve the struct body), and bindgen falls back to emitting an opaque type instead of failing loudly. The generated bindings compile in isolation but are missing fields, so the breakage surfaces only in dependent crates.

Environment

Arch Linux 2026, /usr/lib/libclang.so.22.1.3 as default; an llvm18 install at /usr/lib/llvm18/lib/libclang.so.18 also present.

Reproduction

  1. On a box where default libclang ≫ bindgen's supported range, build a *-sys crate that uses buildtime bindgen against a non-trivial header (e.g. libsqlite3-sys with bindgen).
  2. Observe opaque structs in OUT_DIR/.../bindgen.rs and downstream no field / cannot find type errors.

Workaround (works, but undiscoverable)

Point bindgen at an older, supported libclang:

export LIBCLANG_PATH=/usr/lib/llvm18/lib   # libclang 18

→ full structs generated, rusqlite compiles. The hard part is knowing this is the problem — there is zero signal pointing at libclang.

Proposed fix (LLM-actionable)

  • bindgen: when the detected libclang major version is greater than the highest known-tested version, emit a loud cargo:warning= (e.g. bindgen: libclang 22 is newer than tested (<=N); generated bindings may be incomplete/opaque. Set LIBCLANG_PATH to an older libclang if you see missing-field errors.). Optionally make "fell back to opaque type due to parse failure" emit a warning rather than being silent.
  • *libsqlite3-sys / -sys crates: detect empty/opaque core structs post-bindgen and fail the build script with an actionable message, or document the LIBCLANG_PATH requirement on new distros.

Verification

On a too-new-libclang system, the build prints a clear warning naming libclang/LIBCLANG_PATH; with the override set, sqlite3_index_info has its real fields and dependents compile.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions