Summary
First real attempt to wire c2rust's raw, "clean"-outcome output directly into a real kernel build (not just standalone rustc --crate-type lib compile-checking) found three genuine, corpus-wide gaps between "c2rust says clean" and "actually compiles/links in a real kernel context." Full evidence and fix details: docs/combined-boot-attempt-2026-07-18.md in the linux-rs repo.
Test case: lib/group_cpus.c → group_cpus_evenly(), chosen because it was already c2rust-clean and passed every rule-conformance check this project has. Despite that, it needed real hand-fixes before it would compile as part of an actual kernel build (scripts/Makefile.build's real Rust compilation path, not a standalone rustc invocation).
Gap 1: unstable features outside the kernel's allowed set
c2rust's raw output declared #![feature(asm, extern_types, raw_ref_op, strict_provenance)]. Real kernel builds (this one, and by inspection the general Rust-for-Linux convention — see Rust-for-Linux/linux#2) hard-restrict which unstable features non-rust/-crate code may use via -Zallow-features=... plus a -Zcrate-attr='feature(...)' override that replaces any file-level #![feature(...)] declaration entirely. This project's allowed set: arbitrary_self_types,asm_goto,generic_arg_infer,used_with_arg — zero overlap with what c2rust emitted.
Concretely, the biggest offender is extern_types, used for every pulled-in-from-headers opaque type marker (extern "C" { pub type foo; }) — c2rust emits dozens of these per file for types the actual translated function never even uses (whole-TU header context, not per-function scoping). A stable substitute exists and was used successfully in this fix: a zero-field #[repr(C)] struct foo { _private: [u8; 0] } gives the same "opaque, pointer-only" semantics without the unstable feature.
Gap 2: missing explicit unsafe {} blocks inside unsafe fn bodies
26 raw pointer dereferences / unsafe-function calls inside unsafe extern "C" fn bodies had no explicit unsafe {} wrapper — c2rust relies on older implicit-unsafe-in-unsafe-fn semantics. This surfaced as E0133 errors in the real kernel build even under --edition=2021 (the kernel's own build flag), suggesting either a rustc-version-level default shift or an interaction with the crate-attribute override from Gap 1. Mechanical fix: wrap each affected function's body in one unsafe { ... } block.
Gap 3: .init_array constructor trick has no kernel-linker equivalent
c2rust emits a __UNIQUE_ID_addressable_* static plus a c2rust_run_static_initializers() function registered via #[link_section = ".init_array"], intended to keep a function's address alive against dead-code elimination in a userspace ELF-loader context (a libc/dynamic-linker convention). The kernel's own link script has no .init_array support and discards the section, leaving a dangling relocation: ld.lld: relocation refers to a symbol in a discarded section. Not needed in a kernel context where the real EXPORT_SYMBOL_GPL asm block already keeps the symbol referenced — likely safe to simply not emit this pattern at all for kernel-target output (or gate it behind a kernel-idiom rule, same mechanism as the existing --enable-rule conventions).
Ask
Each gap is corpus-wide (affects any file using these constructs, not just group_cpus.c) and mechanically fixable in the transpiler:
- Avoid emitting
extern_types for unused opaque-type declarations (or use the zero-field-struct idiom generally instead of extern type).
- Wrap raw unsafe operations in unsafe-fn bodies in explicit
unsafe {} blocks.
- Don't emit the
.init_array constructor pattern (or gate it behind an opt-in rule, off by default for kernel targets).
Filed with concrete evidence from a real fix, not a hypothetical — happy to share the full hand-fixed file as a reference if useful.
Summary
First real attempt to wire c2rust's raw, "clean"-outcome output directly into a real kernel build (not just standalone
rustc --crate-type libcompile-checking) found three genuine, corpus-wide gaps between "c2rust says clean" and "actually compiles/links in a real kernel context." Full evidence and fix details: docs/combined-boot-attempt-2026-07-18.md in the linux-rs repo.Test case:
lib/group_cpus.c→group_cpus_evenly(), chosen because it was already c2rust-clean and passed every rule-conformance check this project has. Despite that, it needed real hand-fixes before it would compile as part of an actual kernel build (scripts/Makefile.build's real Rust compilation path, not a standalone rustc invocation).Gap 1: unstable features outside the kernel's allowed set
c2rust's raw output declared
#![feature(asm, extern_types, raw_ref_op, strict_provenance)]. Real kernel builds (this one, and by inspection the general Rust-for-Linux convention — seeRust-for-Linux/linux#2) hard-restrict which unstable features non-rust/-crate code may use via-Zallow-features=...plus a-Zcrate-attr='feature(...)'override that replaces any file-level#![feature(...)]declaration entirely. This project's allowed set:arbitrary_self_types,asm_goto,generic_arg_infer,used_with_arg— zero overlap with what c2rust emitted.Concretely, the biggest offender is
extern_types, used for every pulled-in-from-headers opaque type marker (extern "C" { pub type foo; }) — c2rust emits dozens of these per file for types the actual translated function never even uses (whole-TU header context, not per-function scoping). A stable substitute exists and was used successfully in this fix: a zero-field#[repr(C)] struct foo { _private: [u8; 0] }gives the same "opaque, pointer-only" semantics without the unstable feature.Gap 2: missing explicit
unsafe {}blocks insideunsafe fnbodies26 raw pointer dereferences / unsafe-function calls inside
unsafe extern "C" fnbodies had no explicitunsafe {}wrapper — c2rust relies on older implicit-unsafe-in-unsafe-fn semantics. This surfaced asE0133errors in the real kernel build even under--edition=2021(the kernel's own build flag), suggesting either a rustc-version-level default shift or an interaction with the crate-attribute override from Gap 1. Mechanical fix: wrap each affected function's body in oneunsafe { ... }block.Gap 3:
.init_arrayconstructor trick has no kernel-linker equivalentc2rust emits a
__UNIQUE_ID_addressable_*static plus ac2rust_run_static_initializers()function registered via#[link_section = ".init_array"], intended to keep a function's address alive against dead-code elimination in a userspace ELF-loader context (a libc/dynamic-linker convention). The kernel's own link script has no.init_arraysupport and discards the section, leaving a dangling relocation:ld.lld: relocation refers to a symbol in a discarded section. Not needed in a kernel context where the realEXPORT_SYMBOL_GPLasm block already keeps the symbol referenced — likely safe to simply not emit this pattern at all for kernel-target output (or gate it behind a kernel-idiom rule, same mechanism as the existing--enable-ruleconventions).Ask
Each gap is corpus-wide (affects any file using these constructs, not just
group_cpus.c) and mechanically fixable in the transpiler:extern_typesfor unused opaque-type declarations (or use the zero-field-struct idiom generally instead ofextern type).unsafe {}blocks..init_arrayconstructor pattern (or gate it behind an opt-in rule, off by default for kernel targets).Filed with concrete evidence from a real fix, not a hypothetical — happy to share the full hand-fixed file as a reference if useful.