Skip to content

[Coding Guideline]: Do Not Depend on Function Pointer Identity Across Crates #255

@rcseacord

Description

@rcseacord

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

category: requiredA coding guideline with category requiredchapter: associated-itemscoding guidelineAn issue related to a suggestion for a coding guidelinedecidability: decidableA coding guideline which can be checked automaticallyscope: systemA coding guideline that can be determined applied only when entire source is inspectedstatus: draft

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions