Closed tronical closed 1 year ago
We could also try to have a way to "initialize" inline
// no arguments
callback foo => { /*...*/ }
// with arguments
callback foo(abc: int) -> int => { /* ... */ }
Or is this =>
too much?
We also need to consider the side effects an threading issues of these function.
I think we have the following dimention for different kind of function or callback:
Maybe we can use these keywords
No side effect | side effect allowed | |
---|---|---|
synchronous, 1 handler, can return value | function |
callback |
possibly async, possibly several handler | N/A | event |
Or is this => too much?
IMO yes. I'd prefer function
because that's what it is. If that comes with additional restrictions then I think that's fine, such as a check for side-effects.
I like the proposed table though, that's nice.
@tronical and I discussed this today.
We realized that there is many dimension to the problem of declaring a callback:
callback
) or not (helper functions)clicked => queued { debug("foo") }
We haven't agreed on a good way to declare purity and if it should be the default and enforced. (@pure
attribute or public pure function
, or pure by default and public impure function
or side-effect
)
But we agreed that there is a need for non over-writable function such as
component CheckBox {
public function toggle() { // private by default
self.checked = !self.checked;
self.toggled(self.checked);
}
callback toggled(checked : bool); // callbacks are always public
}
For callback, we decided to
callback clicked(buttons: int);
clicked(buttons: int) => { ... }
But we agreed that there is a need for non over-writable function such as
To clarify what you mean, in that example toggle
would be callable from outside CheckBox
but outside code could not assign toggle
to some other function?
Our current thinking is this:
pure
/If you want to call a function from a binding or pure function, you then need to declare it pure. Later, if you want to add a side-effect to the pure function, you end up having to remove the "pure" declaration from the public function and chances are that you might see that such a change will affect your users.
PR that implements it: https://github.com/slint-ui/slint/pull/1989
Elsewhere in Rust the default is the "safe" or "better" option and you have to opt-in to the other option, eg by using unsafe
or mut
for safety and mutability. This makes me wonder if where possible things should be pure by default and you should have to opt-in to impure :thinking: But maybe I'm overthinking things and this would be too much boilerplate to mark most functions as impure :-)
We indeed are wondering if the default should be pure or impure. I think for callback it should be impure as most callbacks are meant to be impure. (Unless they return a value, maybe)
For function it is a bit less clear what the default should be.
Making it a different default based on the return value doesn't sound like a good idea.
That said, it is not clear what in pure or impure is the "safer" or "better". The equivalent of pure in rust would be const fn
which is not the default
Right, so maybe having a side affect isn't strong enough reason to choose between pure/impure by default. And as you say for functions Rust doesn't use const fn
anyway :thinking:
Reading your blog there is the example of calling a function that has a side affect during property evaluation.
height: {
toggle(); // BAD: this changes another property during evaluation.
return compute_size(42px);
}
Once there are pure/impure, are you thinking here that the binding would need to have something tagged onto or an unsafe block to indicate that the developer really does want to call an impure function ? such as unsafe { toggle(); }
or tag the whole property binding or something ? eg what would this look like with the pure/impure stuff ?
Based on the experiment in #1989 , I noticed that, after porting the examples to annotate the callback with pure
, only two testcases do suspicious things by making use of side effects in properties. These tests are arguably not nice code and i don't know if their use case should be supported.
I'm pretty sure however that people might rely in such hack like the ones in https://github.com/slint-ui/slint/issues/112#issuecomment-1065866865
However, if it involve native code, they can still mark the callback as pure
and have the native code violate that. We don't protect against it
So i don't know if we need a way to tell .slint that we want to do side effect in a pure function. If so, unsafe { } is not the right keyword. Maybe side-effect { toggle() }
or toggle!()
or 😷toggle()
Done
At moment it's possible to use callback handlers to define what can function as helper functions:
This works well in the sense that the
callback helper
declaration acts like it is declaring a property and the handler declaration assigns the right body to it. It would be great if the parser would support a more convenient syntax to achieve the same semantical result: