RFA-609 · Case file with fixtures · Case 581 of 694 · Compiler evidence
Rust extern Functions Must Use a Supported ABI Name
An ABI string selects machine-level calling rules from Rust's supported set. It does not name a protocol, library, or business service.
- Reviewed
- Rust
- Rust 1.98.1, edition 2024
- Targets
- ABI-dependent; verify the selected target
- Profiles
- dev, release, test
Direct answer
What this Rust failure means
- Why it happens
- The extern ABI string was mistaken for a free-form protocol or library name rather than a machine-level calling convention selector.
- First discriminating check
- Read the authoritative foreign declaration and choose one rustc-supported ABI for the exact target, then verify types and calls end to end.
The string after extern selects a calling convention understood by rustc and the target. It is not a free-form name for the library or operation. The failing fixture writes extern "service", so rustc emits E0703.
Calling convention is a machine contract
An ABI determines details such as how parameters and return values are passed, which registers are preserved, and how symbols interact with platform tooling. Both caller and callee must agree. A mismatch can corrupt state even if every Rust-level type name looks compatible.
The official E0703 explanation says to choose a predefined ABI. Current rustc can print supported calling conventions with rustc --print=calling-conventions; availability and behaviour can depend on target and stability.
I derive the choice from the foreign header, platform specification, or host API—not from a guess that C always means every foreign interface.
The fixture repair selects C
The repaired fixture uses extern "C" and calls the function. C is suitable for this generic demonstration because it is a widely supported interoperability convention.
Windows callbacks may require system or a more specific convention. WebAssembly hosts and embedded interrupt handlers can have their own requirements. Rust's default ABI is for Rust-to-Rust calls and is not generally a stable foreign interface.
The Reference section on the extern function qualifier is the language source. I also pin target triples in build evidence because an ABI name accepted on one target may not provide the intended contract on another.
ABI name does not make types FFI-safe
After E0703 is fixed, every parameter and return type still needs an externally defined layout and valid-value agreement. Rust references, trait objects, ordinary enums, and many generic types are not automatically suitable for C.
I use C-compatible scalars, pointers, and repr(C) records where the foreign declaration requires them. Opaque handles keep Rust internals behind pointer-sized identities. Strings cross through pointer-plus-length or a documented nul-terminated convention with explicit ownership.
The Rustonomicon's FFI chapter outlines these unsafe responsibilities. A valid ABI string is only one line of the proof.
Unwinding and errors need a boundary policy
A Rust panic must not cross an ABI boundary that does not permit unwinding. I catch or prevent panics as appropriate and translate errors into status codes or result records the foreign side understands.
Similarly, foreign exceptions must not cross into Rust through an incompatible declaration. ABI variants containing -unwind have specific semantics and support; I choose them only from a complete cross-language design.
Callbacks need lifetime and thread rules. A C function pointer does not carry a Rust closure environment. User-data pointers, registration lifetime, deregistration, and concurrent invocation all need a wrapper contract.
Verify both sides from generated artifacts
I prefer generated bindings from authoritative headers and a small foreign harness. The harness calls into Rust and, where applicable, Rust calls back out. CI links and executes this on every supported architecture.
Symbol inspection verifies expected exports, while layout assertions verify sizes and offsets. Sanitizers can catch some mistakes, but none compensates for the wrong declaration.
If a service protocol runs over HTTP or sockets, its name belongs in modules and types, not in extern. Network protocols define bytes and messages; an ABI defines in-process calls. Confusing the layers produces exactly the invalid string in this case.
My E0703 checklist
- Is the extern string in rustc's supported calling-convention list for this target?
- What authoritative foreign declaration requires that ABI?
- Was a library or protocol name mistaken for a calling convention?
- Are every parameter and return layout and valid-value set FFI-safe?
- What are ownership, lifetime, thread, and callback rules?
- Can panics or foreign exceptions cross the boundary?
- Do generated bindings match the current header and target?
- Does a real cross-language harness link and execute the release artifact?
The core principle is that an ABI string selects machine calling rules, not application meaning. E0703 catches an unknown selector. I replace it from authoritative target evidence and then validate the complete foreign boundary, where most of the safety work still remains.