-
Notifications
You must be signed in to change notification settings - Fork 28
Description
Chapter
Associated Items
Guideline Title
Do Not Depend on Function Pointer Identity Across Crates
Category
Required
Status
Draft
Release Begin
unclear
Release End
latest
FLS Paragraph ID
fls_1kg1mknf4yx7
Decidability
Decidable
Scope
System
Tags
surprising-behavior
Amplification
Do not rely on the equality or stable identity of function pointers originating from different
crates or that may be inlined, duplicated, or instantiated differently across compilation units,
codegen units, or optimization profiles.
Avoid assumptions about low-level metadata (such as symbol addresses) unless explicitly guaranteed by the Ferrocene Language Specification (FLS).
Function address identity is not guaranteed by Rust and must not be treated as stable.
Rust's fn type is a zero-sized function item promoted to a function pointer,
whose address is determined by the compiler backend.
When a function resides in a different crate,
or when optimizations such as inlining, link-time optimization, or codegen-unit partitioning are enabled,
the compiler may generate multiple distinct code instances for the same function or
alter the address at which it is emitted.
Consequently, the following operations are not reliable:
- Comparing function pointers for equality (
fn1 == fn2) - Assuming a unique function address
- Using function pointers as identity keys (e.g., in maps, registries, matchers)
- Matching behavior based on function address
This rule applies even when the functions are semantically identical, exported as pub, or defined once in source form.
Exception(s)
#[no_mangle] functions are guaranteed to have a single instance.
Rationale
Compiler optimizations may cause function pointers originating from different crates to lose stable identity. Observed behaviors include:
- Cross-crate inlining producing multiple code instantiations
- Codegen-unit separation causing function emission in multiple units
- Incremental builds producing variant symbol addresses
- Link-time optimization merging or splitting functions unpredictably
This behavior has resulted in real-world issues, such as the bug reported in rust-lang/rust#117047, where function pointer comparisons unexpectedly failed due to cross-crate inlining.
Violating this rule may cause:
- Silent logic failures: callbacks not matching, dispatch tables misbehaving.
- Inappropriate branching: identity-based dispatch selecting wrong handler.
- Security issues: adversary-controlled conditions bypassing function-based authorization/dispatch logic.
- Nondeterministic behavior: correctness depending on build flags or incremental state.
- Test-only correctness: function pointer equality passing in debug builds but failing in release/LTO builds.
In short, dependence on function address stability introduces non-portable,
build-profile-dependent behavior, which is incompatible with high-integrity Rust.
Non-Compliant Example - Prose
Due to cross-crate inlining or codegen-unit partitioning, the address of handler_a in crate B may differ from its address in crate A, causing comparisons to fail as shown in this noncompliant code example:
Non-Compliant Example - Code
// crate A
pub fn handler_a() {}
pub fn handler_b() {}
rust
// crate B
use crate_a::{handler_a, handler_b};
fn dispatch(f: fn()) {
if f == handler_a {
println!("Handled by A");
} else if f == handler_b {
println!("Handled by B");
}
}
dispatch(handler_a);
// Error: This may fail unpredictably if handler_a is inlined or duplicated.
Compliant Example - Prose
Replace function pointer comparison with an explicit enum as shown in this compliant example:
Compliant Example - Code
// crate A
pub enum HandlerId { A, B }
pub fn handler(id: HandlerId) {
match id {
HandlerId::A => handler_a(),
HandlerId::B => handler_b(),
}
}
// crate B
use crate_a::{handler, HandlerId};
fn dispatch(id: HandlerId) {
handler(id);
}
dispatch(HandlerId::A); // OK: semantically stable identity
Metadata
Metadata
Assignees
Labels
Type
Projects
Status