Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Closures as a type in a Rust struct

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?

like image 889
lesurp Avatar asked Dec 11 '16 02:12

lesurp


People also ask

What are Rust closures?

Rust's closures are anonymous functions you can save in a variable or pass as arguments to other functions. You can create the closure in one place and then call the closure elsewhere to evaluate it in a different context. Unlike functions, closures can capture values from the scope in which they're defined.

What is a closure type?

A closure expression produces a closure value with a unique, anonymous type that cannot be written out. A closure type is approximately equivalent to a struct which contains the captured variables.

How are Rust closures implemented?

By taking &Fn(f64) , you've restricted yourself to only accepting closures which require immutable access to their context. If you want a closure to be able to mutate its context, you need to use FnMut instead.

What is the type of struct Rust?

Structs in Rust come in three flavors: Structs with named fields, tuple structs, and unit structs. Regular structs are the most commonly used. Each field defined within them has a name and a type, and once defined can be accessed using example_struct.


1 Answers

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));
}
like image 198
Francis Gagné Avatar answered Oct 03 '22 18:10

Francis Gagné