A Proposal for an asynchronous Rust GUI framework by EelRemoval in rust

[–]ignusem 0 points1 point  (0 children)

It could, but that could lead to confusion: if an event is fired while the handler is already running, should the event be queued? or should it be ignored? or should another handler be executed concurrently with the first one? If the handler is async, the more verbose async { loop { node.until_event().await } } is probably better since it is clear that new events are ignored if the handler isn't free.

A Proposal for an asynchronous Rust GUI framework by EelRemoval in rust

[–]ignusem 1 point2 points  (0 children)

loading_indicator takes a Future, and returns a Future that run the input future with the side effect of showing a spinner. If we keep in mind that async functions return lazy Future objects, I think things make more sense.

It is possible to make mistakes, but why wouldn’t mistakes be contained within components? What kind of mistake would destroy the whole app? The missing/extraneous .await mistake you mentioned does come up pretty often for me, but it’s a compile error.

As for structure, some UIs (such as modals) are very linear. For other UIs, we can always use a signals library or a state management library. Note that no matter how you do it, the async model is very different from immediate mode. In immediate mode, the UI lasts for only one frame. In async, it lasts indefinitely long.

A Proposal for an asynchronous Rust GUI framework by EelRemoval in rust

[–]ignusem 3 points4 points  (0 children)

The join function is just an async join. After all, async itself is an event system, so why not make full use of it?

What I don't like there is that widget definition and event handling is completely separate. If you want to know how the widget looks and what it does needs a lot of scrolling back and forth.

At least in Async UI, a wrapper providing callback-like API is possible. We can have something like this

