[HN Gopher] Xilem: An Architecture for UI in Rust ___________________________________________________________________ Xilem: An Architecture for UI in Rust Author : raphlinus Score : 216 points Date : 2022-05-07 19:09 UTC (3 hours ago) (HTM) web link (raphlinus.github.io) (TXT) w3m dump (raphlinus.github.io) | kuon wrote: | I am currently designing an UI framework in rust and I took a | similar approach. It is intended for medical monitoring systems. | I should be able zo open source it at some point. But for now, | all I can say is that this approach seems the way to go. | thatguy_ wrote: | Really interesting post. | | One thing I think might be problematic for you would be | fallibility in the `Adapt` callback process. i.e. if it would not | be sound to change the state of a child due to some emerging app | state inconsistency or similar. | | To put it differently, I understand your architecture outputs a | new _statically typed_ tree every time through kind of recursive | state mutation. However, being statically typed, it is clear from | the start of the operation what the end complete static type will | be (to the compiler at least). If the transformation of one of | the children is not possible (although the rest might be fine), | what do you do? | | The obvious way would be to make each conversion fallible, but | this might be a pain to use/propagate. Otherwise, you might have | state that all operations involved in state mutation must be | infallible. | | Anyway just my 2c. I might have misunderstood the mechanism, | though. | | Thank you for all your impressive work! | Barrera wrote: | From the first paragraph: | | > ... Architectures that work well in other languages generally | don't adapt well to Rust, mostly because they rely on shared | mutable state and that is not idiomatic Rust, to put it mildly. | ... | | The author doesn't mention Redux (the architecture), which is | surprising. There are three principles[1]: | | 1. The global state of your application is stored in an object | tree within a single store. | | 2. The only way to change the state is to emit an action, an | object describing what happened. | | 3. Changes are made with pure functions. | | In other words, the components of an application never mutate the | state tree directly. Rather, they emit actions which re-generates | the state tree without mutation. | | This style of state management is compatible with Rust's | ownership model.[2] The emphasis on pure functions (that _clone_ | state rather than mutate) means that it 's not necessary for your | application to alias mutable references, which I'm guessing | underlies the "generally don't adapt well to Rust" part of the | claim. | | [1] https://redux.js.org/understanding/thinking-in- | redux/three-p... | | [2] https://github.com/jaredonline/redux-rs | cultofmetatron wrote: | redux architecture is just a global scan(). more or less what | the elm architecture is as well. | raphlinus wrote: | The other responses have this right. I think the Redux pattern | is similar enough to Elm that I didn't feel a need to make a | finer distinction. | | I'll also say this: the tools that Rust provides for reasoning | about mutation are powerful and principled. A central | philosophy of Rust is that _mutation_ isn 't the problem, it's | _shared mutable state._ If you believe that philosophy (and I | do), then restricting yourself to pure functions over clonable | state feels like tying one hand behind your back. I hope I 've | made the case that providing finer grained access to mutable | app state is an approach at least worth exploring. | ______-_-______ wrote: | I think Redux is similar enough to Elm that it's not worth | mentioning separately. Redux plays fast and loose with types | whereas Elm uses types strictly; that's probably why Elm is | mentioned more often in the context of Rust. | | He does mention Redux in passing if you expand "Advanced topic: | comparison with Elm" | dwmbt wrote: | it's... it's beautiful... wishing Ralph good luck! his blog posts | have taught me sooo much in the past. i particularly enjoyed the | disclaimer at the end, a lot of these Rust projects aren't | production ready but learning about their respective | architectures is a great way to passively consume 'academic' | paradigms/concepts. if anything, this is what keeps me interested | in Rust! i don't have a formal background in CS, so new projects | and their inspiration serve as a great gateway into more rigorous | study. | | edit: the potential for Python bindings is very interesting, it | seems to me that Python and Rust are developing a sweet kinship | :,) | danappelxx wrote: | Really cool work. My impression after reading the article is that | it's significantly inspired by SwiftUI, but without the magic | annotations (@State, @Binding, @EnvironmentObject, @StateObject, | etc.). It will be interesting to see how Rust handles the fully- | statically-typed view tree, which has been really pushing the | limits of the Swift compiler. | | Question for the author: perhaps I missed it, but how do you plan | to handle view trees that change based on state (ie SwiftUI's | IfElseView + viewbuilder)? | raphlinus wrote: | Good catch! Yes, the plan is to implement _ConditionalContent. | It would look something like this: | if_view(bool_predicate, || view1(...then...), || | view2(...else...)) | | Whether we end up having a proc macro that has similar | functionality as ViewBuilder in SwiftUI is an open question. | For the time being, I'm seeing how far I can get with just | vanilla Rust. | | I'm generally pretty hopeful about the ability of the Rust | compiler to handle big complex types, but it is a risk. There | other projects out there that also stress it, and the compiler | team is pretty serious about making this work well. | adamnemecek wrote: | Is this inspired by adaptron by any chance? If no, do you have an | opinion on said work? | raphlinus wrote: | Adapton is definitely an inspiration, and I have cited it in | previous iterations (and even had it in an earlier draft - if | you check the Markdown source, the link def is still there). | | Here's my current thinking on the topic. It all depends on | whether you want to model your problem as a tree or a graph | (see also [1] for some great discussion of that tension). If | you model your problem as a graph, then a general purpose | incremental computation engine like Adapton (or Incremental or | Salsa) is great. However, if your problem is a tree, then | _constructing_ the explicit dependency graph requires a | nontrivial amount of ceremony. What Xilem does is represent the | most common tree-structured flows of information in a very | lightweight manner (mostly just plain code that builds views), | while allowing you to insert arbitrary graph edges if you like | with more work. | | I think it's a discussion worth continuing. One thing you could | do is integrate Adapton and Xilem together, where the | implementation of most view nodes in the latter becomes queries | into the Adapton engine. How well would that work? Really only | one way to find out. | | [1]: https://glazkov.com/2022/02/06/tension-between-graphs-and- | tr... | infogulch wrote: | Very interesting! This somehow seems convergent with the model- | level incrementalization approach that incr_dom [1] and its | successor bonsai [2] are using. Have you had a chance to compare | these? | | [1]: https://github.com/janestreet/incr_dom | | https://www.youtube.com/watch?v=R3xX37RGJKE | | [2]: | https://github.com/janestreet/bonsai/blob/master/docs/blogs/... | raphlinus wrote: | I would say that they were even greater inspirations for the | Druid architecture that predated this latest work - we were | hoping that a lot of the incremental/reactive patterns could be | expressed using combinators over immutable data structures. | That didn't work out as well as hoped; I think it's _possible_ | to build things that way, but it 's also pretty hard going and | the community never really reached critical mass. | | A semi-explicit goal of this work (that somehow didn't make it | into the blog post) is that developing for this architecture, | both building the UI components and using them, should be a lot | more _fun_ than before. Of course, that 's a slippery goal to | quantify. We'll just have to see how it goes, but I'm hopeful. | rektide wrote: | Author Raphlinus has an incredible history of great, superb | technical posts here, many which have spawned great | discussions[1]. | | Referenced in this article are: Xi-Editor, discussed in Xi-Editor | Retrospective[2] and Druid, discussed in Rust 2021: GUI[3], which | follows closely after Principled Reactive UI[4] (describing a | prototype for Druid called Crochet). | | > _I have long believed that it is possible to find an | architecture for UI well suited to implementation in Rust, but my | previous attempts (including the current Druid architecture) have | all been flawed._ | | These have all been very very good & technical posts on what | toolkits really support & enable ui (amid other great technical | topics too). It's delightful seeing such an ongoing continuation, | an evolcing refinememt of ideas & self-review from someone of | such expert caliber. | | Rarely do we get such an intimate view into what the real hunt | for rightness is, see how we ever hunt for perfection. Personally | I believe that hunt for better is one of the undertold aspects of | hackerdom, a less visible less knowable reciprocal to fast-and- | dirty. Tapping & enabling this creative, knowledge & intellect | based capability is a core spring from where greatness emerges. | | > _I have studied a range of other Rust UI projects and don't | feel that any of those have suitable architecture either._ | | It's also notable how widely raphlinus travels to get the best | persepctive available. This post begins with a a vast field | survey of other ui libraries & their origins. I lack the energy | to search down & link HN discussions on each of these, for there | are many! But seeing how everyone else is doing, looking wide & | far to explore their peer's attempts, is also a notable | characteristic I admire here. | | [1] https://news.ycombinator.com/from?site=raphlinus.github.io | | [2] https://raphlinus.github.io/xi/2020/06/27/xi- | retrospective.h... https://news.ycombinator.com/item?id=23663878 | (538 points, 26 months ago, 157 points) | | [3] | https://raphlinus.github.io/rust/druid/2020/09/28/rust-2021.... | https://news.ycombinator.com/item?id=24631611 (374 points, 31 | months ago, 244 comments | | [4] | https://raphlinus.github.io/rust/druid/2020/09/25/principled... | https://news.ycombinator.com/item?id=24599560 (234 points, 31 | months ago, 97 comments) | thurn wrote: | Animation is usually the biggest pain point with frameworks like | this -- and it usually feels like a complete afterthought for | framework designers. Of course you always have the simple CSS- | style stuff (here's a list of properties you can attach | transitions to) but as soon as you get into anything more | complicated it all falls apart. | chriskrycho wrote: | CSS _transitions_ are fairly restricted, but animation in CSS | is substantially more capable than transitions if you're using | the animations API instead: https://developer.mozilla.org/en- | US/docs/Web/CSS/CSS_Animati... | infogulch wrote: | CSS is pretty flexible. What kind of animations would you | expect to be difficult to express? | wtetzner wrote: | How important are animations in a UI? I typically find them | annoying (because you have to wait for them). | chriskrycho wrote: | Poor animation design has exactly the problem you describe. | Animation in general is a very useful affordance, though: it | allows you to convey the relationships between the various | nodes and modes in your UI. That ranges from being able to do | more than hard toggles on colors of buttons for different | modes to actually being able to do push/pop on "stacks" | within the UI, which is a concept mobile UIs in particular | make heavy use of. Overuse of animation can be an | accessibility problem, but having no animations or | transitions can actually _also_ be an accessibility problem | because it can make it hard for people to build a mental | model of what happened when navigation occurred. | [deleted] | melony wrote: | What are your thoughts on Sycamore? It uses Svelte-style compile- | time reactivity. | | https://github.com/sycamore-rs/sycamore | raphlinus wrote: | To be honest, I haven't looked too deeply into Sycamore. A lot | of the reactive machinery looks pretty similar to Dioxus | (threading a context scope, using explicit observable objects | for change propagation). I think it's worth comparing more | carefully, and would be more than happy to link a writeup to | such a comparison. | coryvirokmobile wrote: | Apologies for the noob question, but why reinvent the wheel when | we have HTML/DOM/CSS? | __jem wrote: | So that you don't have to ship an entire web browser for your | application. | chriskrycho wrote: | Besides the (correct!) sibling comment: the idea here is to | provide underlying primitives which can then be used to express | UI in a variety of contexts, first of all native UIs but (as | the post notes at the end) also _including_ the DOM. For | example, people have implemented experiments which use SwiftUI | for authoring HTML, and you can use React to author native UI | (React Native, but also e.g. the Raycast widget system), text | UIs, etc. | [deleted] | ______-_-______ wrote: | Big fan of your work, Raph. | | One small typo: | | > {anonymous function of type FnMut(u32) -> ()} | | It looks like the param type should be `&mut u32`. And in that | simple case the whole thing could probably just be `fn(&mut u32)` | since the closure doesn't capture any locals. | raphlinus wrote: | Heh yes, somebody else caught that. I think the current state | is ok. This closure won't capture any locals, but in general | closures in the view tree will. I'll take a PR if you think it | should be improved further :) | CyberRabbi wrote: | Seems like a large part of the complexity is enabling the | creation and use of reusable UI components that work in a variety | of UI hierarchies and modify a variety of backend models (or app | states), in a type-safe way. Is that right or are there other | problems attempting to be solved? | | The simple way to do this is a callback system. Why is that not | appropriate for Rust? Does it require custom ownership dynamics | that the borrow checker does not support? | stormbrew wrote: | Borrow checking gets a lot more complicated with closures that | live past their scope. It's usually much more frustrating than | it's worth. | tcmart14 wrote: | I think I just ran into this issue about a week ago. I | started working on a task manager written in Rust using the | Cursive library, which provides like an ncurses TUI. Heavy | use of callbacks, but all callbacks require a <'static> | lifetime. All the structures I make for information on the | database of course don't have <'static> lifetimes. I | eventually figured out that cursive has a function where you | can kind of give it the data, but to pull the data back out | requires a lot of cloning and boiler plate code. | LegionMammal978 wrote: | Isn't the standard solution to pass the data within an Rc | or Arc? That way, the closure still owns all of its data. | tcmart14 wrote: | I'll need to play around with that. I just started doing | a serious dive into Rust about a month ago. | steveklabnik wrote: | Callbacks are often isopmorphic to a big ball of mutable state, | which Rust makes very painful. | | It's like the classic Joe Armstrong quite about getting the | whole jungle when all you wanted is the banana. | | You can do it, and if you check out the Gtk/QT bindings you'll | see the boilerplate it introduces. Not insurmountable but many | people are interested in figuring out if there's a better way. | nicoburns wrote: | I'm sure I'm being naive, but could this be worked around by | passing a mutable reference to the state into the callback | rather than it closing over the state? Assuming there's only | one UI thread, then only one callback can run at once | anyway... | gpm wrote: | I think the fundamental problem with that approach is that | even in single threaded code rust makes it illegal to have | to have two pointers where at least one is mutable to the | same state. So I can pass in a `&mut State` pointer, but | then I can't also pass in a `&mut | AnElementInSomeListInState` pointer. Nor can I really have | `AnElementInSomeListInState` just have a parent pointer to | the rest of the state, because someone has to have a `&mut | State` pointer, and they would conflict (also because | doubly linked lists are hard in rust). ___________________________________________________________________ (page generated 2022-05-07 23:00 UTC)