Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rust cannot infer type without annotation

Tags:

types

rust

The code below cannot compile due to the type error error[E0283]: type annotations required: cannot resolve _: std::cmp::Eq. What type annotation is needed in order to compile this code?

This example code is taken from a much larger program where MyHashGenerator::hash_node() is used to hash nodes in an AST (the type T relates to a value that is held inside AST nodes, but the definition of the AST is not needed to reproduce the type error).

use std::hash::Hash;

pub trait HashGenerator<T: Clone + Eq + Hash + ToString> {
    fn hash(&self, msg: &str) -> u64;  // Hash a string.
    fn hash_node(&self) -> u64;  // Hash an AST node.
}

struct MyHashGenerator {}

impl<T: Clone + Eq + Hash + ToString> HashGenerator<T> for MyHashGenerator {
    fn hash(&self, msg: &str) -> u64 {
        0
    }

    fn hash_node(&self) -> u64 {
        // error[E0283]: type annotations required: cannot resolve `_: std::cmp::Eq`
        self.hash("")
    }
}

Code on Playground.

This question is similar to the one here, which has not been answered.

like image 208
snim2 Avatar asked Jan 28 '23 10:01

snim2


1 Answers

The problem is MyHashGenerator doesn't take a type argument. So you're making a promise "The same type MyHashGenerator will suffice as a hash generator and will behave the exact same way regardless of T". self.hash("") is a call to hash on HashGenerator, but Rust doesn't necessarily know that it's the same HashGenerator instance that hash_node was called on. You can make this requirement explicit in one of two ways.

Option 1: Explicit Type Arguments

By explicitly telling Rust what the arguments are, you can avoid this problem.

fn hash_node(&self) -> u64 {
    HashGenerator::<T>::hash(self, "")
}

Now it knows to call specifically the <T> instance, which will suffice.

Option 2: Phantom data

You can parameterize MyHashGenerator to have an (unused) T parameter.

use std::marker::PhantomData

...

struct MyHashGenerator<T> {
    foo: PhantomData<T>
}

Then Rust can infer which instance you want based on the type of self, so long as you declare your instance as

impl<T: Clone + Eq + Hash + ToString> HashGenerator<T> for MyHashGenerator<T> {
    ...
}

Then you don't have to change your implementation of hash_size.

Personally, I recommend Option 1. It's not as pretty, but it provides the additional API guarantee that things will in fact work the same way regardless of T. However, if you think MyHashGenerator might use the T argument later (or if it does now, and that simply wasn't included in your MCVE), you may consider going with Option 2 to make that dependency more clear.

like image 85
Silvio Mayolo Avatar answered Feb 03 '23 14:02

Silvio Mayolo