[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)