Optional generic type to build new struct

Hello,

Here is one of my problems:

I added comments to the code where it fails.

I want to return a new generic struct from a function by using a kind of optional generic type, but I need to cast to something that is nothing in fact... I'm lost and I don't want to cast anything if it is possible.

And I want to use Generic<impl A,impl B> parameter to fill a Vec<impl A,impl B> which is not permitted. Maybe it is impossible!

Thank you in advance for your help.

enum variants are not stand-alone types; they have the type of the enum. So a None might be an Option<String> or Option<i32>, etc; there's not one None across all Option<T>s.

So you have to let the compiler know what the type of the b fields should be.

// I moved this to a new block to support non-`Sized` `B`
// since your example didn't have any implementors
impl<AS: A, BS: B + ?Sized> G<AS, BS> {
    fn new_a_only(a: AS) -> Self {
        Self {
            a: Some(Box::new(a)),
            b: None,
        }
    }
}

impl Demo {
    // I added the `?Sized` here too
    fn new_from_a_only() -> G<Demo, impl B + ?Sized> {
        // Then the type can be specified here
        G::<_, dyn B>::new_a_only(Self {})
    }
}

It's possible you may have thought the caller could choose any implementor of B, but this is not the case. When you have impl Trait in return position (RPIT), it's an opaque type but not a generic type parameter. Underneath, it must be a single concrete type determined by the function body (and not the caller).

If you wanted the caller to be able to choose the type, I show how to do so further below.


Your code seems to demonstrate some more general confusion around generics, dyn Trait, and impl Trait. First, be aware that dyn Trait is it's own, concrete type. So in this type for example:

struct S {
    vec: Vec<G<dyn A, dyn B>>,
}

You can't just push any G<_, _> into your Vec, you need to push a G<dyn A, dyn B> specifically -- which is a single, concrete type.

Next consider this function:

impl S {
    // impl seems more appropriate for my needs
    fn load(&mut self, s: G<impl A, impl B>) {

impl Trait in argument position (APIT) such as this is a form of generics, and is pretty much the same as this:

impl S {
    fn load<AS: A, BS: B>(&mut self, s: G<A, B>) {

The way that generic function parameters work, including APIT, is that the caller chooses the type of the generics, which have to meet the trait bounds, and the function body has to work for any and every set of possible types that meet the trait bounds.

This line fails to compile:

    fn load(&mut self, s: G<impl A, impl B>) {
        self.vec.push(s); // fails
    }

And given what I've said so far, I hope the reason is clear: The caller of the function can choose any G<AS, BS> such that AS: A and BS: B, but your Vec requires G<dyn A, dyn B> specifically.

There is a way forward, though I don't know if this is what you intended. With some adjusting to the bounds, you can coerce the fields of the G argument to create a G<dyn A, dyn B>:

    fn load(&mut self, s: G<impl A + 'static, impl B + 'static>) {
        let s = G {
            a: s.a.map(|a| a as Box<dyn A>),
            b: s.b.map(|b| b as Box<dyn B>),
        };
        self.vec.push(s);
    }

(You need the extra 'static bound because the Vec is more explicitly a Vec<dyn A + 'static, dyn B + 'static>.)

Finally, revisiting the signature here:

impl Demo {
    fn new_from_a_only() -> G<Demo, impl B + ?Sized> {

This is impl Trait in return position (RPIT). As mentioned above, and unlike APIT, this is not a generic type parameter. The caller doesn't choose what impl B is here, the function body determines what impl B is. The underlying type is opaque to the caller, but it must be one specific, concrete type that meets the stated bounds.

The difference from generics may be part of what confused you with regard to None.

If you wanted the caller to be able to choose the type for the b field, that would look like this instead:

    fn other_new_from_a_only<BS: B + ?Sized>() -> G<Demo, BS> {
        G::new_a_only(Self {})
    }

4 Likes

Very great and detailed explaination. Thank you.
I use an Arc Mutex instead of a Box and I get a non primitive casting error.
It seems I have to implement the Mutex: is it true ?

Arc is used instead of Box when atomic reference counting is needed -- both of them allocate and wrap the value, so you don't need to nest them (which does two allocations).

So replace this:
Arc<Mutex<Box<dyn B>>>
with this:
Arc<Mutex<dyn B>>

The smart pointers chapter of the book may help.

3 Likes

Thank you all.
You point me to the right direction because of the confusion between dyn Trait and impl Trait.
Finally, I need to redesign my code...

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.