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
- 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).
- 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.
Repo (primary): rust-lang/rust-bindgen
Also affects: rusqlite/rusqlite (
libsqlite3-sys), any*-syscrate using buildtime bindgen on bleeding-edge distrosVersions 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
libclangis 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-sysproduced:which made
rusqlitefail with: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. Whenlibclangis 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.3as default; anllvm18install at/usr/lib/llvm18/lib/libclang.so.18also present.Reproduction
*-syscrate that uses buildtime bindgen against a non-trivial header (e.g.libsqlite3-syswith bindgen).OUT_DIR/.../bindgen.rsand downstreamno field/cannot find typeerrors.Workaround (works, but undiscoverable)
Point bindgen at an older, supported libclang:
→ full structs generated,
rusqlitecompiles. The hard part is knowing this is the problem — there is zero signal pointing at libclang.Proposed fix (LLM-actionable)
libclangmajor version is greater than the highest known-tested version, emit a loudcargo: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.LIBCLANG_PATHrequirement 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_infohas its real fields and dependents compile.