9 ms·
Show HN: Async UI: A Rust UI Library Where Everything is a Future
- adamnemecek 4y agoYou might be interested in RUI https://github.com/audulus/rui https://github.com/audulus/rui
- wishawa 4y agoRUI is really cool, but it is solving a different problem. It is immediate-mode, and it is focused on building a new toolkit. Async UI is more about re-exposing existing retained-mode toolkits in nice-to-use, Rust-friendly APIs. The cool thing is this: with some effort, it is probably possible to adapt the two library together to have a UI with RUI's rendering system and Async UI's APIs.
- skavi 4y agoIn a similar vein, what do you think of Druid? I’ve enjoyed using it in the past.
- wishawa 4y agoHi HN! I've been working on Async UI for half a year now, and I think it is time I share it with everyone. The project is not even really usable yet (due to lack of implemented components and documentation), but I think the proof-of-concept is enough to demonstrate this new way of doing UI — a way that, I believe, is more suitable to Rust than existing UI framework designs. P.S. My website is new, so if you find any readability or accessibility issue, please let me know!
- karmakaze 4y agoI don't use Rust (yet) but was still curious how this looks-n-feels. I get a distinct React JSX vibe from it. I get that this is a very flexible way of building but at the same time I prefer having the component templates 'at the top' as in Vue rather than control flow. Even Vue is built from using builder control flow underneath so it's a different authoring environment vs different implementation. Another one that's interesting is Elm. Best of luck and I'll be checking out where this ends up going.
- api 4y agoI really like what I see so far, especially the out of the box focus on web as well as desktop. That's absolutely killer. One thing to not neglect is accessibility. Even if it's not fully baked yet giving it some thought in the API and implementing for web would be a big plus. Accessibility is the hill most non-mainstream UI toolkits die on. They usually leave it for later and then find that it's hard because they didn't think about it. Being retained mode puts you ahead in the accessibility game right away.
- lucasyvas 4y agoThis is wild to me... how does that login_form function work? If awaiting renders, then it can also return values?
- wishawa 4y agoYou're right that components should intuitively pend forever. But with Async UI, the line between a component and a normal async function is blurry; our login_form eventually returns, so it is a normal async function, but it also renders something, so it is kind of a component... Concretely the login_form function works by racing a render (a true, never-completing component) with a "listener" future that completes when the user submits their login. Once the listener completes, the race is over and the render future gets dropped. We can then return from login_form.
- lucasyvas 4y agoThis pretty interesting - does it take inspiration from anything? I personally haven't seen anything like this. It would also be great to see example code for that - it's the most significant part where I was looking for more information but couldn't find it. Edit: A bit unrelated, but the tokio vs async-std split continues! Edit 2: How does error handling work? Is check_login a component or regular request?
- wishawa 4y agoThe full example code is here: https://github.com/wishawa/async_ui/blob/main/examples/gtk-login-form/src/main.rs https://github.com/wishawa/async_ui/blob/main/examples/gtk-l.... (except for that invalid_login_popup is not a real popup because I haven't implemented popups yet) I'll add a link to it in the blog post.
- wishawa 4y agoThe async control flow is not directly inspired by anything. It is a cool side effect of using async for everything that I myself only discovered once I started writing examples. Async UI as a whole is inspired by the simple fact that UI is an effect system[1], and async is also an effect system. [1]: https://en.wikipedia.org/wiki/Effect_system https://en.wikipedia.org/wiki/Effect_system Re async split: Diversity promotes innovation :) Re error handling: There's no real support yet. For now when I hit an error I just render nothing. I might add support for components returning Result<_, _> in the future.
- avrionov 4y agoYour scientists were so preoccupied with whether or not they could, they didn't stop to think if they should.
- cottsak 4y agosigh how did we all survive before async UI... ¬‿¬
- mananaysiempre 4y agoBy building, say, cooperatively scheduled systems of active objects (actors) accepting arbitrary messages Smalltalk style, like Windows 1. Or preemptively scheduled systems of typed ones, like Symbian. It’s not like the classic approaches are that much simpler, is my point.
- wishawa 4y agoBy lots of 'static and Rc.
- davidatbu 4y agoSo, does this work with an application that uses tokio?
- wishawa 4y agoThis uses the executor used by async-std rather than the one used by Tokio. So if you're using something Tokio-specific, you would probably need async-compat[1]. But can you share more on what you need Tokio for? I'd completely understand if we're working with servers. But when it come to clients - UI applications - I feel like async-std and smol are pretty competitive. [1]: https://docs.rs/async-compat/ https://docs.rs/async-compat/
- Klonoar 4y ago>But can you share more on what you need Tokio for? Because everything. uses. Tokio. It's become the defacto standard of async Rust, whether people like it or not. This issue has been notable enough to cause some projects to outright consider dropping async-std support - and I think they only relented because there's apparently enough (private, not-open-source) users who apparently still use it. (https://github.com/launchbadge/sqlx/issues/1669 https://github.com/launchbadge/sqlx/issues/1669) (Note that they also have a note about async-compat not exactly being an ideal solution and that they'd be looking into writing their own)
- davidatbu 4y ago`reqwest` comes to mind as a client-side library that's my goto, that I think is tokio-only? But I think the distinction you made about "client-side Rust" not being as obviously-tokio-dominated as backend-side does make me stop and think. Btw, this looks interesting regardless! Great job! Can I ask how your Rust learning journey went? I assume one needs an under-the-hood understanding of async Rust to build a library like this, and I'd love to hear about your learning journey getting to that understanding.
- HippoBaro 4y ago(Context: I write async rust professionally) Fly you fools. This will be a nightmare to debug, introspect and reason about for a speed boost that you (and your users) won’t be able to measure. If you want to build a native app, more power to you. There are simpler languages that will enable you to do that with a much higher productivity. Kudos to the library writer though!
- TuringTest 4y agoThe point of async APIs is not speed boost, it's decoupling processing from the local call stack (which happens to hang up the GUI until the routine resolves, but also forces components to be tightly coupled and monolithic). IMO every end-user-facing interface should be based on async calls, which provides better composability and forces the developer to think the relations between all full possible interactions and not just the current call. Too many GUIs do weird things when the user clicks on several controls in quick succession before allowing the previous one to finish. The program should have a model for how to resolve such anomalous inputs, instead of leaving those interactions as undefined behavior or handling it as error-prone edge cases. Having an async framework isn't enough for that, but at least forces the developer to think about out-of-order interactions between commands. If that makes reasoning about the complex it's because current debug & introspect tools are inadequate for async-heavy flows; but that's a reason to improve the tools, not to drop the flows. Better declarative languages and inspect tools would ease development in that style.
- quietbritishjim 4y ago> The point of async APIs is not speed boost, ... I think the parent comment meant using Rust rather than a garbage collected language like C# or even Java for a GUI. Not just using async within Rust.
- Huh1337 4y agoJava GUIs are horribly slow. Maybe it's not inherent but I've never encountered one that wasn't. C# ones are sometimes alright but only if they're using the native Windows frameworks.
- mbStavola 4y agoI must admit, I was a bit skeptical when reading the title, but after reading I'm super interested. The login form example which returns values reminds me a lot of imgui and other immediate mode GUI frameworks.
- voorwerpjes 4y agoVery cool! I don't understand the motivation about lifetimes in sync rust not being able to be arbitrary. I'm also confused because most of the time went you want to send data around in a async context you wrap it in `arc`, which has the pretty much analogous `rc` in a sync context which would also solve the lifetimes issue. Is there something I am missing?
- sakex 4y agoIn C++, this will compile: class T { public: T(int & m): member{m} {} int & member; } T MakeT(int m) { return T(m); } int main() { const auto t = MakeT(10); std::cout << t.member << std::endl; // UB <- member has actually been destroyed } Rust will correctly observe that the lifetime of m in `MakeT` is lower than the T object returned and will refuse to compile
- deleted 4y ago[deleted]
- royjacobs 4y agoThe programming model reminds me of Rob Eisenberg's older attempts at building UI toolkits ([0]). I don't recall if that was fully async or 'just' using a coroutine/generator-style approach, but it feels similar. I'm not sure the complexity of doing everything using async constructs is worth it, though. Large-scale UI's built in Qt or Javascript are mostly single threaded anyway, but it's still worthwhile to explore so kudos for that. Looking forward to seeing how far you get. [0] https://github.com/Caliburn-Micro/Caliburn.Micro https://github.com/Caliburn-Micro/Caliburn.Micro
- pharmakom 4y agoLooks a bit like concur JS
- wishawa 4y agoI must admit I've never seen Concur before!.The idea of using generator/async is pretty similar. The difference, as I understand, is that in Concur, you yield the widget and let the framework mount it, while in Async UI, you await the widget yourself.
- pharmakom 4y agoisn't it better for the framework to mount it etc? seems like things might get out of control in large apps otherwise.
- cercatrova 4y agoIsn't this basically retained mode vs immediate mode GUIs?
- cyber_kinetist 4y agoI wonder if one can do this with coroutines in a more ergonomic way. Lua might be a good candidate language to create such a UI framework…
- wruza 4y agoWe’re almost back to if (MB_OK == MessageBoxA(…)) and other modal forms, just need to wait a little more for this ui insanity to end and life can be good again.
- fyvhbhn 4y agoCould you compare it with Sycamore with a few bullet points? I feel like it is pretty close even though you don't see the async exposed? L
- wishawa 4y agoSycamore works pretty similarly to React. See https://www.reddit.com/r/rust/comments/xvv49w/comment/ir6pw0d/?utm_source=share&utm_medium=web2x&context=3 https://www.reddit.com/r/rust/comments/xvv49w/comment/ir6pw0... for how Async UI is different from React-style frameworks.
- fyvhbhn 4y agoIt don't see how this works out. React uses a virtual Dom. Sycamore: > Write code that feels natural. Everything is built on reactive primitives without a cumbersome virtual DOM. Yew works more similar to React
- wishawa 4y agoYou're right. I got the two frameworks confused. Async UI is similar to Sycamore in term of not diffing VDOM. The API is different in that in Sycamore, you tell the framework what to render (by using sycamore::render) and the framework will handle it from there. In Async UI, you await what you want to render yourself. Async UI's API is more transparent in this way, and this brings benefits including - making async control flow (like the control flow example in the blog post) possible - making component simply async function or anything that implements IntoFuture<Output = ()> Sycamore's reactivity is pretty painless (a little too magical for my taste, but that's probably just me), so it's something Async UI can learn from.
- debdut 4y agohas a SwiftUI resemblance, nice :)
- tayistay 4y agoOnly in small examples. This doesn't look that much like SwiftUI to me: https://github.com/wishawa/async_ui/blob/main/examples/web-todomvc/src/lib.rs https://github.com/wishawa/async_ui/blob/main/examples/web-t...
- your_challenger 4y agoThis is very Compose (Android) and Swift UI (iOS) like. I love it!
- SeniorMars 4y agoI love you wisha <3
- qwerty456127 4y agoI always dreamt of a GUI library where every widget would be a separate actor sending and receiving messages.
- zarzavat 4y agoThis is basically how Objective-C worked, being based on Smalltalk. Especially with NSNotificationCenter, you also get the async aspect (so-called “NSNotificationCenter spaghetti code”). Personally I have mixed feelings on it. ObjC/AppKit was clearly a step up from classic object UI toolkits built in rigid languages like C++, but I find React and its immediate-mode brethren far more enjoyable to work with precisely because there is no amorphous graph of actors sending messages to each other.
- qwerty456127 4y agoI see. Isn't it (non-blocking message-exchanging objects) the most natural way to reason about though? Surely classic types and sequential function execution is more convenient for data processing/scripting but a GUI seems a naturally object-oriented thing and it feels better when the objects act independently.
- gwbas1c 4y agoHopefully I can find some time in my busy schedule to try this. One thing I like is that the code appears to flow more like a console app than a GUI app. I've always found it's easy to create a quick-and-dirty console app; but if I want to do a quick-and-dirty UI (windows) app, it's much more time consuming. This is because, with console IO, you can write your UI in a very linear manner. With UI (windows), it's much harder to write the code in a linear manner.
- meltedcapacitor 4y agoSeems a weird mix of async and callbacks spaghetti. I´d expect an async UI to be in "immediate UI" style, e.g.: async fn give_em_cookies(window:Win) { win.title("Cookies!"); win.columnwise(); win.label("Which cookie do you like?"); let cookie = win.selector(&["Shortbread", "Chocolate chip"]).await; win.label("When to you want to eat the cookie?"); win.rowwise(); if win.button("Now!").await { eat_cookie_now(cookie); } if win.button("Later!").await { eat_coookie_later(cookie); } if win.closed().await { no_cookie_eaten(); } } // the function represents the state machine that encodes // the behaviour of the dialog box, which the caller gives to UI // engine for rendering ui.display(give_em_cookies(Window::new())).await;
- deleted 4y ago[deleted]
- Existenceblinks 4y agoIt's 2.56 MB gzip!
- wishawa 4y agoThe Todo example is 2.5 MB non-gzipped. About 600KB gzipped. Still large, but it's because we're shipping a lot of code that we don't need. Mostly the APIs exposed by web_sys. Proper dead code elimination would bring it down a lot.
- Existenceblinks 4y agoRight. I'm interested in wasm lately and binary size is always my concern. There's almost non-existence of ast to wasm knowledge, only through Rust, C++, and some other langs. If we really want a lot of non-javascript langs to succeed, I think we need to focus on raw wasm, .wat is pretty good for teaching. But they have new thing coming up which is called "Component Model", something about interface types related, which may help reducing bloat binaries.
- jcelerier 4y agoi'm really curious about the UI performance. a very good way to get lower latency in my usually callback-driven code is to move to synchronous things as far as possible - every callback has a cost, and these costs really add up especially if you want as fast as a startup as is possible for your app. And every step in a coroutine in an event-driven system is pretty close conceptually to a callback, isn't it ?
- ohgodplsno 4y agoToday in "HN never used a reactive framework that is not React and is offended when UI is not written with Dear ImGui". This is literally the exact style of SwiftUI and Jetpack Compose (down to the author having used the term fragment, I sure hope this isn't leftover trauma from being an Android developer), except written in Rust (hence having to deal with lifetimes in the middle, default parameters, lambdas being quite verbose and needing to move things, etc). Not blocking the UI thread is mandatory if you ever want to make any kind of complex UI. If you're a web dev, well you only have one thread anyways, good luck, if you're on any other platform, interactions _cannot_ ever block the UI (unless you, yourself, update the UI to say it is blocked). Making this async is a good thing. Stack traces are a problem, but then again they've been a problem in any remotely capable UI toolkit. With ReactiveCell, it looks surprisingly similar to what Compose does, where modifying a State<T> causes recomposition of everything observing it. Which means that it might be powerful enough one day to do the same things as Molecule (https://github.com/cashapp/molecule https://github.com/cashapp/molecule), or ComposePPT (https://github.com/fgiris/composePPT https://github.com/fgiris/composePPT), where everything is a potential target and it interops really well with existing toolkits.
- tayistay 4y agoThis is an interesting idea, but if you look at bigger examples, such as the todo-list example, the code is littered with `async` noise, calls to `borrow()`, and other fancy stuff like reducers. (A todo list is easily expressed in SwiftUI without much ceremony) Seems like scaling up to actual apps would be a mess. https://github.com/wishawa/async_ui/blob/main/examples/web-todomvc/src/lib.rs https://github.com/wishawa/async_ui/blob/main/examples/web-t...
- wishawa 4y agoThat example is indeed pretty noisy, but partly due to my premature optimization. The operations of adding, editing, and toggling Todo items are all O(log N)[1]. Things could be simpler if I’d just take the O(N). [1] O(log N) for “our” code. I don’t know what the browser’s rendering/layouting engine is doing. In general Async UI will still be noisier than SwiftUI or React, but I hope only in the way that Rust is more explicit/verbose than other languages.
- tayistay 4y agoHmm, surely a SwiftUI todo list could be implemented using O(log n) operations with ease. Anyway I think that example might scare people off, so definitely try to simplify it if you can :)