Skip to content

unnecessary_cast: type-alias suppression depends on how the casted local was initialized (mem::zeroed vs MaybeUninit::assume_init) #17166

@Sohex

Description

@Sohex

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    I-false-positiveIssue: The lint was triggered on code it shouldn't have

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions