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