rust-lang / rust-lang/rust-clippy
unnecessary_cast: type-alias suppression depends on how the casted local was initialized (mem::zeroed vs MaybeUninit::assume_init)
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 13.5k
- Forks
- 2.2k
- Avg merge
- 2d 10h
- Merged PRs (30d)
- 32
Description
Summary
unnecessary_cast suppresses casts of values whose type might be a cfg-dependent type alias (e.g. libc struct fields) by conservatively skipping when the cast source involves an external function it can't inspect. But that suppression only matches free functions (Res::Def(DefKind::Fn, _) on a path expr), so two semantically equivalent initializations of the same local lint differently: initializing via std::mem::zeroed() suppresses the lint, while initializing via MaybeUninit::uninit() + .assume_init() does not — assume_init is a method call (no callee path expr) and MaybeUninit::uninit resolves to DefKind::AssocFn, which the check doesn't match.
The cast in the reproducer is genuinely platform-dependent (statvfs.f_blocks is fsblkcnt_t, not u64 on every target), so the lint's own rationale for the alias suppression applies equally to both forms. The practical effect is that migrating FFI out-params from mem::zeroed() to the more idiomatic MaybeUninit surfaces this false positive and forces an #[allow].
Lint Name
unnecessary_cast
Reproducer
I tried this code (with libc = "0.2", on x86_64-unknown-linux-gnu):
fn with_zeroed(path: &std::ffi::CStr) -> u64 {
let mut s: libc::statvfs = unsafe { std::mem::zeroed() };
unsafe { libc::statvfs(path.as_ptr(), &mut s) };
s.f_blocks as u64
}
fn with_maybe_uninit(path: &std::ffi::CStr) -> u64 {
let mut s = std::mem::MaybeUninit::<libc::statvfs>::uninit();
unsafe { libc::statvfs(path.as_ptr(), s.as_mut_ptr()) };
let s = unsafe { s.assume_init() };
s.f_blocks as u64
}
I expected to see this happen: both functions treated the same — no lint on either, since f_blocks is the cfg-dependent alias fsblkcnt_t and the cast is required on targets where it isn't u64.
Instead, this happened: only the MaybeUninit version lints:
warning: casting to the same type is unnecessary (`u64` -> `u64`)
--> src/main.rs:11:5
|
11 | s.f_blocks as u64
| ^^^^^^^^^^^^^^^^^ help: try: `s.f_blocks`
The cause is in is_cast_from_ty_alias: walking the local's initializer, std::mem::zeroed is a path resolving to DefKind::Fn and hits the "External function, we can't know, better be safe" break, while MaybeUninit::uninit resolves to DefKind::AssocFn (not matched) and assume_init is an ExprKind::MethodCall (never seen as a path), so no suppression applies. Field accesses on external structs aren't examined at all, so the actual alias (fsblkcnt_t) is invisible either way.
Related: #8018, #8093 (alias/cfg-dependent FPs in this lint), #6466 (same libc-field pattern in useless_conversion).
Version
rustc 1.96.0 (ac68faa20 2026-05-25)
clippy 0.1.96 (ac68faa20c 2026-05-25)
Additional Labels
@rustbot label +I-false-positive
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in clippy_lints/src/casts/unnecessary_cast.rs at is_cast_from_ty_alias, then compare how the reproducer’s mem::zeroed and MaybeUninit initialization expressions are resolved while walking the local initializer. Use the provided with_zeroed and with_maybe_uninit examples as the regression case; done means both equivalent initializations avoid the unnecessary_cast warning for the cfg-dependent libc field.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- tooling
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 76/100