Hello, all!

I’m one of the maintainers of Winit, the main pure-Rust window creation library. Even if you haven’t used it directly, you’ve probably heard of projects that depend on it - Servo and Alacritty being the best-known applications that depend on our codebase. If you’ve done any graphics programming in Rust using Glutin (or dependent projects including gfx-rs, Glium, and Amethyst) we’ve been the ones making the windows actually show up on your desktop.

This announcement details the major changes since Winit 0.19. Also, we are looking for new contributors! If you are interested in working on the foundations of Rust’s GUI story, now is a great time to join the project.

Winit 0.20 alpha 1 and the new event loop

The highlight of this release is the final major overhaul of Winit’s core Event Loop API. This

change vastly improves Winit’s stability and introduces a few quality-of-life upgrades, such as

native timer support and an API for injecting custom user events into the Winit event loop. For example:

#[derive(Debug, Clone, Copy)] enum CustomEvent { Timer, } let event_loop = EventLoop::<CustomEvent>::new_user_event(); // `EventLoopProxy` allows dispatching custom events /// to the WInit event loop from any thread. let event_loop_proxy = event_loop.create_proxy(); let timer_length = Duration::new(1, 0); event_loop.run(move |event, _, control_flow| { match event { // When the event loop starts running, queue the timer. Event::NewEvents(StartCause::Init) => { *control_flow = ControlFlow::WaitUntil(Instant::now() + timer_length); }, // When the timer expires, dispatch a timer event and queue a new timer. Event::NewEvents(StartCause::ResumeTimeReached{..}) => { event_loop_proxy.send_event(CustomEvent::Timer).ok(); *control_flow = ControlFlow::WaitUntil(Instant::now() + timer_length); }, Event::UserEvent(event) => println!("user event: {:?}", event), Event::WindowEvent { event: WindowEvent::CloseRequested, .. } => { *control_flow = ControlFlow::Exit; }, _ => () } });

This release also cleans up Winit’s API, universally improving both its internal consistency and its consistency with the rest of the Rust ecosystem.

Updating

You can get 0.20.0-alpha1 from crates.io by adding the following lines to your Cargo.toml :

[dependencies] winit = "0.20.0-alpha1"

If you’d like to use the new features through Glutin, add the following lines instead:

[dependencies] glutin = "0.22.0-alpha1"

Why an Alpha release?

There are a few reasons we’re introducing this as an alpha:

All implementations still need thorough testing, and some platforms still have major, application-breaking bugs. We’d like to merge an overhaul of Winit’s HiDPI API, but the overhaul is only implemented on Windows.

However, we lack the personnel necessary to implement those changes!

A call for help

That segues nicely into the second point I’d like to bring up: for such a core piece of infrastructure in the Rust ecosystem, we have astonishingly little manpower. Only three of the seven platforms we support have active maintainers: X11, macOS, Android, iOS, and WASM are all largely unmaintained, despite their importance! Additionally, Glutin is currently maintained by just a single person, despite being a project no lone individual has the time to handle. This had led to a great deal of issue buildup on those platforms, since we lack contributors with enough time to debug and resolve issues on those platforms. Major new features and API improvements can’t land on crates.io since we currently can’t implement them across all platforms - gamepad support being the most significant (although certainly not only) example.

If you are interested in Rust becoming a larger force in graphics ecosystem, please take a look at the issues below and find one you can contribute to! We encourage all types of contributions, so go out and write, test, review, and submit PRs, add and review documentation, and whatever else you would like to do. No matter what you do, your time would be very much appreciated, and would result in widespread improvements across the Rust community.

High-level issues:

FAQ

Why remove poll_events completely?

poll_events only functions as expected on Linux.

All other platforms break in some way with applications built around poll_events - some breaking in subtle ways, others breaking completely. For example, when a user resizes the application’s window, Windows* and macOS freeze the main event loop completely if the event loop uses poll_events . On the more extreme end, Mobile and Web platforms don’t ever return from poll_events , completely breaking any application that relies on poll_events functioning.

As much as we’d like to expose poll_events as an API, the reality is that we cannot expose it without fundamentally lying about its cross-platform functionality.

* You might notice that calling poll_events on Windows in legacy Winit doesn’t freeze the event loop. This is because legacy Winit spawns an entire background thread to run the Windows event loop in order to hide the freezing behavior. This has caused innumerable amounts of stability and UX problems, and moving to the new event loop model is the only way to fix those issues without creating an unstable tower of hacks.

Since poll_events is dead, is there any sort of replacement?

Yep! The run_forever API has been renamed to run , and received a few major usability upgrades:

You now receive an event when the platform event queue has been drained, allowing you to bundle your application logic into a single event handler. New windows can be created within the event handler closure, using the EventLoopWindowTarget field. You can now set timers in the event loop, adding proper support for framerate limiters and other timer-based functionality.

These new features make run actually usable in a real application, and should more than compensate for poll_events ’ removal.

Why does the new run function not return?

It was the only way for us to expose a single API that more or less behaves the same way on all platforms.

Admittedly, it isn’t necessary on desktop platforms (Windows, macOS, and Linux). However, Android, iOS, and WASM all require the host platform take exclusive control over the application’s event loop, and this API allows us to expose that behavior under the same API as our desktop backends.

If, for whatever reason, you absolutely must be able to return from the run function, we expose run_return on our desktop platforms. We’d discourage using that unless absolutely necessary, since it breaks cross-platform compatibility and can lead to subtle bugs on desktop platforms if used improperly.

Why break the API’s backwards compatibility now?

There were several small details in Winit’s public API that we weren’t happy with, but required breaking changes to fix. Since we were already breaking downstream applications with the new event loop API, we decided to try bundling all our desired breaking changes into a single release instead of painfully staggering them out over several point releases. This will make upgrading to 0.20 a bit more of a hassle, but should make the API significantly easier to use going forward.

Unfortunately we didn’t quite manage to include all the breaking changes we wanted this release, but breaking changes in the future should be significantly less disruptive and will be bundled with major feature releases.

Why is control_flow passed by reference to the event loop callback, instead of returned?

With a return-based solution, there isn’t one clear “correct” way of handling the loop’s control flow. Consider the following event loop:

event_loop.run(|event| { match event { Event::EventsCleared => ControlFlow::Poll, _ => ControlFlow::Wait } });

That situation leads to return patterns like Wait, Wait, Wait, {...}, Poll . What do you do with the Wait s that are returned before the Poll ? You could interpret them as “halt the event loop between each call to the function”, which is pretty clearly nonsense. However, it’s the only solution that doesn’t involve throwing away values. Other solutions could be to have some sort of “control flow precedence pattern” where returning one type overrides future returns of another type, or to ignore all of the return values except for the one from EventsCleared (in which case what’s the point of having it return anyway!).