Unintuitive behaviour with passing a reference to trait object to function

Hello everyone.

I'm learning Rust for almost a month now, and just as I feel like I've grasped all the concepts, something new pops up.

I'm having a problem with passing a std::hash::Hasher trait object into the std::hash::Hash::hash method. Its signature takes a mutable reference to a generic Hasher:

fn hash<H: Hasher>(&self, state: &mut H);

I have a function that takes a dyn Hasher:

fn hash_something_with(hasher: &mut dyn Hasher) { ... }

It seems obvious to me, that state in Hash::hash and hasher in my function are of the same type, so I can just pass it through:

fn hash_something_with(hasher: &mut dyn Hasher) {
    123i32.hash(hasher);
}

However, it doesn't compile:

error[E0277]: the size for values of type `dyn std::hash::Hasher` cannot be known at compilation time
 --> src/main.rs:6:16
  |
6 |    123i32.hash(hasher);
  |                ^^^^^^ doesn't have a size known at compile-time
  |
  = help: the trait `std::marker::Sized` is not implemented for `dyn std::hash::Hasher`
  = note: to learn more, visit <https://doc.rust-lang.org/book/ch19-04-advanced-types.html#dynamically-sized-types-and-the-sized-trait>

This would be reasonable, if I hadn't passed it as a reference. It almost seems to me that compiler implicitly dereferences hasher before passing it to hash.
But if I do:

fn hash_something_with(hasher: &mut dyn Hasher) {
    123i32.hash(&mut *hasher);
}

... nothing changes.
If I make my argument mutable and then take another reference of it, it suddenly works:

fn hash_something_with(mut hasher: &mut dyn Hasher) { // passed in a mutable reference to a mutable Hasher
    123i32.hash(&mut hasher); // passing in a mutable reference to a mutable variable that is a mutable Hasher...
}

And I can't understand why, because &mut hasher should be a reference to a reference (&mut &mut dyn Hasher), and it doesn't match Hash::hash signature. And to top it all off, if I use an std::hash::BuildHasher and build an instance of std::hash::Hasher inside of my function, passing a reference to that instance also works! Even though it's passed as a direct reference.

I'm totally lost here. Any help will be greatly appreciated.

Here's the full code for demonstration and a link to playground:


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

// DOESN'T WORK:
// fn demo1(hasher: &mut dyn Hasher) {
//    123i32.hash(hasher);
// }

// WORKS:
fn demo2(mut hasher: &mut dyn Hasher) { // passed in a mutable reference to a mutable Hasher
    123i32.hash(&mut hasher); // passing in a mutable reference to a mutable variable that is a mutable Hasher...
}

// STRANGELY WORKS:
fn demo3<H: Hasher>(factory: &dyn BuildHasher<Hasher = H>) {
    let mut hasher = factory.build_hasher(); // creating a mutable Hasher
    123i32.hash(&mut hasher); // passing in a mutable reference to i32::hash()
}

fn main() {
    let factory = std::collections::hash_map::RandomState::new();
    let mut hasher = factory.build_hasher();
    
    demo2(&mut hasher);
    demo3(&factory);
}

(Playground)

Generic type parameters (e.g., <H : Hasher>) have an implicit : Sized bound, and dyn Hasher is not Sized.

On the other hand, H = &'_ mut dyn Hasher is Sized (and Hasher itself too), so you can use a &mut &mut dyn Hasher as your input: Playground


The fact that the Hash trait uses an implicit Sized bound on its generic parameter looks like an oversight / retrocompatibility artifact. I am pretty sure that a modern version of the trait would be using <H : ?Sized + Hasher>

1 Like

Thanks for your reply. However, I know about Sized bounds, and I've even written about that in my post (well, indirectly, when I talked about references).

The question is, why does std::hash::Hash::hash accept &mut &mut dyn Hasher when its signature is specified as &mut H. And then, why does taking a reference from a local hasher (demo3 function in my playground) also works, even though clearly the type of &mut hasher there differs from one from demo2.

demo3 works because you are creating a H hasher, where H : Hasher /* + Sized */, there is no dyn Hasher there.

demo2 works because for all <T : ?Sized> the type H = &'_ mut T is itself Hasher when T : Hasher, thanks to a generic impl that delegates to the referred Thing: https://doc.rust-lang.org/1.40.0/src/core/hash/mod.rs.html#362-405

In your case, T = dyn Hasher so H = &mut dyn Hasher, which becomes Sized thanks to indirection (a reference is always Sized, in the case of a reference to a dyn Trait object it has the size of two pointers, as explained in this other post of mine: What's the difference between T: Trait and dyn Trait?). Since the .hash() function takes a &mut (H), that leaves us with a &mut (&mut dyn Hasher)

3 Likes

Thanks for the explanation!

So, just to be clear, am I right in saying that without impl<H: Hasher + ?Sized> Hasher for &mut H { ... }, it wouldn't be possible to pass dyn Hasher into .hash() at all (so demo2 wouldn't work as well)?

Well almost. If everything else was the same except that specific impl was removed, then you would indeed not be able to pass your mutable reference, and you also wouldn't be able to create the impl yourself as Hasher is not defined in your code.

That said, it would be possible to create your own MyHasher type that contains a &mut dyn reference, and implement Hasher on that type. See playground. And you would then be able to pass a MyHasher::new(my_dyn_reference) to something accepting a sized hasher.

Of course this would be a major pain, which is why the &mut H impl is in the standard library.

2 Likes

Fundamentally this boils down to the fact that traits are not implemented for references, even if they are implemented for the referenced type. This is the unintuitive part that caused the problem.

Here are some SO Q&A on the topic:


Now the question arises why Rust is made that way, but that's a different story.
Thanks everybody for help!

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.