# antoyo/relm: an Elm-style GTK+ GUI library for Rust

> relm wraps GTK+ in an Elm-inspired update loop, with a #[widget] attribute that generates the boilerplate. It is beta software with a small, GTK-shaped surface, and it is not the same project as Relm4.

**antoyo/relm** — Idiomatic, GTK+-based, GUI library, inspired by Elm, written in Rust

- Repository: https://github.com/antoyo/relm
- Stars: 2,444 · Forks: 78
- Language: Rust
- License: MIT
- Published: 2026-09-28 · Updated: 2026-09-28 · Language: en
- Canonical page: https://hysenlabs.com/projects/antoyo-relm

## What relm solves, and who it is for

GTK+ in Rust is a pile of widgets, signals and callbacks. Wiring a delete_event handler or a button click to application state means writing closures that capture the widgets they mutate, and the borrow checker notices every one of them. relm's answer is to take the state out of the callbacks and put it in a Model, then let messages carry events to a single update function. The README describes the library as "Asynchronous, GTK+-based, GUI library, inspired by Elm, written in Rust".

The audience is narrow and specific. You need GTK+ installed on the system before relm is usable, and the README points at the GTK+ installation page rather than shipping its own prerequisites. If your application is already a GTK+ application, relm changes how you organise state, not which toolkit you ship. If you wanted a cross-platform toolkit with its own renderer, this is the wrong layer.

The README opens with a warning: the library is in beta, has not been thoroughly tested, and its API may change at any time. Treat that as a design constraint, not a disclaimer. The last push to the repository was on 2026-07-23, so the project is moving, but there are no retrieved releases to pin against beyond the crate version.

## The Model, Msg and Update loop

The mechanism is three types and one trait. A Model struct holds the data related to a Widget. A Msg enum, derived with relm_derive::Msg, names the events. An impl Update for Win declares type Model, type ModelParam and type Msg, and implements model() to build the initial state and update() to react to each message. Widget::view creates the GTK+ widgets and returns the struct; Widget::root returns the root widget, in the README's example a Window.

Events enter the loop through the connect! macro. The README's example connects the window's delete_event signal to the Quit message and returns (Some(Msg::Quit), Inhibit(false)) from the callback, which is the shape you use when the GTK+ callback needs to return a value. A second form of connect! exists for signals that do not need a return value.

The #[widget] attribute is the other half of the design. It generates the view! macro for declarative widget construction, creates the struct and the associated types automatically, and inserts a call to Widget::set_property() inside update() whenever you assign to a model attribute. In the counter example, changing self.model.counter is enough for the label bound with text: &self.model.counter.to_string() to follow. That binding is the part worth understanding before you commit: the attribute rewrites your update function, so what you read in the source is not exactly what the compiler sees.

## Installing GTK+, adding the crates, and a first counter

There is no relm-specific installer. Since relm is based on GTK+, the README says you need GTK+ on your system and links to the GTK+ installation documentation. Once that is present, the crate dependencies from the README go into Cargo.toml:

```toml
gtk = "^0.16.0"
relm = "^0.25.0"
relm-derive = "^0.25.0"
```

Note the gap: the README's Usage section pins gtk 0.16.0, while the repository Cargo.toml lists gtk = "0.18.2" and version = "0.25.0" for relm itself. Check crates.io for the version that matches your GTK+ bindings before copying either line.

The imports the README lists for a relm application are these:

```rust
use relm::{connect, Relm, Update, Widget};
use gtk::prelude::*;
use gtk::{Window, Inhibit, WindowType};
use relm_derive::Msg;
```

With those in place, the smallest useful program is the counter from the README's #[widget] section. A Msg enum with Decrement, Increment and Quit, a Model holding counter: u32, and a view! block containing a Window, a vertical Box, a "+" Button sending Msg::Increment, a Label bound to the counter, and a "-" Button sending Msg::Decrement. The window's delete_event maps to (Msg::Quit, Inhibit(false)).

Finally, main() starts the loop. The README's non-attribute example ends with:

```rust
fn main() {
    Win::run(()).unwrap();
}
```

Win::run takes the ModelParam, which is () in both examples. If the program compiles and a window with two buttons appears, the GTK+ installation and the crate versions agree. If it does not, the mismatch is almost always between the GTK+ version on the system and the gtk crate version in Cargo.toml.

## The #[widget] attribute trades speed for brevity

The README is unusually direct about this. It warns that your program might be slower when using the attribute, because the code generation is simple. Its example is a loop that increments self.model.counter one hundred times inside update(). The attribute expands that into a generated update function that performs the property set on every iteration, rather than once at the end. The README truncates the generated function in the excerpt, but the point is stated plainly: the generated code is naive.

