Understanding unused type parameters


#1

I’m tinkering with a for-fun port of Redux to Rust, to try to get a better handle on the way Rust handles generics and closures. I’ve run into a bit of a snag.

This works, but requires an additional temporary variable that I’d like to eliminate:

pub trait Reducer<S, A> {
    fn reduce(&self, state: &S, action: &A) -> S;
}

impl<S, A, F: Fn(&S, &A) -> S> Reducer<S, A> for F {
    fn reduce(&self, state: &S, action: &A) -> S {
        self(state, action)
    }
}

pub struct Store<'a, S, A> where
    S: 'a,
    A: 'a {
    state: S,
    reducer: &'a Reducer<S, A>,
}

impl<'a, S, A> Store<'a, S, A> {
    pub fn from_state(initial_state: S, reducer: &'a Reducer<S, A>) -> Store<'a, S, A> {
        // …
    }
}

The offending temporary:

fn add(state: &i32, action: &i32) -> i32 {
    state + action
}

let reducer = &add; // <-- cannot be inlined
let mut store = redux::Store::from_state(5, reducer);

This doesn’t compile, and complains about A:

// Reducer is unchanged.

pub struct Store<S, A, R: Reducer<S, A>> {
    state: S,
    reducer: R,
}
error[E0392]: parameter `A` is never used
 --> src/store.rs:8:21
  |
8 | pub struct Store<S, A, R: Reducer<S, A>> {
  |                     ^ unused type parameter

Which is a pity, because I was hoping I could do this and eliminate the temporary.

fn add(state: &i32, action: &i32) -> i32 {
    state + action
}

let mut store = redux::Store::from_state(5, add);

As far as I can see, the A type parameter is actually used in the latter definition of Store, but only in the definition of further type parameters. How would I satisfy the compiler, here?


#2

That can become

use std::marker::PhantomData;
pub struct Store<S, A,R: Reducer<S, A>> {
    state: S,
    reducer: R,
    _phantom:PhantomData<A>
}

https://is.gd/4iu3UP
The code I’ve been writing is very generic heavy, and I’m often needing to use this. It’s an unfortunate amount of verbosity for something which feels like it should be easily inferred by the compiler.


#3

@tupshin Thanks. The compiler makes the same suggestion. Adding a dummy field works, but I was (and am) hoping for something more elegant.

I had the thought that the compiler is right, and that A really isn’t used in the body of struct Store. It, and the constraint that R be some type implementing Reducer<S, A>, are only relevant to the impl, not to the struct.

This is legal:

pub struct Store<S, R> {
    state: S,
    reducer: R,
}

fn new<S, R, A>(state: S, reducer: R) -> Store<S, R>
    where R: Reducer<S, A> {
    Store {
        state: state,
        reducer: reducer,
    }
}

This is not, for reasons I’m not 100% clear on but willing to accept on faith:

impl<S, R, A> Store<S, R>
    where R: Reducer<S, A> {
    fn new(state: S, reducer: R) -> Self {
        Store {
            state: state,
            reducer: reducer,
        }
    }
}
error[E0207]: the type parameter `A` is not constrained by the impl trait, self type, or predicates
  --> src/store.rs:13:12
   |
13 | impl<S, R, A> Store<S, R>
   |            ^ unconstrained type parameter

I finally settled on this:

impl<S, R> Store<S, R> {
    fn new<A>(state: S, reducer: R) -> Self
        where R: Reducer<S, A> {
        Store {
            state: state,
            reducer: reducer,
        }
    }
}

This works, although I find myself repeating the R: Reducer<S, A> constraint on each method in the impl.