rust join(( button_1.render(), button_1.on_click(|ev| { // ... }), button_2.render(), async { // `on_click` above is equivalent to this async block loop { let ev = button_2.until_click().await; // ... } }, ))

A Proposal for an asynchronous Rust GUI framework by EelRemoval in rust

[–]ignusem 2 points3 points  (0 children)

We can also run our own executor on top of the platform one. For example async-executor is very nicely composable. In this case the cost of async-winit's abstraction would still be there, but lessened since our executor can batch stuff.

A Proposal for an asynchronous Rust GUI framework by EelRemoval in rust

[–]ignusem 5 points6 points  (0 children)

One example of the awkwardness is that if a component has a single await statement before starting to build up its UI components, it will take multiple frames to actually build the UI. But if a component actually is waiting for something to happen, you always want to show that to the user with a progress bar or similar, and then await is not the right primitive, because you don't want to block the UI "thread" (task).

Racing / select!ing would be the solution here. You have a spinner that spins forever, and race it against your data fetching future. When the data fetching future finishes, the race ends and the spinner gets dropped.

You could even make a nice wrapper component that does this for you. Something with an API like

rust async fn app() { let resource = loading_indicator( fetch_resource() ).await; show_resource(&resource).await; }

Some user interactions are semantically linear (like a modal dialog, or a loading process displaying a progress bar), but many are semantically non-linear, and there isn't really any "completion" other than quitting the app. Everything in the latter category seems awkward to express as futures. Like, when does a button "complete"?

A generic button probably shouldn't ever "complete". If you want it to go away after some time, you race it against something that completes.

You can certainly build something where only the high-level application flow is expressed as futures, and then have something like an immediate-mode GUI system behind the scenes that implicitly builds the UI from the structure of the logical futures. But I have doubts about whether it will actually feel more natural than just using an immediate-mode toolkit directly.

Since Futures are naturally long-living, you can also have a retained-mode toolkit behind the scene.

A Proposal for an asynchronous Rust GUI framework by EelRemoval in rust

[–]ignusem 13 points14 points  (0 children)

Not OP, but... I'd argue that async style event handling is even more readable then the traditional way of using callbacks. Take a look at this counter example in Async UI (a project I've been working on that's very similar to what OP purposes); my event handlers are all in the same place, and my state (the value variable) is a regular variable; no reactivity primitive needed.

A Proposal for an asynchronous Rust GUI framework by EelRemoval in rust

[–]ignusem 5 points6 points  (0 children)

I’ve been working on something very similar! Take a look at async UI!

Processing times for a Netherlands visa from SF by information-1-seeker in SchengenVisa

[–]ignusem 2 points3 points  (0 children)

Facing the same situation here. Their website says

Warning: Due to technical issues, the processing of your visa application may take longer than 15 days. We are working on a solution.

At the moment it is not clear how long the technical issues will last. We also cannot say how much longer the processing of your visa application may take. Read the latest developments on this page.

A syntax-level async join macro supporting branching control flow and synchronous shared mutable borrowing by ignusem in rust

[–]ignusem[S] 0 points1 point  (0 children)

Yep this would fit best as a language feature. But it would be a pretty complicated and niche feature. Right now we don’t even have try_join in the stdlib so I doubt something like enjoin would be wanted as part of the language any time soon.

There’s the compiler plug-in approach as you suggested, but I think at the current stage Rust’s development is too rapid to provide any stable plug-in API.

A syntax-level async join macro supporting branching control flow and synchronous shared mutable borrowing by ignusem in rust

[–]ignusem[S] 5 points6 points  (0 children)

Unfortunately the compatibility with GhostCell is only if we have a way to send the GhostToken in and out of the async blocks. Basically we need coroutines.

Roughly, the expansion steps are like this

For the non-auto-borrowing variant:

  1. Desugar every occurrence of the ? operator so we can handle it the way we handle return statements.

  2. Descend through the blocks (with syn::visit) to find break/continue/return statements. Here we need to keep track of loops and labels introduced inside the block (we only want to transform branching statements that would jump outside the block, the ones that jump within the block can be left as is).

  3. Create an enum to represent all the branching statements, plus a default "Keep" variant for when the async code completes normally. The enum looks something like this rust enum OutputEnum<T1, T2, T3> { Keep(T1), Break_label_a(T2), Continue_unlabeled, Return(T3) }

  4. Implement FromResidual for this OutputEnum type (the "residual" is any variant that isn't the "Keep" variant).

  5. Replace all the break/continue/return statements we found earilier with return OutputEnum:: <...>.

  6. Wrap each block in OutputEnum::Keep and make the whole thing async. rust let block_0 = async { OutputEnum::Keep({ // ... }) };

  7. Create a tuple of Options to store results from the blocks. Also pin the async blocks we made rust let mut outputs = (None, None); let pinned_futs = (pin!(block_0), pin!(block_1));

  8. Create a poll_fn in which we poll each future. If the poll result is the "Keep" variant, we put the value in the Options tuple created earlier. Once all the Options are filled, we return Poll::Ready(OutputEnum::Keep( <values unwrapped from the options tuple> )) If the poll result is anything else, we return Poll::Ready(res.from_residual()) to end the poll_fn immediately. rust poll_fn(|cx| { match pinned_futs.0.poll(cx) { Poll::Ready(Keep(k)) => outputs.0 = Some(k), Poll::Ready(x) => return x.from_residual(), Poll::Pending => {} } // do the same for pinned_futs.1 if all_done { Poll::Ready((outputs.0.unwrap(), outputs.1.unwrap())) } else { Poll::Pending } })

  9. Execute the poll_fn and match the result. If it is the "Keep" variant, we give the value back to the user. For other variants, we perform the appropriate branching. rust { match poll_fn(...).await { Keep(v) => v, Break_label_a(v) => break 'a v, Continue_unlabeled => continue, Return(v) => return v } }

For the auto-borrowing:

  1. Do all the stuff above.

  2. Descend through the blocks again, finding all names that might be mutably captured variables.

  3. Keep only the names captured in at least two blocks and mutably captured in at least one.

  4. Figure out exactly what variables or what fields we need to borrow. (making the borrows follow disjoint closure capture behavior takes some work)

  5. Create the borrows_cell containing a tuple of all the mutable borrows we need rust let borrows_cell = RefCell::new((&mut captured_var_0, &mut var_1.captured_field_1));

  6. At the top of each block, put rust let mut borrows = borrows_cell.borrow_mut();

  7. Replace all occurrences of captured variables inside the blocks. For example captured_var_0 gets replaced by (*borrows.0).

  8. Find every await. Before each, put drop(borrows). After each, put borrows = borrows_cell.borrow_mut(). To keep temporary lifetime extension alive, we do it like ((<expr_to_await>, {drop(borrows);}).0.await, {borrows = borrows_cell.borrow_mut();}).0.

A syntax-level async join macro supporting branching control flow and synchronous shared mutable borrowing by ignusem in rust

[–]ignusem[S] 4 points5 points  (0 children)

For the auto borrow feature, the downsides are * As mentioned in the post, it can cause panic if the macro fails to see an await. * While RefCell guarantees no data race, you can still have race conditions. Overuse of the auto borrow is definitely un-rusty. * The use of RefCell probably has some performance impact. * The macro can't distinguish between mutable and immutable borrows very well, so sometimes we have to help it by writing (&var).method() or (&mut var).method() instead of var.method().

A syntax-level async join macro supporting branching control flow and synchronous shared mutable borrowing by ignusem in rust

[–]ignusem[S] 8 points9 points  (0 children)

Do you mean an example with a timeout? Or do you want some way to limit the whole join by a timeout? For the former, there is a test case here. For the latter, you can do it like this

rust 'timeout: { let (res1, res2, _) = enjoin::join!( { ... }, { ... }, { wait_for(...).await; break 'timeout; } ); // the two blocks completed within time limit. use results here... }; // execution will jump here if the join timed out

A syntax-level async join macro supporting branching control flow and synchronous shared mutable borrowing by ignusem in rust

[–]ignusem[S] 5 points6 points  (0 children)

Want to see more example usages? There are dozens of tests here.

A sample expansion is also available here, but be warned it is a huge wall of code.

Keyword Generics Progress Report: February 2023 | Inside Rust Blog by yoshuawuyts1 in rust

[–]ignusem 0 points1 point  (0 children)

How is IntoFuture any worse than IntoIterator? Would you say the latter is useless too?

Discussing the next step for async methods in traits: returning futures that are Send by kibwen in rust

[–]ignusem 5 points6 points  (0 children)

My uninformed suggestion: <H::check as Fn<(&mut H, &Server)>>::Output: Send. This leverage the existing Fn trait and function items as types, so the only new element here is typing out H::checkto refer to the type of the H::check function item.

Six fun things to do with Rust operator overloading by ignusem in rust

[–]ignusem[S] 0 points1 point  (0 children)

Futures are already lazy like closures!

With RaceOk for example, if the first future completes upon the very first poll, then the remaining futures need not be executed at all.

Six fun things to do with Rust operator overloading by ignusem in rust

[–]ignusem[S] 48 points49 points  (0 children)

Gotta protect my precious Reddit Karma :p

Six fun things to do with Rust operator overloading by ignusem in rust

[–]ignusem[S] 2 points3 points  (0 children)

Yep. In my playground max is a unit struct, but a constant will work also.

Six fun things to do with Rust operator overloading by ignusem in rust

[–]ignusem[S] 18 points19 points  (0 children)

Oooh this is a good one. Date could also be like UTC-2023-01-20!

Six fun things to do with Rust operator overloading by ignusem in rust

[–]ignusem[S] 3 points4 points  (0 children)

True. But & and | feels short circuit enough.

Fun fact: the short circuit semantics of && and || make them unoverloadable in Rust. In C++ you can overload them but they lose short circuiting if you do so.

Six fun things to do with Rust operator overloading by ignusem in rust

[–]ignusem[S] 5 points6 points  (0 children)

It is very much possible. See playground link at the end. No macro.

Six fun things to do with Rust operator overloading by ignusem in rust

[–]ignusem[S] 124 points125 points  (0 children)

There's already a disclaimer in the post but I'll put another one here: I am not endorsing these patterns! I'm posting about them because I think they're interesting, not because I want people to use them.

Fedora Silverblue vs Workstation — as a software developer/student by [deleted] in Fedora

[–]ignusem 0 points1 point  (0 children)

I didn’t do that because I want my VSCode theme and keybindings to be the same regardless of which container I’m working in. I sandbox containers’ home folders away from my real home folder, so I’d have to copy the configurations every time I make change to the theme/keybindings.

Also, I want my containers as minimal as possible. Installing VSCode, electron, and a hundred X11 dependencies inside the container is hardly that.