What You’ll Need
- Rust toolchain: rustup-managed toolchain set to 1.93.0 (or newer)
- cargo and rustc access via your development environment
- Editor or CI where you can change crate attributes or RUSTFLAGS
- No special prerequisites such as API keys
How It Works
The function_casts_as_integer lint flags places in your code that cast function pointers (fn types or function item types) directly to integer types such as usize, u64, or u32. Rust 1.93.0 turns this lint to warn-by-default so you get notified without changing compiler flags.
When the compiler emits the warning it indicates a potentially unsafe or non-portable operation: function pointers represent code addresses and converting them to integers can break pointer provenance, create UB if later used incorrectly, or fail on exotic targets. The lint helps you decide whether the cast is intentional and safe (for low-level systems code) or a bug and should be refactored.
[rust] function_casts_as_integer lint
🔔 Never Miss a Breaking Change
Get weekly release intelligence — breaking changes, security patches, and upgrade guides before they break your build.
✅ You're in! Check your inbox for confirmation.
![[rust] function_casts_as_integer lint](https://example.com/rust-lint.png)
Why This Lint Matters for Safety
Rust’s safety guarantees depend on the compiler tracking where pointers come from—pointer provenance. Casting a function pointer to an integer erases that provenance. The compiler can no longer reason about what the integer represents, and optimizations like alias analysis can miscompile your code.
This isn’t theoretical. On CHERI capability hardware or Harvard architectures, a function address cast to usize may not round-trip back to a valid pointer. Even on x86-64, ASLR means function addresses change between runs, so serializing or comparing them as integers is fragile.
The lint is part of Rust’s broader strict provenance push—the same effort behind ptr::addr and ptr::with_addr. Flagging these casts at the source helps you write code that stays correct as Rust’s pointer model tightens.
Step-by-Step Guide
- Install or switch to Rust 1.93.0: run
rustup install 1.93.0andrustup override set 1.93.0(orrustup default 1.93.0for your account) so rustc emits the new warn-by-default lint. - Build your crate to see warnings: run
cargo buildorcargo check. The compiler will emit warnings at sites where functions are cast to integers. - Tune lint level per crate: add a crate-level attribute like
#![warn(function_casts_as_integer)]to make the warning explicit, or#![deny(function_casts_as_integer)]to treat it as an error in CI. - Suppress locally if intentional: for a known-safe cast, annotate the item or expression with
#[allow(function_casts_as_integer)]or wrap the specific expression in a small unsafe block and justify it in a comment. - Prefer safer alternatives: when the cast is used for bookkeeping, consider storing function pointers as typed function pointers or as raw pointers (
fn() as *const ()) and avoid serializing addresses. Refactor code to avoid integer-address arithmetic where possible. - Enforce in CI: add
RUSTFLAGS="-D function_casts_as_integer"or set#![deny(function_casts_as_integer)]to block merges that introduce these casts. - Document intentional uses: for low-level code that must cast (JITs, FFI trampolines), add comments and tests showing the platform assumptions and why the cast is safe.
Use Cases & Examples
Debugging and address printing: If you cast a function pointer to usize just to print its numeric address for debugging, the lint will warn. Better: use println!("{:p}", my_fn as *const ()); or restrict debug-only code behind cfg flags.
Function pointer registries for plugins: Some plugin systems keep numeric keys derived from addresses. The lint warns you; instead keep typed function pointers in a table and use stable indices or handles rather than raw addresses.
FFI trampolines and low-level systems code: Code that must produce machine addresses (JITs, trampolines) is a valid use case for casting. Use #[allow(function_casts_as_integer)] locally and include clear safety comments and cross-platform tests.
Code Examples: Before and After the Lint
Here are concrete patterns that trigger the lint and how to fix each one.
Pattern 1: Printing a function address
// ❌ Before — triggers function_casts_as_integer
fn greet() { println!("hello"); }
let addr = greet as usize;
println!("greet lives at 0x{:x}", addr);
// ✅ After — use pointer formatting directly
fn greet() { println!("hello"); }
println!("greet lives at {:p}", greet as *const ());
Pattern 2: Hashing a function pointer for a lookup table
// ❌ Before — casts to u64 to use as hash key
let mut map = HashMap::new();
map.insert(handler_a as u64, "a");
// ✅ After — store typed function pointers directly
let mut map: HashMap<fn(), &str> = HashMap::new();
map.insert(handler_a, "a");
Pattern 3: Intentional low-level cast (JIT / FFI)
// ✅ Suppress locally with justification
#[allow(function_casts_as_integer)]
fn emit_call_trampoline(target: fn()) -> u64 {
// SAFETY: we control the target platform (x86-64 Linux)
// and this address is written into executable JIT memory.
target as u64
}
Migration Guide
Upgrading a codebase to 1.93.0 and seeing dozens of warnings? Here’s the systematic approach.
1. Audit: Run cargo check 2>&1 | grep "function_casts_as_integer" to find every site.
2. Categorize: Most casts fall into three buckets: debug/logging (replace with {:p} formatting), data-structure keys (use typed fn() pointers or opaque handles), and genuine low-level needs (allow with comment).
3. Gate intentional casts: Add #[allow(function_casts_as_integer)] on the smallest scope possible—function or expression, not the whole module. Include a // SAFETY: comment.
4. Lock it down: Once clean, set #![deny(function_casts_as_integer)] or RUSTFLAGS="-D function_casts_as_integer" in CI so new casts fail the build.
Common Issues
- Still seeing no warnings: ensure your project is building with rustc 1.93.0; run
rustc --versionto confirm, then clear target dir and rebuild. - Too many warnings: narrow the scope with
#![warn(function_casts_as_integer)]at crate root and add#[allow(function_casts_as_integer)]to third-party modules or generated code. - False positives in macros or generated code: wrap or annotate the generated code with
#[allow(function_casts_as_integer)]or change the generator to avoid direct casts. - Need to fail CI on these casts: set
RUSTFLAGS="-D function_casts_as_integer"in the CI environment to convert the warnings into errors.
FAQ
Does this lint apply to closures?
No. Capturing closures are anonymous structs implementing Fn/FnMut/FnOnce—you can’t cast them to usize. Non-capturing closures can coerce to fn(), and casting that to an integer will trigger the lint.
Will this become a hard error?
No RFC yet, but the trajectory is clear. Treating this as deny now means you won’t scramble when it tightens.
Does transmute bypass this lint?
Yes. std::mem::transmute uses different syntax than as casts, so this lint doesn’t fire. However, transmute requires unsafe and has its own lint (ptr_to_integer_transmute_in_consts) in const contexts.
What about fn() as *const () as usize?
The lint doesn’t fire—it targets direct function-to-integer casts. Going through *const () first bypasses it, though that second step may be caught by provenance lints in the future.
What’s Next
- Review other pointer-related lints (e.g., pointer-structural issues) and consider tightening levels in CI.
- Explore safer pointer representation patterns: typed handles, stable indices, or
*const ()with careful provenance handling. - For JIT and OS-level code, review platform-specific pointer models and add cross-target tests.
Related Resources
- Parent release guide: (see release guide URL added by parent)
- Official rustc lint documentation: https://doc.rust-lang.org/rustc/lints/index.html