[HN Gopher] Xilem: An Architecture for UI in Rust
       ___________________________________________________________________
        
       Xilem: An Architecture for UI in Rust
        
       Author : raphlinus
       Score  : 429 points
       Date   : 2022-05-07 19:09 UTC (1 days 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!
        
       | aaaaaaaaaaab wrote:
       | Another UI framework that re-renders everything each cycle? Yeah,
       | it's gonna be awesome for todo lists (as long as you don't have
       | more than ~100 todo items). Meanwhile high-performance apps will
       | still be written in retained-mode UI toolkits.
        
         | eximius wrote:
         | This is targeting retained mode.
        
           | aaaaaaaaaaab wrote:
           | Ummmm... no?
           | 
           | "As is completely standard in declarative UI, it is done by
           | diffing the old view tree against the new one, in this case
           | calling the rebuild method on the View trait. This method
           | compares the data, updates the associated widget if there are
           | any changes, and also traverses into children. The view tree
           | is retained just long enough to do event propagation and to
           | be diffed against the next iteration of the view tree, at
           | which point it is dropped. At any point in time, there are at
           | most two copies of the view tree in existence."
        
       | flohofwoe wrote:
       | Obviously Raph knows what he's doing, so this isn't a critique,
       | but a question:
       | 
       | What's the reason for replicating the "React idea" outside the
       | web browser?
       | 
       | The way I understand the motivation behind React is that it tries
       | to work around the too high level and too rigid DOM by mapping
       | one way to describe an UI (the React API) to another (the DOM),
       | for the only reason that there's no realistic way to bypass the
       | DOM (except doing _everything_ yourself - including fundamentals
       | like text rendering - via a 2D- or WebGL canvas).
       | 
       | But if you don't have something as rigid as the DOM as lowest
       | layer to begin with, what's the point of building a React-style
       | system with 'tree diffing' against data that persists across
       | frames? Is it really worth the complexity managing intermediate
       | data that sits between the UI description that (most likely)
       | needs to be updated each frame, and the low-level rendering
       | instruction stream - which most likely also needs to be generated
       | each frame?
       | 
       | Or is it all about Rust's language restrictions?
        
         | loxs wrote:
         | As someone who writes exclusively Rust on the backend and React
         | on the frontend... To me React is a "language", not the
         | technique of the "tree-diffing". And I think it's the best one
         | there is for expressing an UI. The tree-diffing (and the speed
         | deterioration) is only the (unfortunate) way to compile it to
         | the DOM. Still worth it IMO. With Rust it can probably be done
         | both safely and imperatively (i.e., fast) while retaining the
         | declarative language.
         | 
         | Haven't really thought it over fully, but so far it sounds
         | really nice.
        
           | mikevm wrote:
           | What about various libraries that don't use a virtual DOM and
           | have vastly better performance than React, for example
           | https://www.solidjs.com or Svelte?
        
             | loxs wrote:
             | Svelte is nice, although a lot less ergonomic than React as
             | it is not "native JS" which creates the need for "two
             | domains" of programming - one, the Svelte domain (which is
             | later compiled) and one JS domain which is called as a
             | library. Haven't tried SolidJS yet.
             | 
             | Cannot really say if Svelte is worse than React, as I have
             | much more experience with React itself. My main blocker to
             | adopt it (Svelte) is mostly library support - with React
             | one can use "everything" in all kinds of "hackish" ways and
             | with TypeScript, which wasn't really the case with Svelte
             | last I checked, maybe 2 years ago.
        
               | miohtama wrote:
               | Svelte might not be internally as ergonomic as React, but
               | the fact that it ditches "everything needs to be native
               | JS" principle, allows one to build components with much
               | less boilerplate. This translates to more readable code,
               | less boilerplate and lines to type. Effectively Svelte
               | sacrifices some of the purism principles in the favour of
               | better developer productivity.
               | 
               | The trick with Svelte is that "two domains" are very,
               | very, close to each other. A JavaScript programmer can
               | learn and understand Svelte after 2 days tutorials.
               | 
               | Library support is something that can be improved only
               | over time.
        
               | loxs wrote:
               | Yeah, I agree, but I suspect that React is still the
               | "right" tool for 80% of the software out there (having
               | all of these things in mind). Though I should give Svelte
               | another go soon maybe :)
        
       | [deleted]
        
       | 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"
        
         | mkishi wrote:
         | Apart from the architectural similarity, Elm also predates both
         | Redux and Flux, even being referenced in Redux's Prior Art page
         | [1].
         | 
         | [1]: https://redux.js.org/understanding/history-and-
         | design/prior-...
        
       | 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.
        
       | ogoffart wrote:
       | > The problem is that it requires shared mutable access to that
       | state, which is clunky at best in Rust (it requires interior
       | mutability).
       | 
       | I don't see the problem with using interior mutability
       | (Rc<RefCell<T>> or Arc<Mutex<T>>)
       | 
       | With Slint [1], we just embrace it, and rely on interior
       | mutability for the shared state, and that works well.
       | 
       | [1] https://github.com/slint-ui/slint
        
         | tayistay wrote:
         | I tried using interior mutability in rui [1] but the clunkiness
         | appeared in having to call clone on the Rc/Arc too often. I'd
         | have a few clones before moving into a closure, like this:
         | let text = self.text.clone();             focus(move
         | |has_focus| {                 let text = text.clone();
         | state(TextEditorState::new(), move |state| {
         | let text = text.clone();                     let text2 =
         | text.clone();                     let cursor = state.with(|s|
         | s.cursor);                     let state2 = state.clone();
         | 
         | currently looks like this:                   focus(move
         | |has_focus| {             state(TextEditorState::new, move
         | |state, cx| {                 let cursor = cx[state].cursor;
         | canvas(move |cx, rect, vger| {
         | 
         | (Note the context (cx) passed to callbacks to look things up.)
         | 
         | [1] https://github.com/audulus/rui
        
       | 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.
        
           | [deleted]
        
       | 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...
        
         | grayrest wrote:
         | > Animation is usually the biggest pain point with frameworks
         | like this -- and it usually feels like a complete afterthought
         | for framework designers.
         | 
         | It tends to be. The only (popular) JS framework where it's a
         | first class concept is with Svelte. There's a basic set of
         | transitions and the framework handles the post-out-transition
         | removal (which is annoying in a lot of frameworks) but there's
         | also custom CSS animations where a JS-defined curve is rendered
         | into CSS [1], semi-automated FLIP animations for non-
         | transitioning nodes [2], and deferred transitions where the
         | outro of one element is sync'd with the intro of another [3].
         | There's a few other things like springs and whatnot but I
         | consider the package as a whole to be a significant advancement
         | in the state of the art.
         | 
         | [1] https://svelte.dev/tutorial/custom-css-transitions [2]
         | https://svelte.dev/tutorial/animate [3]
         | https://svelte.dev/tutorial/deferred-transitions
         | 
         | The only other framework I know of that's tackled it in core is
         | Inferno but I haven't read through their implementation in
         | detail.
        
           | IshKebab wrote:
           | Flutter is also very good at animation. I suspect that a
           | significant part of the motivation for Flutter was Android
           | developers getting frustrated with the repeated
           | unsatisfactory attempts at an animation API for Java Android.
           | I think there are like 4 different animation APIs in Java
           | Android now.
        
             | ink404 wrote:
             | The compose APIs for animation are quite nice on Android
             | now
        
         | 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).
        
           | ativzzz wrote:
           | Simple sliding animations for going back/forward in mobile
           | screens add so much more confirmation to what exactly is
           | happening when you click an element. Or when you tap a
           | button. That feedback you get when you interact with an
           | element is often some kind of animation.
        
           | pjmlp wrote:
           | In modern UIs, even screen transitions rely on animations.
        
           | 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.
        
             | berkes wrote:
             | I'd go so far as to state that anyone who 'hates
             | animations' uses them daily.
             | 
             | The ones you don't notice are most often good - yet they
             | are there. You'll notice degrading UX when they aren't. The
             | ones you notice are quite likely bad, but that doesn't make
             | the majority of aniation bad.
             | 
             | Anything, from a blinking cursor, to the highlight effect
             | on your smartphone keyboard to the check-uncheck of a
             | checkbox is animated. Subtle, but crucial.
        
           | viraptor wrote:
           | The basic stuff is incredibly important, but there's also
           | lots of fluff in many applications which is why you find it
           | annoying.
           | 
           | My two go-to examples:
           | 
           | - The shopping app I use has some kind of points system and
           | some items give you "boost". I hate the concept and don't
           | even care what it does because adding those items animates a
           | bar filling up and take extra 4s while I'm trying to do this
           | as quickly as possible. This is a terrible use of animations.
           | 
           | - Any app where you can rearrange a list _needs_ an animation
           | for when you hold an item and try to move it. Specifically,
           | other entries shouldn 't instantaneously jump around when you
           | cross some threshold. There's lots of subtle ways to achieve
           | this, but one of my favorites is greying out the old space
           | and springing items up/down as you move the chosen one
           | around. This animation is almost required, because the
           | experience is jarring otherwise.
        
           | golergka wrote:
           | In game development, extremely important. And to increase
           | complexity, they can also "delay" state updates from the
           | actual state to what is presented to the user. The player's
           | gold purse might already have that 10gp, but the gold counter
           | at the top should only be updated when the gold icon
           | completes it flight from the center of the screen. Also, this
           | icon? It was originally rendered in the game layer underneath
           | the ui, and has to change layers (which usually have very
           | different layout algorithms) and un-chain itself from the
           | camera movement during this animation, completely unnoticed
           | by the player.
        
       | chaosprint wrote:
       | Very insightful post. Although my work is mainly about audio not
       | GUI, I still learn a lot from your idea. For my audio work, I
       | also use declarative style and diff algorithm for updating the
       | audio graph. But when rewriting it, I found sending messages is
       | also very convenient:
       | 
       | https://github.com/chaosprint/glicol/tree/main/rs/synth
       | 
       | This might be interesting for you as I found that you also have a
       | synth project (https://github.com/raphlinus/synthesizer-io).
       | Admittedly, there is still some way to go for this audio lib.
       | Will further study this post when I got more spare time.
        
       | conradev wrote:
       | Have you thought about backing the persistent widget tree with
       | native widgets as the final layer? Or, at least supporting it as
       | a render target. That is, UI/NSViews on Apple platforms, DOM
       | nodes on web, GTK objects on Linux, etc. Essentially using them
       | as the final "render tree".
       | 
       | SwiftUI takes this approach, as ComponentKit did before it. The
       | framework still does most everything (state updates, layout,
       | animation, etc), but the host compositor handles the actual
       | rendering.
       | 
       | This approach has a few advantages in my opinion. These widgets
       | integrate nicely with the host accessibility system, for one.
       | They also can come with built-in styling to make them "fit into"
       | the target platform.
       | 
       | There are also performance benefits to leveraging the system
       | compositor - Firefox switched to have platform native layers on
       | macOS and it helped immensely with battery life.
       | 
       | It is worth noting that SwiftUI and ComponentKit components don't
       | have 1:1 mappings with native widgets - they both perform
       | flattening (i.e. for drawing paths and layout only nodes) to
       | optimize their performance.
       | 
       | I think most importantly, though, it allows for rewriting code
       | incrementally as that approach naturally supports bidirectional
       | embedding. Having rewritten the Shortcuts app editor from UIKit
       | into ComponentKit into SwiftUI (maybe one of the larger projects
       | written in SwiftUI?), incremental rewriting is crucial for
       | existing software projects.
       | 
       | The other concern is mobile - it just is _not_ feasible to
       | rewrite the text input stack on mobile, for example, so being
       | able to use native text editing views would very much be
       | necessary.
       | 
       | This all looks fantastic!
        
         | raphlinus wrote:
         | I've thought about it. Your "etc" in the first paragraph is
         | doing a lot of work, especially on Windows. The idea that there
         | is a "native" widget set is increasingly a fiction. I agree
         | though that on mac and iOS it makes quite a bit of sense.
         | 
         | A somewhat galaxy-brain approach to this is to make views
         | generic over Cx, and add "factory" methods to Cx for creating
         | button, stack, slider, etc.
         | 
         | All that said, my personal feeling is that while this would
         | give promising early results for creating simple property-sheet
         | like UI, it will be extremely difficult to make polished, truly
         | native-feeling UI in it, as ultimately the seams will show. The
         | first 90% will go well, but the second 90% will be painful.
         | 
         | I don't want to discourage people from trying it though!
        
         | pkphilip wrote:
         | Personal opinion but I think the old RAD approach in tools like
         | Delphi where the components are laid out visually using the IDE
         | but having the option of generating the components
         | programmatically during runtime including attaching/detaching
         | event handlers etc with state handled application-wide or
         | within the context of the "form" is a much faster way to
         | develop than to use the declarative approach in tools like
         | Flutter where state management is really complex.
         | 
         | Also, one could develop custom components very easily in Delphi
         | with custom draw events which would draw just the component.
        
           | tayistay wrote:
           | I haven't used Delphi, but what you're describing sounds like
           | Interface Builder, which I've used a lot. The biggest problem
           | I had with IB was that it got quite tedious to specify all
           | the constraints to make an app responsive to different
           | screen/window sizes. Plus, you'd have some constraint in
           | there and you wouldn't remember why it was there! IIRC you
           | couldn't add comments. Also you couldn't diff the files
           | because it was a ton of XML and merging them was really
           | scary. I think bigger teams tended to stay away from
           | IB/Storyboards altogether (storyboards were really bad
           | because it was all centralized in one file).
        
       | [deleted]
        
       | [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.
        
       | eximius wrote:
       | I think the answer to this is no, but I'll ask anyway.
       | 
       | Is there any concern with the allocation for the view/widget tree
       | every cycle? I know it's retained mode so the most important
       | resources (graphics) are cached between cycles, but I'm wondering
       | if the trees ought to be allocated out of arenas or something so
       | that the allocating/freeing every cycle isn't undue performance
       | penalty.
       | 
       | (I'm not sure what existing regained frameworks do.)
        
       | archagon wrote:
       | Great technical article as always, and apologies for not
       | responding to the meat of the article, but I tend to look at
       | declarative UI with some degree of skepticism. Raph, do you think
       | this general approach can eventually be scaled up to develop
       | complex and highly interactive applications such as DAWs, video
       | editors, and graphic design software? At least based on my
       | experience with SwiftUI, I find the paradigm easy and satisfying
       | to get started with, and great for prototyping and widgets, but
       | quickly run into roadblocks whenever trying to build creative
       | software that's not just a view around a database. Or do you see
       | this as just another tool in the UI toolbox, and not necessarily
       | mutually exclusive with imperative and immediate mode UI? Curious
       | to hear your thoughts!
        
         | raphlinus wrote:
         | I think your skepticism is well placed, and I think you are
         | asking the right question.
         | 
         | Here is why I'm hopeful. SwiftUI is as you say wonderful, but
         | is very much a closed ecosystem. You can't really implement
         | your own custom views, rather you can assemble the premade ones
         | in various (cool and interesting!) ways. If you wanted grid
         | layout before iOS 14, you were on your own.
         | 
         | By contrast, in what I'm building, everything is open-ended,
         | and you are invited to build fully custom versions of every
         | piece of the system - change propagation, async resource
         | loading, layout, drawing, animation, everything.
         | 
         | So yes, I am hopeful this approach will give good results for
         | especially those highly intensive applications you mention. And
         | if not, we'll learn something why not. I'm looking forward to
         | trying!
        
           | elcritch wrote:
           | I'll have to checkout your project. I've been experimenting
           | with something similar, but in Nim. It's interesting to
           | devise declarative or immediate mode UIs that make use of
           | compiled languages with good async. Animations become very
           | easy. I think those more complicated UIs may work well with
           | declarative frameworks.
           | 
           | I'm curious to see how you're handling events. Best of luck!
           | 
           | 1: https://github.com/elcritch/fidgetty
        
             | elcritch wrote:
             | The node id path is interesting. I disabled it for
             | performance reasons in Fidgetty since it wasn't used, but
             | was contemplating using it for a theming system similar to
             | CSS. Do you have any ideas of combining declarative style
             | UIs and CSS theme-ing?
        
         | tayistay wrote:
         | Ok I'm not Raph but, FWIW, my 3d sculpting app is written using
         | SwiftUI (https://sculptura.app), which is not a view around a
         | database (in fact it was previously UIKit... SwiftUI is so much
         | better). I recently implemented a piano-roll editor in SwiftUI:
         | https://github.com/AudioKit/AudioKitUI/tree/main/Sources/Aud...
         | . I have little doubt a DAW, at least, could be implemented in
         | SwiftUI easier than with UIKit or AppKit.
        
       | 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]
        
       | Klasiaster wrote:
       | Would be great to eventually have different backends for this,
       | like Java had where the app looked native in Windows, Gtk, etc.
       | One could even think of having a TUI backend besides an HTML or
       | Canvas backend.
       | 
       | That would be restricting of course when one needs features
       | available in only one backend and developers could opt-in for
       | more control and require a certain backend like Gtk or not
       | require but detect the backend and get extra features.
        
       | infogulch wrote:
       | What happens if two child components need mutable access to the
       | same data? Say, a group of dropdowns to filter/sort a table plus
       | a pie chart with clickable slices that also change the filtering.
        
         | raphlinus wrote:
         | If two components generate events in the same cycle, they get
         | queued, and the corresponding callbacks are run sequentially,
         | each time with a mutable borrow of the app state. This seems
         | like the most correct and ergonomic approach to me.
        
           | infogulch wrote:
           | That makes sense.
        
       | ______-_-______ 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 :)
        
       | berkut wrote:
       | I've never persevered with immediate-mode UIs deeply enough to
       | get to the point I needed to solve this, given I mostly deal with
       | complex nested UIs and in my (possibly limited/incomplete?)
       | experience, immediate-like UIs don't really work well for that
       | type of complex and dynamic setup, but it sounds like the Widget
       | tree persists in this model (at least more than the other trees),
       | but it's not clear if it's update-able as well? (there is mention
       | of it being rebuild-able, so I guess so?).
       | 
       | I wonder what happens regarding state (say, selection state, or
       | visibility/enabled state) in the case where you might want to
       | allow the user to re-arrange entire UI components (say the user
       | draging a nested tab/pane from one window of the app to another,
       | and docking it into another different hierarchy): would the trees
       | have to be completely re-built (I guess sub treelets could still
       | be kept?) along with re-building the id paths. Would that mean
       | diffing is hard/impossible in some cases to transfer across a
       | large re-build of these trees?
        
         | raphlinus wrote:
         | First question is easy: yes, the widget tree can be updated.
         | That's generally done by diffing data stored in view nodes, but
         | in fact the View trait is open-ended and you can implement the
         | rebuild method to do anything you like. And yes, selection
         | state lives in widgets and it is absolutely a goal to have that
         | persist.
         | 
         | The second question is more challenging (see the "advanced
         | topic" under identity for a little more background). View id's
         | cannot be re-parented, in other words when a parent
         | relationship is expressed in an id path (by virtue of having
         | the child id follow the parent in a path), that relationship
         | cannot be changed. However, widget ids and view ids are not
         | _necessarily_ the same, though they can be. I think what 's
         | needed for your use case is a level of indirection so the
         | _view_ id paths remain stable, but the _widget_ id structure
         | relationships can be changed. I haven 't worked out all the
         | details, but think it can be done, and if it's done right it
         | wouldn't require any rebuilding of widget subtrees.
        
           | berkut wrote:
           | Thanks for the info!
        
       | 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.
        
               | pornel wrote:
               | Tip for you: when the compiler says you need a 'static
               | lifetime, it's actually trying to say that all temporary
               | references are forbidden and won't work. Arc is a
               | reference, but isn't temporary. Add a mutex or atomic
               | when you need to modify data behind arc.
               | 
               | This is the "interior mutability" approach that the
               | article mentions.
        
               | stavros wrote:
               | This is very useful, so useful that I can't help but
               | think that the compiler actually _should_ say what you
               | said.
        
               | viraptor wrote:
               | Try raising an issue on GitHub. Rust people are
               | interested in improvements like that. I'm not saying it
               | will be accepted, but it won't be ignored.
        
               | stormbrew wrote:
               | I definitely think the error could be improved but I'm
               | not sure it should really say "just shove it in an
               | `Arc<Mutex>`," because that really is only sometimes the
               | right solution and it might lead people to make poor
               | choices by default.
               | 
               | I think a lot of the problem is really with the
               | confusingness of the concept of static lifetime in rust
               | to begin with, where it's kinda used for both "lives
               | forever" and "doesn't reference anything that lives
               | longer than it"[1]. I hope someday these meanings get
               | different names, tbh.
               | 
               | But when you see that error the naive thought is like...
               | "ok better make it live forever" but it really just means
               | you need to make it something that you control, and there
               | are other ways to do that than a refcounting mutex.
               | 
               | [1] https://doc.rust-lang.org/rust-by-
               | example/scope/lifetime/sta...
        
               | stavros wrote:
               | That's very useful, thanks!
        
               | stavros wrote:
               | That's very useful, thanks! I need to learn more Rust.
        
         | 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).
        
             | GolDDranks wrote:
             | The problem is that multiple callbacks might require a
             | reference to the same state, and closing over those
             | references and retaining that callbacks, makes two mutable
             | references to the same state, which Rust makes an illegal
             | pattern. That is indeed safe in single-threaded code, but
             | Rust prevents it nevertheless; there is a famous (in Rust
             | circles) blog post about this:
             | https://manishearth.github.io/blog/2015/05/17/the-problem-
             | wi...
             | 
             | There is a workaround called "internal mutability", an
             | ability to mutate state pointed by a shared pointer. That
             | is syntactically slightly messier, and generally frowned
             | upon. The blog post mentions about this workaround, and
             | also states that it's not ideal so we should strive for
             | better.
        
               | [deleted]
        
               | CyberRabbi wrote:
               | > There is a workaround called "internal mutability", an
               | ability to mutate state pointed by a shared pointer. That
               | is syntactically slightly messier, and generally frowned
               | upon.
               | 
               | I don't see why this would be frowned upon. It seems to
               | be a runtime version of the borrow checker. For
               | sufficiently dynamic code I don't see how you can get
               | around checking mutability access at runtime (or
               | asserting that multiple blocks of code aren't
               | concurrently requesting a mutable reference).
               | 
               | As a comparison, there are many cases where the compiler
               | can prove that a bounds check is unnecessary for
               | accessing an element in a vector but there are also many
               | useful cases where it's simply not possible to do at
               | compile time. It would be silly to frown upon runtime
               | bounds checks when the requested index or the size of the
               | vector is not known at compile time, a common occurrence
               | in many interesting programs.
               | 
               | I get the motivation to make APIs as statically checkable
               | as possible but it doesn't seem to always be practical.
               | Reusable UI components can be used in a variety of
               | contexts, e.g. situations with multiple callbacks for
               | different backends. The information is just not always
               | there at compile time.
        
               | jcelerier wrote:
               | As someone who had literal paid gigs in c++ where I had
               | to remove all sorts of reference counts to reach
               | performance targets, it's depressing that there doesn't
               | seem to be a better way in rust
        
               | CyberRabbi wrote:
               | The way to do this in Rust is to use "unsafe." It
               | essentially means "trust the programmer that this pointer
               | is live."
               | 
               | I don't think there is a way around this. You're dealing
               | with a high-level runtime condition. There are no
               | tractable ways to get compilers to understand these
               | higher level runtime conditions, so conditions must be
               | reproven at runtime. It needs an oracle. That oracle
               | should be you but you've decided that you cannot be
               | trusted. This appears to be somewhat of a contradiction.
               | Should you even be writing this program in the first
               | place?
        
       | AbuAssar wrote:
       | kudos for explaining the meaning of the name, alot of projects
       | just puck a random name and call it a day.
       | 
       | > The name "Xilem" is derived from xylem, a type of transport
       | tissue in vascular plants, including trees. The word is spelled
       | with an "i" in several languages including Romanian and Malay,
       | and is a reference to xi-editor, a starting place for
       | explorations into UI in Rust (now on hold).
        
       ___________________________________________________________________
       (page generated 2022-05-08 23:02 UTC)