Trying to 'eliminate' a Generic Parameter

Consider the following:

use super::*;

pub trait FrpNode<Out> {
    fn update(&self);
    fn get(&self) -> (u32, Ref<Out>);
}

pub trait FrpNodeAny {
    fn update(&self);
}

impl<T, Out> FrpNodeAny for T
where
    T: FrpNode<Out>,
{
    fn update(&self) {
        FrpNode::<Out>::update(self);
    }
}

I want to keep FrpNode<Out> the same. I want to declare a new trait FrpNodeAny which doesn't care about the Out parameter. (The goal is to create a vec of HETEROGENEOUS FrpNode<Out>'s, and call the update function on them.) EDIT: Vecs need to be HOMOGENEOUS, so instead of FrpNode<Out> with different Out parameter I want to have a FrpNodeAny trait.

Clearly I'm doing something wrong. What is the way to fix this?

EDIT: Sorry for not being clear. Here is the compile error:

   |
12 | impl<T, Out> FrpNodeAny for T
   |         ^^^ unconstrained type parameter

You're not doing anything "wrong" per se, it's not possible to have a Vec<T> where T have different Out params in FrpNode<Out>s -- Vec<T>s must have every T the same size, and different Outs break that constraint.

One thing you can do is have a Vec<Box<dyn FrpNodeAny>>, and the boxed pointer would be the same size in the Vec, and thus you are holding different FrpNode<Out>s, just not storing the objects directly in the Vec.

@azriel91: Sorry for not being clear. The above code does not compile. I have edited the post to include the compiler error.

The "what am I doing wrong" is referring to "why does this code not compile"

Is Ref referring to std::cell::Ref?
This is irrelevant. I changed the return type to something else and your error appears.

Should Out be an associated type?

pub trait FrpNode {
    type Out;
    fn update(&self);
    fn get(&self) -> (u32, Ref<Self::Out>);
}
2 Likes

FWIW, I've made this abomination that compiles (playpen):

pub struct Ref<Out>(pub Out);

pub trait FrpNode<Out> {
    fn update(&self);
    fn get(&self) -> (u32, Ref<Out>);
}

pub trait FrpNodeAny {
    fn update<Out>(&self) where Self: FrpNode<Out>;
}

impl<T> FrpNodeAny for T {
    fn update<Out>(&self)
    where
        Self: FrpNode<Out>,
    {
        FrpNode::update(self);
    }
}

This is problematic because you can't use FrpNodeAny without first proving FrpNode<Out> for some out. This just pushes the problem to the users, which is probably not what we want.

2 Likes

Associated Type appears to have fixed my problems.

Is there an "Rust Associated Types" cookbook somewhere? There has been quite a few times where I run into a problem, the solution is associated types, but even after it works, I still have no idea why the compiler is now happy. I'm looking for a list of pairs of the form

  • here's a problem you might try to solve via Generics, and get this error
  • and here's how you solve it via Associated types

EDIT: This still doesn't work (compile error pushed elsewhere). I'm currently figuring out a minimal example that captures all the requirements.

I think it boils down to the following questions:

  • Does my type consume Out (i.e. it's either part of the type of a struct field or a method parameter type)?
  • Does my type produce Out (i.e.it's part of a return type of a method)?

If you can answer the first question with a "No" and the second with a "Yes", it should probably be an associated type. That's what I know from contra- and covariance, the latter referring to what associated types are enabling.

1 Like

Associated types won't help. They're not object-safe. (in order to create a trait object, the associated types must all be specified; dyn FrpNode<Out=T>)

Can't you split the functions that involve the generic type parameter out of the trait? So that you have an object-safe trait with update, and a non-object safe trait with get()?

pub struct Ref<Out>(pub Out);

pub trait Update {
    fn update(&self);
}

pub trait FrpNode: Update {    
    type Out;

    fn get(&self) -> (u32, Ref<Self::Out>);
}

fn main() {
    let v: Vec<Box<dyn Update>>;
}

(oops, posted wrong link at first!)
Fixed link: Rust Playground


If you can answer the first question with a "No" and the second with a "Yes", it should probably be an associated type. That's what I know from contra- and covariance, the latter referring to what associated types are enabling.

FYI, associated types have nothing to do with what Rust calls variance. (associated types are always invariant in all of their type parameters, including the Self type). And associated types can frequently be input. The deciding factor is whether the type parameter should be uniquely specified by all of the other types, or if multiple possible choices should be allowed.

4 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.