Pauan/rust-signals

Difficult to return Switch from a fn due to typing

KeatonTech opened this issue · 5 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).

Pauan commented

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

Pauan 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!

Pauan 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.