Factory for impl T<const I: usize>

Hi, all. I'm trying to wrap my head around static dispatch and not having much success.

Basically, I have some trait that takes a const generic and a few implementations of that trait which I'd like to choose between at runtime depending on how the program is called. (playground)

There are a few issues here:

  • "match arms return incompatible types." How can I return different implementations of T from the same factory function if not through match?
  • "trait T<I> is not implemented for PathA." (How) can I abstract away the trait's const generic while keeping the associated function interface? I think this is the real roadblock. I've tried switching to associated consts, which feels like a more semantic fit for my application but the language support isn't yet there. But I can use both PathA and PathB interchangeably if I instantiate them manually, so it seems there must be a way...?

You can never return incompatible types. You can however make variants of an enum and return that enum or return a boxed trait object. Both will result in some kind of dynamic dispatch.

This really depends on what you're actually doing with that generic const in the trait. In the example it doesn't nothing so you could just remove it and it should work.

What do you mean the language support isn't yet there? You can already declare associated consts in a trait, so what do you need other than that?

You can never return incompatible types.

But I (think I) should be able to return an impl T somehow, right?

Both will result in some kind of dynamic dispatch

Yep -- I'm trying to avoid this for performance and because static dispatch seems to be the preferred way of doing things, so I'd like to learn how to work with it better. (Also when I tried doing dynamic dispatch I ran into all kinds of Sized headaches.)

depends what you're actually doing with the generic const

Like always, the real situation is pretty complicated. In general, the trait describes some DSP behaviours, which have the same function interface but can take a different number of audio streams in and out. This results in, among other things, an associated function which uses the const generics I and O for the number of samples in and out:

    fn tick(&mut self, in_samp: Samples<I>) -> Samples<O>;

and functions which generate the ports and buffers which also use the generics (ie, to make I × input ports and O × output ports):

    fn get_buffers<'a>(&mut self, /*...*/) -> ([&'a [f32]; I], [&'a mut[f32]; O]);

...language support...

When I replace these with Self::I I get generic parameters may not be used in const operations; when I use Self::I in the function implementation, I get constant expression depends on a generic parameter - note: this may fail depending on what value the parameter takes. I think I read that one or both of these will be available with const_generics, but even enabling that feature on nightly gives a "here be dragons" warning.

I think there's a misunderstanding about what 'incompatible' means here. Types are incompatible for return from a function or match block if they are not either 1) the exact same type or 2) behind a layer of dynamic dispatch (either handwritten and static-ish as with enums, or compiler-written as with Box<dyn Trait>)

impl Trait does not allow you to return multiple types. It prevents knowledge of the type you are returning from being used in the calling function, but it does not allow you to return different types.

impl Trait in argument position is sugar for fn func<T: impl Trait>(arg: T) - it's really a generic, and will be monomorphized as a generic for any given input type.

1 Like

Oh okay, your example illustrating impl Trait as a generic clears it up. So the factory pattern I have in mind basically requires dynamic dispatch? That explains why I was having such a hard time avoiding it...

Thanks @Zarenor and @SkiFire13!

Yes. Generally if something depends on the value that's supplied at runtime you won't be able to use static dispatch, since it requires the actual type to be known at compile time.

Note that that example is for when it's used as a parameter. When it's used as a return type there's no generic involved, it's just a placeholder for a certain type (one type) that you don't want to name.

1 Like