Countdown interface object in rust no_alloc

Im writing a program for Arduino uno that handles a car with engines and many different sensors, and some of the actions take time to execute like travel_cm(amount: u32), which depend on state that is modified by interrupts, and i wanted to implement some kind of Countdown that could be run and return amount left to finish.
In my program there would be some different counters that depend on different objects, and i wanted to have a single counter in my main loop that can be changed depending on action executed.

let countdown = Box<dyn Countdown>;
loop {
  if countdown.check() < 0 {
    //handle next action
     countdown = nextAction.countdown();
  } else {
    //do something in the meanwhile
  }
}

I tried to go about that with closures but working with them proved to be complicated because of their unique types, and because of no allocator i can't use Box<dyn Countdown> to easily handle them. I thought about using some crate that can provide Box<dyn > without heap, also i figured i can do an enum that has every type of countdown closure in it

trait Countdown = Fn() -> i32;
enum Countdowns<A, B, C, D>
where
    A: Countdown,
    B: Countdown,
    C: Countdown,
    D: Countdown,
{
    Left(A),
    Right(B),
    Servo(C),
    Infrared(D),
}

impl<A, B, C, D> Countdowns<A, B, C, D>
where
    A: Countdown,
    B: Countdown,
    C: Countdown,
    D: Countdown,
{
    fn check(&self) -> i32 {
        match self {
            Countdowns::Left(f) => f(),
            Countdowns::Right(f) => f(),
            Countdowns::Servo(f) => f(),
            Countdowns::Infrared(f) => f(),
        }
    }
}

impl CarState {
    fn get_countdown(
        &self,
        typ: i32,
    ) -> Countdowns<impl Countdown , impl Countdown, impl Countdown, impl Countdown> {
        match typ {
            0 => Countdowns::Left(|| self.left),
            1 => Countdowns::Right(|| self.right),
            2 => Countdowns::Servo(|| self.servo),
            _ => Countdowns::Infrared(|| self.ir),
        }
    }
}

Is there an any better, elegant way to go about implementing something like that?
my gihub repo

you can use dynamic dispatch without heap allocation, you just use borrowed trait objects (i.e. &dyn Countdown and &mut dyn Countdown) instead of the owned Box<dyn Countdown>.

personally, I would prefer enum in such use case. in general, enums are for closed set of types that share a common public interface, and trait objects are for potential open set of types implementing the same interface.

there are crates that help to reduce the boilerplates for the enum case, like enum_dispatch, which let's you use an enum through a trait API:

2 Likes

I knew about the borrowed trait objects but i struggle to find a way to use them in my project, maybe i could instantiate every type of countdown and exchange the reference, but that doesnt seem very good. If you have any idea how to do that in better way i would be very happy to know.

I looked into the enum generation, and found a crate that seemed more fit to my use case

It can handle closures with use of some unstable features, and i got something like that

fn main() {
    let mut car_state = CarState {
        left: 10,
        right: 20,
        servo: 30,
        ir: 40,
    };

    let countdown = car_state.get_countdown(1);
    println!("Countdown value: {}", countdown(()));
    drop(countdown);
    car_state.left = 100;
    
    let countdown = car_state.get_countdown(0);
    println!("Countdown value: {}", countdown(()));
}

impl CarState {
    #[auto_enums::auto_enum(Fn)]
    fn get_countdown(
        &self,
        typ: i32,
    ) -> impl Fn(()) -> i32 {
        
        match typ {
            0 => |_| self.left,
            1 => |_| self.right,
            2 => |_| self.servo,
            _ => |_| self.ir,
        }
    }
}

Which seems pretty nice, but for some reason closures need an argument, otherwise it doesn't work, maybe thats because of the unboxed_closures. I'm not sure if something like that can be done with the crate you provided, as it seems to need a type name which i dont have, but im open to any suggestions.

I thing you should look at Elm architecture. I think it can inspire you about design. It relies on messages and you can create messages with countdown too. When a message created then you will trigger events. Iced uses this architecture for GUI. I'm not saying use Iced in this project for sure but this quick one page explanation about Iced and Elm structure may give you some ideas about your design too.