Default derived `Hash` impl for `fn` types can lead to subtle bugs
Nobody has claimed this yet.
- 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
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
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 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