8

I am trying to create a struct like this in Rust:

pub struct Struct<T, F>
    where T: Eq,
          T: Hash,
          F: Fn() -> T
{
    hashMap: HashMap<T, F>,
    value: T,
}

My constructor looks like this:

pub fn new(init_value: T) -> Struct<T, F> {
    Struct {
        hashMap: HashMap::new(),
        value: init_state,
    }
}

However when trying to instantiate the class, using let a = Struct::<MyEnum>::new(MyEnum::Init);, the compiler complains that the generics needs two arguments (expected 2 type arguments, found 1)

I saw here that this code works:

fn call_with_one<F>(some_closure: F) -> i32
    where F: Fn(i32) -> i32 {

    some_closure(1)
}

let answer = call_with_one(|x| x + 2);

I guess the problem comes from me having another generic in my template instantiation, but how can I do that?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
lesurp
  • 343
  • 4
  • 19

1 Answers1

11

Struct::new doesn't have any parameter that depends on F, so the compiler is unable to infer what type it should use for F. If you called a method later that used F, then the compiler would use that information to figure out the Struct's concrete type. For example:

use std::hash::Hash;
use std::collections::HashMap;

pub struct Struct<T, F>
    where T: Eq,
          T: Hash,
          F: Fn() -> T,
{
    hash_map: HashMap<T, F>,
    value: T,
}

impl<T, F> Struct<T, F>
    where T: Eq,
          T: Hash,
          F: Fn() -> T,
{
    pub fn new(init_value: T) -> Struct<T, F> {
        Struct {
            hash_map: HashMap::new(),
            value: init_value,
        }
    }

    pub fn set_fn(&mut self, value: T, func: F) {
        self.hash_map.insert(value, func);
    }
}

fn main() {
    let mut a = Struct::new(0);
    a.set_fn(0, || 1); // the closure here provides the type for `F`
}

There's a problem with this though. If we call set_fn a second time with a different closure:

fn main() {
    let mut a = Struct::new(0);
    a.set_fn(0, || 1);
    a.set_fn(1, || 2);
}

then we get a compiler error:

error[E0308]: mismatched types
  --> <anon>:33:17
   |
33 |     a.set_fn(1, || 2);
   |                 ^^^^ expected closure, found a different closure
   |
   = note: expected type `[closure@<anon>:32:17: 32:21]`
   = note:    found type `[closure@<anon>:33:17: 33:21]`
note: no two closures, even if identical, have the same type
  --> <anon>:33:17
   |
33 |     a.set_fn(1, || 2);
   |                 ^^^^
help: consider boxing your closure and/or using it as a trait object
  --> <anon>:33:17
   |
33 |     a.set_fn(1, || 2);
   |                 ^^^^

As mentioned by the compiler, each closure expression defines a brand new type and evaluates to that type. However, by defining Struct the way you did, you are forcing all functions in the HashMap to have the same type. Is that really what you want?

If you want map different values of T to possibly different types of closures, then you'll need to use trait objects instead of generics, as suggested by the compiler. If you want the struct to own the closure, then you'll have to use a Box around the object type.

pub struct Struct<T>
    where T: Eq,
          T: Hash,
{
    hash_map: HashMap<T, Box<Fn() -> T + 'static>>,
    value: T,
}

set_fn could then look like this:

pub fn set_fn<F: Fn() -> T + 'static>(&mut self, value: T, func: F) {
    self.hash_map.insert(value, Box::new(func));
}
Francis Gagné
  • 60,274
  • 7
  • 180
  • 155
  • Thank you ! That did solve my issue. I don't understand the `T + 'static` syntax though. What is this (so I can read the appropriate doc) ? Is this about the lifetime ? – lesurp Dec 12 '16 at 06:06
  • `'static` here is a _lifetime bound_. It restricts the lifetime of borrowed pointers in the type that implements `Fn() -> T`. `'static` means that the type cannot contain any borrowed pointers shorter than `'static` (a type with no borrowed pointers at all is OK). If you need more flexibility, you could introduce a lifetime parameter (`<'a, T>`) on `Struct` and use that instead. – Francis Gagné Dec 12 '16 at 15:34