That matters when update() runs in a loop over many items, or when a single message mutates a model field many times. The library gives you an escape hatch, since you can still provide the method and associated types yourself, but the README says you cannot create the struct when the attribute is in use. So the choice is not per-field; it is between writing the Update and Widget impls by hand, as in the first half of the README, or accepting the generated versions.

A second constraint from the same section is visibility. The #[widget] attribute makes the generated struct public, which forces the corresponding model and message types to be public too. In a library crate that is a real API surface you did not intend to expose.

## Where relm is the wrong tool

If you are not already committed to GTK+, relm is the wrong first choice. The README makes GTK+ a prerequisite rather than a backend, and the crate depends on gtk 0.18.2, gio 0.18.4, glib 0.18.5, cairo-rs 0.18.5 and gobject-sys 0.18.0. That is a large native dependency tree, and it is the tree you ship. A toolkit that draws its own widgets avoids it entirely.

The beta warning is the second boundary. An API that may change at any time is workable for an application you control end to end, but it is a poor foundation for a plugin interface or anything you expect third parties to compile against. The README does not document a deprecation policy or a migration path between versions.

There is also a naming trap. relm and Relm4 are different projects, and search results mix them freely. The Relm4 book that people search for is not this repository. If you arrive here looking for Relm4's material, check the repository name before reading further, because the APIs are not interchangeable.

## How relm differs from Relm4 and from non-GTK toolkits

Relm4 is the obvious alternative to name, and the difference is not cosmetic. Relm4 is a separate project with its own book and its own component model; relm is the original, and the README's own framing is the Elm-inspired Update and Widget pair with the #[widget] attribute generating the glue. If you have read Relm4 documentation, the trait names and the attribute behaviour you remember do not all transfer.

The other comparison is against toolkits that do not wrap GTK+ at all. Projects in the Rust GUI space that render their own widgets, or that target WebAssembly, make a different trade: they drop the system GTK+ requirement and the C library dependency chain, and in exchange they do not inherit GTK+ theming, accessibility or the widget set that comes with it. relm's position is the opposite one. It is a thin, opinionated state layer on top of an existing toolkit, and its value is exactly that it does not try to replace the toolkit.

That also bounds the size of the win. relm is not a rendering engine, a layout system or a styling language. It is roughly a few hundred lines of convention around signals and properties. If the Elm pattern is not something you want, there is very little else here.

## Licence, maintenance and what a version bump costs

relm is MIT licensed, per the Cargo.toml license field and the LICENSE file at the repository root. MIT is permissive: you can use it in closed-source software, and the main obligation is keeping the copyright notice and permission text with distributions. That is a summary of the licence text, not legal advice; read LICENSE for the terms that apply to you.

The repository is not archived, and the last push was on 2026-07-23. There are no retrieved releases, so version history is not something this article can describe. The crate version in Cargo.toml is 0.25.0, and the README's dependency block pins relm and relm-derive at ^0.25.0, so the two agree on the minor version even though the gtk line does not.

Upgrade cost is dominated by the GTK+ bindings, not by relm. A bump of the gtk crate pulls gio, glib, cairo-rs and the gobject-sys bindings with it, and the README's Usage block still shows gtk 0.16.0 against a repository at 0.18.2. Because the library is beta, a relm version bump can also change the traits you implement. Budget for reading the generated code when you upgrade, not just the changelog.

## Conclusion

Use relm if you already build GTK+ applications in Rust and want an Elm-style message loop with less boilerplate; skip it if you need a stable API or a non-GTK toolkit. Before adopting, install GTK+ on your system, add gtk 0.16.0, relm 0.25.0 and relm-derive 0.25.0 to Cargo.toml, check the crates.io version against the README, and read the #[widget] warning about generated code being slower.

## FAQ

### What is antoyo/relm?

It is an asynchronous, GTK+-based GUI library for Rust, inspired by Elm, and the README places it in beta stage with an API that may change at any time. Applications organise state in a Model, send events as a Msg enum, and handle them in a single update function.

### How do I install relm?

There is no relm-specific installer: install GTK+ on your system first, following the GTK+ installation documentation the README links to. Then add gtk, relm and relm-derive to your Cargo.toml at the versions the README lists.

### Is relm the same as Relm4?

No. relm is the original Elm-inspired library described in this README; Relm4 is a separate project with its own book and component model. The README does not describe Relm4, and the two APIs are not interchangeable.

## Sources

- [antoyo/relm on GitHub](https://github.com/antoyo/relm)
- [Issues](https://github.com/antoyo/relm/issues)
- [License: MIT](https://github.com/antoyo/relm/blob/master/LICENSE)
- [README](https://github.com/antoyo/relm/blob/master/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/antoyo-relm
