rust-lang / rust-lang/rust

Default derived `Hash` impl for `fn` types can lead to subtle bugs

Open
#130,490 4 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

C-bug T-libs
Dominant language
Rust
Stars
119k
Forks
16.2k
PR merge metrics
PR metrics pending

Description

So I was a bit hesitant to file this bug because the behavior makes sense once you realize what's happening, but I think due to the nature of what can happen it was worth filing an issue. (I briefly searched existing issues and couldn't find anything similar)

Today, #[derive(Hash)] is able to derive an implementation for function pointers used as fields in a struct. For example, imagine:

use std::hash::{Hash, Hasher};

trait ExampleTrait {
  fn example() -> ();
}

#[derive(Hash)]
struct FunctionWrapper {
  func: fn() -> ()
}

impl FunctionWrapper {
  fn new<T: ExampleTrait>(_: T) -> Self {
    Self {
      func: T::example
    }
  }
  
  fn print_hash(&self) {
      let mut hasher = std::hash::DefaultHasher::new();
      self.hash(&mut hasher);
      eprintln!("{}", hasher.finish());
  }
}

struct Example; 

impl ExampleTrait for Example {
    fn example() -> () {
      ()
    }
}

struct Example2;
impl ExampleTrait for Example2 {
    fn example() -> () {
      ()
    }
}

fn main() {
    let example = Example;
    let example2 = Example2;
    
    let wrapper = FunctionWrapper::new(example);
    let wrapper2 = FunctionWrapper::new(example2);
    
    wrapper.print_hash();
    wrapper2.print_hash();
}

Now I would expect that wrapper.print_hash() and wrapper2.print_hash() print different hashes. This works.

   Compiling playground v0.0.1 (/playground)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.49s
     Running `target/debug/playground`
13248834103875764188
6707210748105774930

What's unexpected is the second time I run this,

   Compiling playground v0.0.1 (/playground)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.61s
     Running `target/debug/playground`
5430550945448582829
17979896429865431302

I get different results. When I think about it, this makes sense - you can't make guarantees on where a function pointer is located, and when I look at the Hash implementations I'm guessing this ends up falling into the *const usize Hash implementation. (I was a bit surprised that there was an implementation for hashing raw pointers to be honest)

It's unexpected because typically when Hash provides implementations you half expect them to be semi-stable hashes (at least for the same compiler version), for example you can't derive an implementation for floats because you can't make the same guarantee you get the same float each time (At least that's why I assumed there's no default implementation for f32, f64, etc.) so then you leave it up to the developer to decide they want to handle it.

This would be a hack, but I kind of thought it would essentially end up hashing a string literal like, <Example as ExampleTrait>::example_ptr_0_0 and <Example2 as ExampleTrait>::example_ptr_0_0.

To summarize, I don't think the problem is that the function hashes in an unexpected way, but I do feel like it's an anti-pattern to allow it to be derived in the first place.
 

Meta

https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=ea63c430c7c816134027c5ff33c86821

rustc --version --verbose:

rustc 1.83.0-nightly (9b72238eb 2024-09-14)
binary: rustc
commit-hash: 9b72238eb813e9d06e9e9d270168512fbffd7ee7
commit-date: 2024-09-14
host: x86_64-unknown-linux-gnu
release: 1.83.0-nightly
LLVM version: 19.1.0

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reproducing the linked Rust Playground example and reviewing the reported rustc version and Hash behavior for function-pointer fields. The issue does not name compiler files or tests; done requires an agreed resolution for whether deriving Hash for these fields should be allowed, with the behavior checked against the reproduction.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
compilers
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
32/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.