Andrew Lucker


Lattice 4.0: A solution to the View-State problem

Text boxes are a complex and sensitive issue: how and where do you store the text, and how do you update event listeners when a change happens.

The HTML model is simply to store the text box contents in the DOM and call update listeners whenever a relevant event is triggered. This model is time-tested and works well with how Javascript scopes variables into anonymous functions. Most programming languages (outside of functional) do not scope outside variables into closures. Lattice is built in Rust, which more or less has this problem.

“The problem” of View-State is that when an event is triggered we may want to reference both variables that were created 1) when the text box was created and 2) that have since been bound to the view component. This sounds trivial with Javascript, however by comparison Rust’s safety features regarding variable lifetimes prevent us from a simple solution here.

HTML’s storage of state in the DOM is storing information onto the View Component itself. Let’s call this strategy “binding to the left-side”. An alternative, would be to store the information into the environment that manages and calls the listeners. Let’s call this second strategy “binding to the right-side”. From this perspective, we can see that in HTML/JS, data is shared between the left and right side. This is basically an ideal solution to the View-State problem, however the question now is “How can we do the same in Rust”?

The first solution, which is perfectly sound though ugly, is to copy Javascript’s approach. To share a variable in Rust, we can put the value into a Reference Cell (Rc, RefCell, Box, etc.) and retrieve and/or update the value from both the left and right side. The downside to this approach, is that the client code becomes incredibly dirty and hard to read; not to mention every shared variable must be explicitly bound, unwrapped, and mutated using the obtuse rules of ownership and lifetimes. Maybe this approach will become easier in future Rust, however right now it is too much of a burden for my taste.

The second solution, which is used in Lattice, is to go with a restricted FRP style. This means that object state is stored “near” the left side, but not quite on the View Component. Managed by the engine there is an associated value for each view component and channels that listeners can subscribe to. When a component wants to update its state, it will call the state-setter function builtin to every Component. There may be multiple bound values of different types for each Component. Then, the engine checks whether a subscriber should be notified of the changes, and if so, fires off an event to the listener.

In this manner, left-side and right-side state can be referenced by using the engine as a mediator. The ownership of state is given to the mediator and Components and Listeners always receive borrowed values. This approach is very clean from the user perspective, and in practice works much like the Javascript approach.

I still hope that in the future, Rust closures will have more options and better defaults with regards to scoped variables, but for now there are plenty of workarounds.

This is what my git repositories usually look like

More by Andrew Lucker

Topics of interest

More Related Stories