Best practice for editable model

Hi,

I am looking for some guidance on the best way to implement an editable model. I have an application that builds a flight plan. The plan basically consists of a collection of sectors each with a starting airport and an ending airport and a list (vector) of waypoints in between.

The plan is built incrementally by adding sectors, airports and waypoints. In Java this would be a hierachy of mutable objects, but Rust is built on a basic premise of immutability (a good thing overall) and I really would like some help on how to construct a model, that is referenced from my view, a GtkTreeView, that I can easily mutate in a safe way.

All help gratefully recieved.

It is not. It's fine to mutate values if that's what your business logic requires.

3 Likes

Since you're dealing with gui, you likely have shared ownership (Rc or Arc) of your model. Rust enforces shared xor mutable, but provides cell types to work around it. Rc<RefCell<Model>> (single-threaded) or Arc<Mutex<T>> (multithreaded) might fit your case.

Keep in mind that interior mutability has two main drawbacks: 1. The compiler has to give up some optimizations to make it work (may not be a concern for most uses), and 2. It trades compile-time Shared XOR Mutability bugs for runtime panics/error handling. If you can avoid interior mutability, you probably should [1].

You mention a hierarchy, but the data structure that you describe sounds more like a graph. There are some crates that can do the heavy lifting for you with managing a graph and querying it. petgraph is a popular option, and daggy provides a higher-level abstraction over it if all you need is a DAG. Arenas allow constructing similar data structures (inherently self-referential) if you want to roll your own.

Any of these will allow mutability. You only need to be aware of taking an exclusive (mutable) reference; just don't try to hold it across shared references [2]! But you don't have to internalize this rule because the compiler will tell you when it happens. Most cases that trip up Rustaceans are complex cases that need clever solutions, like constructing a view to avoid limitations in the borrow checker.

Hopefully this info is helpful. Best of luck to you!


  1. But it isn't always possible. Catching these bugs at compile-time is almost always better, FWIW. ↩︎

  2. Sometimes easier said than done. ↩︎

1 Like

to incrementally build (or configure) a large data structure, it's often done using what's called "the builder pattern". the basic idea is simple: you use the type system to isolate the incremental configuring phase from the "fully-up-and-running" phase. you define two distinct but related types, (in fact more than often the builder type might have exactly the same layout as the "complete" type).

one type, the builder type, is used exclusively and can be mutated freely, but it cannot be used in normal operations. once the information is fully gathered and validated, a build function consumes the builder and produces the final data structure for normal use, which maintains whatever invariant the domain problem inhibits, and must be guarded and checked if it needs to be mutated.

there's similar concept in Clojure (which is famous for it's immutable and persistent functional data types), it's called "transient". normal data types in clojure are immutable, they can be converted to an transient data type if certain conditions are met. then you may use it like a java object, because the language guarantees transient data types are not shared. once the you've completed updating the transient data type, you can turn it back to normal immutable data like nothing happened.

2 Likes