Master resource vs. thread local storage

I'm writing a general purpose window management library and I've been torn on one major design decision that I have to make before making it available for others to use. This crate doesn't handle drawing to windows, but I'm also writing other crates for drawing with APIs such as OpenGL. This crate is only responsible for creating and modifying windows and polling for events.

Currently there is a Display struct that must be available when creating a window or polling for events. I consider the display to be a "resource" as well as the windows created by it, so it seemed like a good idea to subject it to Rust's ownership and borrowing system. When starting out, I felt that this was the cleanest and most "rusty" solution. The problem is that people who use the library may find it cumbersome to keep the Display around, but it helps to keep the relevant data in one thread. It would be easy to move this into thread local storage while keeping it available only to the first thread that requests it, but that feels like it may just be a lazy solution.

The provided example currently looks like:

fn main () {
    let mut display = Display::open().unwrap();

    let _window = Window::builder()
        .bg_color(Color::white())
        .title("Hello World")
        .visible()
        .build(&mut display).unwrap();

    loop {
        match display.wait_event().unwrap() {
            Event::CloseRequest(_) => { break; },
            _ => {},
        }
    }
}

If the Display object is removed, it will look more like:

fn main () {
    let _window = Window::builder()
        .bg_color(Color::white())
        .title("Hello World")
        .visible()
        .build().unwrap();

    loop {
        match wait_event().unwrap() {
            Event::CloseRequest(_) => { break; },
            _ => {},
        }
    }
}

In such a simple program, it doesn't make a huge distance, but I could imagine it being cumbersome to pass the Display reference all the time in a complicated program that has more than one dependency that uses this library. For example, even showing a modal message box would require a mutable reference to the display. This is a choice between cleanliness and ergonomics. What do you think? Would it turn people off to a library if they had to pass around a library object in some of the places where it's used?

Edit: Another important detail that I forgot to mention is that only one Display is allowed in a program. If the program tries to open another one, an error is returned. The reason for this is that Cocoa, which is one of the planned back ends, is not very thread safe and only allows windows to be used from the main thread. I guess there is a third option which allows multiple Display objects as long as they're all in the same thread.

There's been a similar discussion recently and people seem to favor passing the Display around. Static thread safety enforcement is a nice feature. It should still be possible to add simpler APIs relying on TLS (panicing on errors) if there is demand.

I see another problem with this example: the use of &mut references. I don't think you can get very far if every action requires a unique reference to the display. How would you create a window from an event handler closure?

1 Like

This is where something small in the back of my brain whispers that dreaded word: monads.

(Unfortunately not applicable to Rust)

Thanks for the feedback! The static guarantees of Rust are likely what brought people to it in the first place, and based on that thread, it seems like people wouldn't mind having to pass the Display around the program. If requiring the display to be mutable is a problem for ergonomics, this would be easy to change by using a RefCell behind the scenes.