urubi
urubi

Reputation: 917

How can generic closures be stored in generic structures?

I have a generic structure I want to store a closure in. This closure's parameter and return types is directly related to the structure's final type. How can I define such a structure?

The only way I know how is to composite types using the where keyword. However when attempting to compile the code, the compiler halts and complains that T and 't are not used.

I could get it to compile by adding useless variables that use T and 't, but I imagine that there is a better, more correct why to do this.

The following code (playpen) compiles. Remove the useless variables and it won't:

pub struct GenericContainer<'t, T: 't, F> where F: Fn(&'t [T]) -> Option<&'t [T]> {
    generic_closure: F,
    unused: Option<&'t T>
}

impl<'t, T: 't, F> GenericContainer<'t, T, F> where 
    F: Fn(&'t [T]) -> Option<&'t [T]> {

    pub fn new(gc: F) -> Self {
        GenericContainer {
            generic_closure: gc,
            unused: None
        }
    }

    pub fn execute(&self, slice: &'t [T]) -> Option<&'t [T]> {
        let g = &self.generic_closure;
        let _ = &self.unused;
        g(slice)
    }
}

fn main() {
    let g = GenericContainer::new(|s| if s.len() == 0 {None} else {Some(&s[..1])});
    assert!(g.execute(b"   ") == Some(b" "));
    println!("ok");
}

Upvotes: 2

Views: 80

Answers (1)

Levans
Levans

Reputation: 14992

You are in a typical "higher-ranked-stuff" situation: 't and T are only used to define the type F.

First, for the lifetime, you can use the higher-ranked-lifetimes syntax of rust:

where F: for<'t> Fn(&'t [T]) -> Option<&'t [T]>

and thus remove it from you struct type.

It would be nice to do something similar for T, but higher-ranked-types are currently not supported by Rust. So you best catch is probably to follow the suggestion of the compiler, and use std::maker::PhantomData

You end up with a type like:

pub struct GenericContainer<T, F>
    where F: for<'t> Fn(&'t [T]) -> Option<&'t [T]>
{
    generic_closure: F,
    _marker: ::std::marker::PhantomData<T>
}

Note that prefixing _marker with _ prevents it to be detected as unused.

Then, you just need to update your implementation:

impl<T, F> GenericContainer<T, F> where 
    F: for<'t> Fn(&'t [T]) -> Option<&'t [T]> {

    pub fn new(gc: F) -> Self {
        GenericContainer {
            generic_closure: gc,
            _marker: ::std::marker::PhantomData
        }
    }

    pub fn execute<'t>(&self, slice: &'t [T]) -> Option<&'t [T]> {
        let g = &self.generic_closure;
        g(slice)
    }
}

Upvotes: 3

Related Questions