I tried this and is awful. GTK and Rust have two very different ways of managing memory and it's painful. Lucky, gtk-rs already helps you with their own types prepared to be used with Rc but with your custom types you need to define all the garbage collector stuff your yourself which makes the code look very ugly (lots of clone, which aren't really clones).
At the end the result is good, but the code looks awful.
My experience is kind of similar. Unfortunately the way Rust interacts with memory is quite different from how Gtk does (and most GUI tool-kits to be fair). The end result is a kind-of awkward battle between the Rust borrow checker and the internal memory handling of Gtk+ leading to loads of Rc<RefCell<T>>. This makes also quite hard to implement custom Widgets in an intuitive way. In Python or C++ you would subclass the generic "Widget" class and start implementing the bits you need. With Rust things are slightly complicated; you can't just implement the IsA<Widget> trait and call it a day; at least not a straightforward way. Hopefully gnome-class [0] will probably solve this problem eventually.
That being said it's been a while since I've used gtk-rs so it is entirely possible things have changed. I'd love to be corrected if that's the case :)
Agreed, I went through the same pain when I ported by dummy Gtkmm from an old article into Gtk-rs, Rc<RefCell<T>> everywhere.
This is such a pain that even Gtk-rs samples have macros to work around cloning widget state into callbacks.
From my point of view language ergonomics still need to improve for Rust to actually be a candidate for GUI development, versus what the GC based languages (RC and tracing GC) offer out of the box with their UI kits.
Unless one envisions it just to take C++'s role of visualization layer and integration with graphics drivers.
One way to handle GUI componentry is to separate out the notions of ownership, parentage and reference.
Ownership defines who frees who. Parentage defines control nesting. References should to be handled via an indirection (e.g. names) and a protocol so that referees get notified when referents die. The indirection means that components get wired up in a late bound fashion; and the late binding means the gubbins for a protocol can be in place by then.
This stuff was figured out reasonably well with Delphi's VCL.
I guess the advantage here is that Delphi and C++ Builder don't have a borrow checker.
Even simple stuff like what Apple has demoed with SwiftUI designer can be a challenge, as you cannot just drag stuff around without changing the ownership semantics.
Naturally Swift has the advantage that Rc<RefCell<T>> is implicit, so it isn't as if the cost is magical gone.
However having to deal with it explicitly is a hard sell for anyone used to the comfort of GUI tooling.
So I imagine Rust is an easier sell for doing the low level stuff of a GUI engine, with what 90% of the devs using some managed language on top.
Basically what is happening to C++ on current desktop and mobile OSes, although for different reasons.
I tried event-driven programming with Rust 4 years ago and came to the same conclusion. It just isn't a good fit for the language. Rust works best when there is a stack and clear ownership semantics. Any kind of callbacks cause a certain amount of pain due to the need for unergonomic wrapper types.
Since then it got better libraries which reduce lots of the verbosity (e.g. gtk-rs), which is great. But the language itself probably won't change too much to better accommodate that use-case, since what things things painful here are the core features that prevent issues elsewhere.
I think we have to accept the fact that for different kinds of problems different programing languages are preferable instead of trying to find a single solution for all problems.
For UIs I think a naturally garbage-collected or refcounted language with reasonable support for dynamic dispatch is a lot easier to work with. In addition to that a restriction towards a single thread or even an inbuilt event loop can avoid bugs, improve interoperability between libraries, and reduce the amount of boilerplate and application-level design decisions even further.
If we take those properties we get the most common languages for GUI apps: Javascript/Dart which have single-threaded runtimes, and C#/Java/Kotlin/Swift as general-purpose languages which are more powerful but require an eventloop on application/library/framework level.
Those properties actually also expand to other very stateful and very concurrent applications. E.g. stateful network servers (something like Redis or a websocket server) have similar implementation challenges, and therefore are currently not very straightforward to write in Rust either (compared to e.g. in node.js).
In SWT, even though Java is garbage-collected, resources must be explicitly disposed[1]. Often they are bound to the lifetime of a parent object and disposed when that parent is disposed.
This seems to work OK in SWT, so what's the problem in GTK/Rust? Are there some particular categories of objects that don't fit into that hierarchical model and need to be handled with a GC?
(Those objects would presumably be regular Java objects in an SWT app and handled by Java's GC.)
The problem is called borrow checker, and that while the Rust team already has done lots of improvements with NLL, the use of widget data in callbacks remains a pain point.
So one could say, it is a case of holding it wrong, and a Rust UI toolkit needs to be written from scratch. Which might be ok, but it does require some support for market adoption.
Having said this, Gtk-rs now has some help in the form of relm, exactly to overcome this issue.
I have kept an eye on this for a long time. Is there any design pattern with Rust style (no unsafe and not ugly from Rust community's perspective) that proved in prototype to be able to achieve the same scaling of performance and memory usage as native C/C++ GUI framework with more complicated interface (e.g. Office)? Could this be an inherent limit of Rust because of the memory safety principle?
I was wondering something similar. This reminds me of the time a few months ago where someone gave up on writing a pure Rust binding to Wayland because the memory model in Wayland doesn't play well with the borrow checker[0]. It seems that the problem is you can write any arbitrary way of manually managing memory in C that doesn't necessarily have a complement in entirely safe Rust.
I'm not really sure this can be reconciled with any arbitrary C library. One of the benefits I see from Rust is the ability to be unable to do unsafe things without explicitly stating you know what you're doing, thus preventing various classes of errors. But it feels like if the ownership model just doesn't fit with Rust's model that allows those guarantees to be made in the first place, it won't work. You can't write just anything in safe Rust without limiting the things you can do, but C libraries don't have such limits.
It might take creating new ways of representing GUI objects before the best model for that problem arises for Rust. I don't think libraries like GTK were designed with the lessons in safe memory management Rust learned from years after the fact in mind.
I would like to see an ECS style UI design in Rust that represents widgets as IDs, to allow arbitrary object graphs without interior mutability. I don't see a reason why this would be incompatible with GTK, but we won't know until we try. It might work out really well.
It also might be that Rust is not the right language to write high level UI logic in at this time, and a GC'd language would be more appropriate. That's fine too. One of the strengths of Rust is its interoperability with other languages. C and C++ are certainly not the right languages to implement high level UI logic in, for example.
I agree with you. From my point of view, the advantage of C/C++ for UI is not about high level logic, but complicated custom widget with lots of low level functions and good performance. C/C++ is not good for a weather app, but almost the only choice for the canvas of Photoshop or the waveform of Audition, and it seems like no one else considers seriously challenging this. I am confident that given enough time, Rust will be adopted for social app or game GUI. But I don’t know if this would prevent Rust from writing practical large productivity software.
Yet, although Microsoft security now advises for C#, Rust and constrained C++, they went ahead and are using C++ for React Native and WinUI 3.0, removing the dependecy on .NET Native.
Apparently because that is the only way to sell MFC/ATL holdouts to WinUI, and allow WinUI to be called from classical Win32 without too much overhead, and also because .NET Native ideas will be migrated to .NET 5.
I am not saying that C++ is the answer for large-scale productivity software, but C++ is the answer for the complicated widgets used there. I would prefer Rust for the main framework of photoshop too, but I am not sure whether there is an elegant way writing the canvas widget.
Delphi and C++ Builder did well enough with high level UI logic.
C++'s problem is that the path of righteousness is too narrow, but with heavy cultural idioms and norms, you can keep a lot of the people safely hemmed in, while retaining all the escape hatches.
At the end the result is good, but the code looks awful.