Pauan / rust-signals

Zero-cost functional reactive Signals for Rust

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Difficult to return Switch from a fn due to typing

KeatonTech opened this issue · comments

Consider the following toy example:

fn foo(
  switcher: Mutable<bool>,
  on_true: Mutable<u8>,
  on_false: Mutable<u8>
) -> ??? {
  switcher.signal().switch(|val| if val {on_true.signal()} else {on_false.signal()})
}

The functionality works, but the return type is tricky to get right. The actual return type is Switch<MutableSignal<bool>, MutableSignal<u8>, [closure@my_project/my_file.rs:87:34: 87:72 on_true:_, on_false:_]>, but due to the dependence on the internal closure type there's no way to actually tell Rust this. I've tried making the closure type into a generic but haven't had any luck with that. I suppose I could make a custom struct and manually implement Fn on it, but that's really cumbersome.

So, what if the function returns Box<dyn Signal<Item = u8>>>? Well that actually does solve the problem! But it breaks a ton of stuff down the road because most of the SignalExt functions (including switch) require Self to be Sized, which is not true of dyn Signal.

I appreciate that futures_signals tries so hard to avoid indirection by sizing things, but it leads to some really incredibly long type signatures in complex use cases. Even if I do convert all my closures to structs, having everything so tightly typed prevents a lot of reuse. For example, if I wanted on_true and on_false to be different types (maybe one is a Switch and one is a Mutable), the return signature would have to be Switch<MutableSignal<bool>, dyn Signal<Item=u8>, MyCustomClosureStruct> -- and then I'd just run into the sizing problem again down the road. I'm wondering if it would be a good idea to have something like BoxSignal which wraps dyn Signal but gives it a size (I tried this myself but ran into problems with all the pinning).

commented

You should be able to return impl Signal<Item = u8>, does that not work?

commented

Also note that this issue isn't because of futures-signals... trait methods which accept self must be Sized, Rust mandates it. And the inability to call Sized methods on a dyn Trait is also a limitation within Rust itself, not futures-signals.

The best option is to use impl Signal<Item = u8>. But if you need dynamic types then you must use Box<dyn Signal<Item = u8> + Unpin> instead. That works because there is an explicit impl for it.

Seems to work, thanks!

commented

Also, you mentioned returning different types within switch, this is how you would do that:

fn foo(
  switcher: Mutable<bool>,
  on_true: Mutable<u8>,
  on_false: Mutable<u8>
) -> impl Signal<Item = u8> {
    switcher.signal().switch(move |val| -> Box<dyn Signal<Item = u8> + Unpin> {
        if val {
            Box::new(on_true.signal())
        } else {
            Box::new(on_false.signal())
        }
    })
}

Note that this is only needed if on_true and on_false are different types.

Also note that the function still returns impl Signal<Item = u8>, because even though the closure returns different types, the switch itself is still a single type, so the switch does not need the overhead of Box.