State mutation

It might not be clear from the previous chapter, but we indirectly used Rc<RefCell<T>> to do state mutation. Indeed, our Playlist type contains a RefCell<Option<String>> and we wrapped our Playlist inside a reference-counted pointer. This was to be able to mutate the state in reaction to events, for instance playing the song when clicking the play button:

let playlist = self.playlist.clone();
let play_image = self.toolbar.play_image.clone();
let cover = self.cover.clone();
let state = self.state.clone();
self.toolbar.play_button.connect_clicked(move |_| {
    if state.lock().unwrap().stopped {
        if playlist.play() {
            set_image_icon(&play_image, PAUSE_ICON);
            set_cover(&cover, &playlist);
        }
    } else {
        playlist.pause();
        set_image_icon(&play_image, PLAY_ICON);
    }
});

Having to use all these calls to clone() is cumbersome and using the RefCell<T> type can lead to issues that are hard to debug in complex applications. The issue with this type is that the borrow checking happens at runtime. For instance, the following application:

use std::cell::RefCell;
use std::collections::HashMap;

fn main() {
    let cell = RefCell::new(HashMap::new());
    cell.borrow_mut().insert("one", 1);
    let borrowed_cell = cell.borrow();
    if let Some(key) = borrowed_cell.get("one") {
        cell.borrow_mut().insert("two", 2);
    }
}

Will panic:

thread 'main' panicked at 'already borrowed: BorrowMutError', /checkout/src/libcore/result.rs:906:4

Even though it is obvious why it panics in this example (we called borrow_mut() when the borrow was still alive in borrowed_cell), in more complex applications, it will be harder to understand why the panic happens, especially if we wrap the RefCell<T> in an Rc and clone it everywhere. This brings us to the second issue with this type: using Rc<T> encourages us to clone our data and share it too much which increases the coupling between our modules.

The relm crate takes a different approach: widgets owns their data and the different widgets communicate between them using message passing.