Assign concreate type to generic var

I am confused.... still learning.
How can I assign a concrete struct to current_screen.
My PlayerScreen struct implements ScreenTrait. But compiler refuses.

expected type parameter S found struct PlayerScreen

pub trait ScreenTrait {
    fn update(&mut self) -> bool;
}

pub struct Program<S> where S: ScreenTrait {
    current_screen: S
}

impl<S> Program<S> where S: ScreenTrait {
    
    pub fn run(&mut self)     {
        let mut running: bool = true;

        self.current_screen = PlayerScreen::new(); // ???

        while running {
            self.update();
        }
    }

    fn update(&mut self) {
        self.current_screen.update();
    }    
}

I think you have multiple options here:

Option 1 - Implement Program for PlayerScreen

Link to playground

Note however that we can now only call run when we have created Program with PlayerScreen as it's type parameter.

Please also see option 2 and 3 below, which might be what you actually want.

impl Program<PlayerScreen> {
    
    pub fn run(&mut self)     {
        let mut running: bool = true;

        self.current_screen = PlayerScreen::new();

        while running {
            self.update();
        }
    }

    fn update(&mut self) {
        self.current_screen.update();
    }    
}

Option 2 - Be generic over Program and provide a new function to the trait ScreenTrait

Link to playground

We add a new method to ScreenTrait, so that we can create new objects, but we can still be generic over S when implementing Program:

pub trait ScreenTrait {
    fn new() -> Self; 
    fn update(&mut self) -> bool;
}
impl ScreenTrait for PlayerScreen {
    fn update(&mut self) -> bool {
        true
    }
    fn new() -> Self {
        Self
    }
}
// ...
// some other code omitted
// ...
impl<S: ScreenTrait> Program<S> {
    
    pub fn run(&mut self)     {
        let mut running: bool = true;

        self.current_screen = S::new();
                          // ^^^^^^^^^^ we can now instantiate it via our generic `S`

        while running {
            self.update();
        }
    }

    fn update(&mut self) {
        self.current_screen.update();
    }    
}

Option 3 - Provide an additional parameter of type S to run method

Link to playground
This is probably the most flexible, because one can construct S outside of run (dependency injection).

// other code omitted
impl<S: ScreenTrait> Program<S> {
    
    pub fn run(&mut self, screen: S)     {
        let mut running: bool = true;

        self.current_screen = screen;

        while running {
            self.update();
        }
    }

    fn update(&mut self) {
        self.current_screen.update();
    }    
}
1 Like

Ok thanks. Add new() to the trait works. But there are three "requirements"

  1. The trait should never be dynamic.
  2. Inside program I have to be able to change to a new screentype.
  3. the new() function can be different for different screens (different parameters), because they initialize differently. But maybe that is work-aroundable...
  1. The trait should never be dynamic.
  2. Inside program I have to be able to change to a new screentype.

Could you please clarify, what you mean by should never be dynamic. I assume it means should not be a trait object?

In the following, I assume it means should not be a trait object.

With our current appproach I don't think this will be possible, unfortunately (except if, everytime you change the screentype, you create a completely new Program and provide the appropriate type parameter , but I don't think this is what you want here).

Enums might be another possibility

If you have a fixed number of screentypes and consumers of your library/app don't need to add their own screentypes, you can maybe use an enum instead of a trait!?
This will also solve point 3 (initialize differently), because you can always explicitly construct an enum variant (e.g. Screentype::ScreenWithTwoParams(foo, bar)).

I hope this helps. :slightly_smiling_face:

The easiest solution would be to simply not try to abstract over the constructor. Just make run accept an instance of the screen and leave instantiation to the caller.

