Replace macro approach with IndirectSignalReceiver to force type inference to work when connecting closures to the signals
#1179
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
What does this PR solve?
Allows us to use
FnTraitsfor signal connections instead of generating signature for every single possible function. This approach makes creating signal extensions and whatnot much easier (since we can work withFnTraitinstead of doing hoops to change Fn signature intoParamTupleand back again).This PR does not introduce any changes in user-facing API (unless we missed something in itest 😅)
WHAT ????
Alright, let's start from scratch.
Crab is behaving very silly when it comes to type inference for closures, especially when Fn Traits are anyhow involved – which is ultra annoying and forces users to sprinkle their code with unhealthy amount of noise. To make it worse, type inference is, on first glance, inconsistent – qualified calls in qualified context are fine (ok, in layman terms – doing Trait stuff, like display and whatnot), while invoking methods on individual types doesn't work.
See: rust-lang/rust#63702.
Part of this weirdness can be addressed by using so-called "identity function". It is well known and established hack, mentioned, for example, here: https://users.rust-lang.org/t/type-inference-in-closures/78399.
Why does it work? Well, the answer can be found in this apologetic comment in the compiler:
https://github.com/rust-lang/rust/blob/5ad7454f7503b6af2800bf4a7c875962cb03f913/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs#L306-L317
Don't worry if you haven't understand anything, we are in the same boat. Long story short, crab behaves ultra silly – if you specify concrete function type (FnMut…), then it is all good, if you specify trait (
impl Bar) then crab gets lost and needs guidance.In our case – specifying callbacks in form of closures for signals – identity function can't be just applied directly. We could make identity function work, doing something along the lines of
signal().connect(infer_plspls!(|this| this.add_node(…)))which is equally annoying, creates unnecessary overhead, doesn't work nicely with most IDEs and also comes with its own tradeoffs.For now, we went with @Houtamelo workaround which was using macro magic to handle all the possible callback types – but it comes with its own trade offs, mainly not being able to easily extend available methods, signal connections and whatnot. It is pretty good workaround, aye, but perfect is the enemy of good!
See also: #1152 (comment)
back on track – crab needs concrete type to infer types in closure. We need some identity function, or type, which would change our closure into trait stuff, right?
If we would work on function pointers then we could do something like
Box<dyn FnMut(..)> -> Box<dyn Bar>conversion… But we can't really doFn(???) -> impl Bar. Or can we? Let's make a step back, to get a better view… Like a view function? Yeah, view function sounds good, let's call it Intermediary.Let's make concrete type from our Intermediary. Something simple and elegant, such as
<&mut Func as Into<Intermediary<'c, Func, Params, Ret>>>. This looks like beautiful concrete type that makes our crab happy!I mean, really
it's all so silly…
I wouldn't call this hack elegant, but hey, it works!
Hopefully, the new trait solver will render this whole issue null: https://rustc-dev-guide.rust-lang.org/solve/significant-changes.html#deferred-alias-equality. If next-gen trait solver fixes this issue, we can just get rid of Intermediary, go back to function trait and call it a day (
Intermediaryis just a view function, so it will be no-brainer).