lundi 12 novembre 2018

Is it necessary to add code to avoid casting when interface alone is no longer sufficient

// ===== library code: =====

trait Sate { // Or in C++ imagine this is a class containing only pure virtuals.
    fn ready(&mut self, ..);
    fn terminate(&mut self, ..);
    fn timeout(&mut self, .. );
    fn ....; // a few more functions
    fn as_any(&mut self) -> &mut Any; // for downcasting in Rust; not necessary in C++
}

states: HashMap<u32, Rc<RefCell<State>>>; // Or in C++ just a map of numbers to shared-ptr of State

fn event_loop_on_some_trigger(trigger_for: u32, trigger: Blah) {
    let state = states[trigger_for];
    if trigger == for_timeout { state.borrow_mut().timeout(...) }
    else if trigger = for_readiness { state.borrow_mut().timeout(...) }
    ...
}


//==== User Code: ====

struct Type0 { ... }
impl State for Type0 { /* provide concrete impls */ } // In C++ this would be inheritance for Type0

struct Type1 { ... }
impl State for Type1 { /* provide concrete impls */ } // In C++ this would be inheritance for Type1

// ... etc.

I was using this library and everything was working fine. But now there are a few types that have to do some specific work which is beyond what's there in the State trait above. So i gradually seem to be running into the "Expressions Problem" in design. I don't control the library providing the trait and the event-loop and even if i did i wouldn't have added 2 or 3 extra functions there as it does not feel right since it's going to be used only for some concrete types and that too occassionally.

What i have additionally is a Map in the user code like HashMap<u32, SomeDetail> which corresponds to the map in the event-loop thread stack above. So if i need to perform a special operation on one of the types i can get the u32 value for the state (by analysing the SomeDetail or whatever), get the state from the map in the event-loop and downcast it to the type to call one of the special functions.

At this point in C++ i would probably have used static_cast<> as I'm sure what type it is (unless there's something wrong in the bookeeping of the id's or it's allocation which would be a logic error in the program). In Rust i'm forced to use downcast_mut (dynamic_cast in C++), which I'm OK with (so far I've never got any runtime error from it, showing that the logic is sound and static_cast were it available in safe-rust would have worked too).

Now my question is that I read that casting is bad design etc. So I was thinking what could I have done differently here. One idea was to store the weak (or strong if we don't care) references in the map that i have in my user code. So while the event loop uses the map it has to fire some function for the State, I would use my map to get the concrete type out whenever I want it and invoke function on it. However since my code is outside the eventloop thread I would have to slap a Mutex on the states, which is useless in 99% of the time as most of the time it's the eventloop invoking it and types doing what they are coded to do.

  1. Should i keep a few maps of (reference to) concrete types the special functionality on which i want to call occasionally and slap a mutex there ?
  2. Just do a dynamic_cast in those cases and this is justifiable ?
  3. Eventloop should have been templatised giving an opportunity for the user to store some state in the eventloop thread stack and deal with it. In this case I would store a few of my maps of <u32, ConcreteTypeReferences> so that I can post the request to the event-loop in rare cases I want to interact with the concrete-types and they can continue living without mutex or atomic-referece-counted objects (in Rust Rc is non-atomic-ref-counted smart-ptr, I don't know if there's an equivalent in C++)

The last option and also partly the 1st one requires I have access to that code - so more of a wish-list but just wanting to know what would have been the suggestion if that were the case. In absence of that (2) dynamic_cast seems the only viable solution ?

Aucun commentaire:

Enregistrer un commentaire