As for the reason you got an error in the OP: In general how generics work is that inside the implementation body, your code must work for every possible type that can satisfy the bounds on the declared generic (even types that don't exist yet). Additionally, each generic type parameter will resolve to exactly one type. So in the OP, you can't assign a PlayerScreen to field current_screen. You can only assign S.

@janriemer's solutions...

  1. Make the implementation non-generic so the field is PlayerScreen
  2. Make the creation of some S doable via trait bound
  3. Make the caller provide the S

If it's ok for the type of Program<_> to change, supply some conversion methods.

If it's not, you need type erasure like a dyn Trait, where distinct base types can coerce to the same type-erased Box<dyn Trait> or such; or an enum, where multiple variants could hold distinct types.

I don't know what "the trait should never be dynamic" means either. Generally the only reason to distinctly want your trait to be non-object-safe is if you're future-proofing and want to protect the ability to add a trait unsafe item in the future without breaking downstream; but if that's what you meant, make the trait non-object-safe now by adding a Sized supertrait or a non-object-safe method.

Maybe new isn't the right abstraction boundary.

1 Like

It is - for me - a syntax thing... I know it is possible.
There is certainly a limited / fixed amount of screentypes. So enumeration / const traitbound is ok with me
My problem is the instantation syntax. And where the generics go.

if state_x currrent_screen = PlayerScreen::new(a, b, c);
if state_y current_screen = MenuScreen::new(q, r);

" You can only assign S" I'll study on that...

So enumeration / const traitbound is ok with me.
My problem is [...] where the generics go

If you decide to use enums, you don't actually need generics anymore:

enum ScreenType {
    PlayerScreen {
        a: Foo,
        b: Bar,
        c: Baz
    },
    MenuScreen {
        q: SomeQStruct,
        r: SomeRStruct
    }
}

impl ScreenType {
    pub fn update(&mut self) -> bool {
       // update logic here for the different enum variants
    }
}

pub struct Program {
    // no generic on `Program` necessary anymore
    current_screen: ScreenType
}

impl Program {
    
    pub fn run(&mut self)     {
        let mut running: bool = true;

        self.current_screen = if state_x {
              ScreenType::PlayerScreen {
                  a: Foo,
                  b: Bar,
                  c: Baz
              }
        } else {
              ScreenType::MenuScreen {
                    q: SomeQStruct,
                    r: SomeRStruct
               }
        }

        while running {
            self.update();
        }
    }

    fn update(&mut self) {
        self.current_screen.update();
    }    
}
1 Like

Of course! That is a straightforward solution. I will use that for now to get it working.
Now I can "redirect" the update routine.
Question: will this match statement be fast? Or can it be made faster?
The update (or the other routines which are there: handle_input etc.) will be called very often.

pub enum ScreenType
{
    Menu { screen: MenuScreen },
    Play { screen: PlayerScreen },
}

impl ScreenType
{
    pub fn update_screen(&mut self) -> bool
    {
        return match self
        {
            ScreenType::Menu { screen } => screen.update(),
            ScreenType::Play { screen } => screen.update(),
        }
    }
}

and

pub struct Program
{
    current_screen: ScreenType,
}

impl Program
{
    fn update(&mut self)
    {
        self.current_screen.update_screen();
    }    
}

If you want to generically instantiate the type S, then you have to say how that can be done by adding a constructor-ish method to the trait, and then call that on the generic type S, not on some arbitrary concrete type. Playground.

But again, you shouldn't be doing this. Trying to abstract over construction of very different types is a straight-up anti-pattern. Just inject the dependency from the outside, like this.

You can annotate the methods that are expected to be called often with #[inline].

From the Rust Performance book about inlining:

Entry to and exit from hot, uninlined functions often accounts for a non-trivial fraction of execution time. Inlining these functions can provide small but easy speed wins.

So the code will look like this (note the #[inline] annotations directly above the method signatures):

// ...
// some code omitted...
// ...
impl PlayerScreen {
    #[inline]
    pub fn update(&mut self) {
        // update logic here
    }
}
impl MenuScreen {
    #[inline]
    pub fn update(&mut self) {
        // update logic here
    }
}
impl ScreenType
{
    #[inline]
    pub fn update_screen(&mut self) -> bool
    {
        match self
        {
            ScreenType::Menu { screen } => screen.update(),
            ScreenType::Play { screen } => screen.update(),
        }
    }
}

In general, it is advisable to measure your performance first before doing optimizations, though.
There are a lot of options to do benchmarking in Rust. Here are some starting points you might want to look into:

Other than that, Brendan Gregg's Homepage is a gem when it comes to general performance tips (not Rust-specific).
There's e.g. a chapter on flamegraphs.

Yes, simple match statements like this are very fast and in general there is no reason to worry about them. It is faster, for example, than calling through a dyn trait object.

But it is up to you to define "fast". Perhaps you mean, "is this slow and often avoided", in which case the answer is no. But if you are micro optimizing a very performance critical code section, then it is worth measuring performance and perhaps looking at the assembly, as others have said.

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.