2

I am trying to implement a container which holds a list of GUI widgets where each widget needs access to that container. Each widget may need to modify other widgets on some event. For example, when a user clicks button the editor text will get updated.

I could use a boxed HashMap but it doesn't solve the problem. What would be the easiest way to achieve what I need?

Here is what I currently have, it doesn't compile but you will get the idea:

use std::collections::HashMap;

struct SharedItem<'a> {
    pub value: String,
    pub store: &'a HashMap<String, SharedItem<'a>>,
}

fn trigger_button(button: &SharedItem) {
    // use case where SharedItem has to be mutable
    let mut editor = button.store.get(&"editor".to_string()).unwrap();
    editor.value = "value inserted by button".to_string();
}

fn main() {
    // map shared items by their name
    let mut shared_store: HashMap<String, SharedItem> = HashMap::new();

    // create components
    let editor = SharedItem {
        value: "editable content".to_string(),
        store: &shared_store,
    };

    let button = SharedItem {
        value: "button".to_string(),
        store: &shared_store,
    };

    shared_store.insert("button".to_string(), button);
    shared_store.insert("editor".to_string(), editor);

    // now update the editor by triggering button
    trigger_button(shared_store.get(&"button".to_string()).unwrap());
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
insanebits
  • 818
  • 1
  • 6
  • 24
  • This is going to be a duplicate of [this question](http://stackoverflow.com/questions/32300132/why-cant-i-store-a-value-and-a-reference-to-that-value-in-the-same-struct) – Shepmaster Oct 05 '15 at 12:53
  • @Shepmaster Yes it's a variation of the same question, but it doesn't have a solution, I would like to know how one can solve it. The main requirement is to have an access to the storage structure from an item inside storage. – insanebits Oct 05 '15 at 13:05
  • 1
    http://stackoverflow.com/questions/27001067/how-can-i-make-a-structure-with-internal-references/27011347#27011347 is actually closer. especially since it has the answer to this question: use reference counted boxes – oli_obk Oct 05 '15 at 13:08
  • @ker, that's actually quite close to what I need:) I will add an answer if I get it working unless someone else answers it :) – insanebits Oct 05 '15 at 13:18

2 Answers2

3

This is required because widget may need to modify other widget on some event. For example: user clicks button and as a consequence editor text will get updated.

Just because this is how most GUI operates in OO languages does not mean that this is how it must be done.

In this specific case, a simple solution is to:

  1. Store as widget ID the ID of the editor box.
  2. Post an event into some event queue, indicating the update to be performed and the widget (by ID) on which to perform it.

Thus, at any point in time, at most one mutable handle on a widget exists in the system, which avoids aliasing and keeps Rust happy.

Note: this answer assumes that you do not need a synchronous response from the editor widget, if you do and use such a system, you fall into callback hell.

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
3

How can I have a container where the items have access to the container?

You cannot do this with references. You could do this with reference-counted boxed values.

However, you can still solve your problem by thinking in a different way...

each widget needs access to that container. Each widget may need to modify other widgets on some event

If it fits with your problem domain, it's much cleaner to simply post events in a one-directional flow of data:

// Create meaningful events with data pertinent to those events.
enum Event {
    UpdateText,
    Click,
}

struct Events(Vec<Event>);

impl Events {
    fn push(&mut self, event: Event) { self.0.push(event) }
}

trait Widget {
    fn event_happened(&mut self, event: &Event, triggered_events: &mut Events);
    fn draw(&self);
}

struct Widgets(Vec<Box<Widget>>);

struct TextField(String);

impl Widget for TextField {
    fn event_happened(&mut self, event: &Event, _: &mut Events) {
        match *event {
            Event::UpdateText => self.0.push_str("event"),
            _ => (),
        }
    }

    fn draw(&self) {
        println!("Drawing text: {}", self.0);
    }
}

struct Button;

impl Widget for Button {
    fn event_happened(&mut self, event: &Event, triggered_events: &mut Events) {
        match *event {
            Event::Click => triggered_events.push(Event::UpdateText),
            _ => (),
        }
    }

    fn draw(&self) {
        println!("Drawing button");
    }
}

fn main() {
    let mut events = Events(vec![]);

    let mut widgets = Widgets(vec![
        Box::new(TextField(String::new())),
        Box::new(Button),
    ]);

    // This would probably loop forever until a shutdown event was posted
    for i in 0..10 {
        for widget in &widgets.0 {
            widget.draw();
        }

        // Fake a click at some point
        if i == 0 {
            events.push(Event::Click);
        }

        let mut next_events = Events(vec![]);
        for event in &events.0 {
            for widget in &mut widgets.0 {
                widget.event_happened(event, &mut next_events);
            }
        }
        events = next_events;
    }
}

To be clear, this is what Matthieu M. was suggesting (without a target ID); I just had already typed out the example so I wanted to post it anyway ^_^.

Community
  • 1
  • 1
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • that will be way better approach than going against the rust memory safety. It's not that easy shifting your mindset coming from the OO background.. I will accept it as an answer. Thanks for your effort! – insanebits Oct 05 '15 at 13:42
  • 1
    *going against the rust memory safety* — to be clear, there's nothing **unsafe** about using `Rc>`, it simply defers enforcing that memory safety from compile-time to runtime. – Shepmaster Oct 05 '15 at 14:00
  • @Shepmaster: On the other hand, while it's safe, it's very easy to end up with a reference cycle somewhere and leaking memory, which is annoying. – Matthieu M. Oct 06 '15 at 07:33