Hacker News new | past | comments | ask | show | jobs | submit login
Rust and C++ Interoperability (slint-ui.com)
235 points by ogoffart 79 days ago | hide | past | favorite | 95 comments

Great article. Answered a lot of questions I've had as I toy with the idea of making part of a C++ project in Rust.

Regarding Slint and all the other new GUI toolkits, one thing I don't understand is why they all seem to completely ignore any controls more complicated than the basics (buttons, checkboxes, drop downs, spin controls, sliders, radio buttons, and a basic list control).

A To-Do app is a proof of concept, not an endgame.

Beyond those basics I need combo boxes, date time pickers, color pickers, font pickers (fonts are hard, I get it, but my users expect access to system fonts), dockable windows, splits, tabs, modal dialogs, tooltips, masked text input, auto completion text input, context menus, calendar controls, file and directory pickers.

Some of the new toolkits offer an assortment of these, including Slint but they rarely get all of them but the biggest thing missing from pretty much all of them are sophisticated data grids (both list and tree varieties). They need support for custom cell rendering, custom cell edit controls, and variable row heights. They also need to be fast even with tens of thousands of rows.

Oh, and think about your printing story. You don't print a UI obviously but if I'm rendering diagrams and stuff using your rendering facilities I at least need a way to hook them up to the platform's system without just duplicating all of the drawing to a completely different rendering API to make printing work.

This isn't a dig on Slint. I'm eagerly watching it, along with egui, as I desperately look for a cross platform alternative to all the legacy stuff I'm neck deep in. I just can't consider anything that has no good support for data dense applications.

It may be worth looking at qmetaobject[0] which lets you use Qt/QML from Rust. Whatever you can do in QML you can do with qmetaobject, and writing new Qt stuff can be done with some annotated Rust code. I'm using it in a personal project and quite enjoying it, the lack of C++ tooling is a joy not to be understated. I believe it's from the same author as Slint.

[0]: https://crates.io/crates/qmetaobject

I think one of the issues with going beyond the basics is that once you start to tackle more advanced controls you not only have to be more opinionated, as there’s many ways to achieve these things, but also they become for more specific use cases and thus creating a general solution has exponential complexity.

What people want from a UI library is to be able to use system components. For example, there should be a way to open the system color picker, to display a system data grid, system date picker, etc. It doesn’t need to be more than that.

These blessed "system components" do not exist any more. Windows has two or three different ways of doing widgets and so does MacOS while the Unix land never had one.

You can still use the old ones, but they will look old and may not behave like you would expect (hidpi displays, touch screen, etc).

wxWidgets and its bindings to many languages exists if that is what you want.

Writing an uniform interface to GUI widgets on many platforms is very much a non-trivial undertaking, there are plenty of failed attempts over the years.

The look & feel of system components on Windows has actively regressed starting from Win 8 and subsequent releases. No idea about the situation on Mac OS. But AIUI, both Qt and GTK+ will give you a good enough UI in practice. Perhaps someone will even split out the SerenityOS widgets (which are truly best-in-class, meshing the advantages of "classic" and "modern" UX) as a multi-platform framework, much like has been done wrt. the browser engine.

> AIUI, both Qt and GTK+ will give you a good enough UI in practice

Not GTK+, it sticks out like a sore thumb. Qt gets much closer, though in both cases you can tell something feels off.

(e.g., click an active tab in a tab control in Qt and you won't see the dotted selection line like you would in Win32. That normally indicates you can switch between tabs on the keyboard.)

SerenityOS is such an amazing project. "Oh, this thing would take a large team several years to build? Let's build it anyway!" The backstory behind the project is extremely admirable, too.

> Perhaps someone will even split out the SerenityOS widgets as a multi-platform framework

That would look out of place on MacOS or Windows. Serenity has a Win 2000 look.

MacOS still has standard components at least. For Windows you can pick the best of several options and stick with that.

For what it's worth, wxWidgets does have hidpi support (at least on Windows).

Don't forget about Microsoft COM components. That technology was specifically created as a system to produce "reusable components" with. What happened to that?

There are definitely people who want more than that. Some controls I am always looking for in a UI library before I even think about using it is a more advanced list control (or table view) which supports columns, sorting etc., and a "tree view". Of course, as the GP wrote, it's hard to implement these without getting more opinionated. Plus, I might add, it's also hard to do these while staying "mobile-friendly".

Qt has solved this problem in a pretty neat way

If you have combo boxes then you already have a font picker. Do you mean you want a built in way to enumerate installed fonts?

edit: I realized that you want the menu entries to be rendered in the fonts themselves

Any combo box with custom draw functionality will provide that.

TLDR use a robust c++ toolkit like QT and spend more time on what matters.

"The situation on the C++ side isn't too different: The ABI is compiler defined. This is why you can not mix libraries generated with MSVC and GCC."

There is a multi-vendor C++ ABI, honored by clang, GCC, Intel's proprietary compiler, and others. It doesn't solve all problems, but provides interoperability.

I'd say this is misleading, or only technically correct (the best kind of correct). Calling conventions are only the first step. For interop to be useful in practice -- to be able pass more interesting C++ types than integers -- you also need your standard library types to be (and remain) compatible[1]. This currently isn't guaranteed even across versions of the same compiler.

[1] Herb Sutter's C++ ABI effort was supposed to solve exactly this, but unfortunately never went anywhere. The proposal document pinpoints the problem very well in my opinion: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n40...

Because the C++ ABI is fragile, in practice you need a blessed library ABI for the standard library. On linux for example it is libstdc++ which has been stable for almost 20 years (yes, there was a brakeage around c++11, but there are compatible shims for backward compat). Clang and the intel compiler can compile against libstdc++.

No, this isn't because the C++ ABI is fragile, but because the standard library wasn't part of its scope. It only covers the layout of objects and how parameters are passed to function calls, and does not cover the implementation details of standard classes. There are competing, incompatible standard libraries.

well, the ABI doc says:

    We have not attempted to constrain the interface [of the standard library] at this level, because we do not consider doing so feasible at this time
So it wasn't necessarily out of scope, it was just hard to do.

I think the reason they don't consider it feasible, is because the C++ ABI would constrain the implementation significantly and there was too much divergence already.

Do you have exmoles to support claim that there is no compatibility between versions of the same compiler? your linked pdf is from 2014, exact time when compilers stopped breaking things.

AFAIK MSVC compilers explicitly guarantee ABI compatibility since 2015 https://learn.microsoft.com/en-us/cpp/porting/binary-compat-...

as for other platforms, all compilers adhere to Itanium ABI and didnt introduce breaking changes for many years now. Standard libraries, libc++ has explicit ABI guarantee as well (https://libcxx.llvm.org/DesignDocs/ABIVersioning.html).

Libstdc++ uses symbol versioning and only nasty thing happened to it was problem with std::string CoW decade ago, but even that was done neatly and in compatible manner https://gcc.gnu.org/onlinedocs/libstdc++/manual/using_dual_a...

MSVC guarantee ABI compatibility between different versions of MSVC, not with the inter-compiler ABI mentioned by parent comment.

But the parent comment's point is that, even for those different compilers that do follow that inter-compiler ABI, the standard libraries have different binary layouts from other compilers. Your final two links show that GCC and LLVM standard libraries each have the same binary layout as itself between different versions, but not as the other.

I never got why ABI incompatibility is such a big problem. Why does every non-Windows compiler insist on bringing a non-Windows ABI to Windows? Why can't people just respect whatever the convention on each platform is and move on? It's not like you need ABIs to be cross-compatible across platforms.

I suppose the key reason is ABI is not defined by the language standard? Nor is it is something (AFAIK) that would be standardized on Windows. This is not the same thing as binary compatibility on windows but rather about the internals of virtual function tables and so on.

There are other problems as well from the "hands off" quality of the standard.

Endemic of how bad the situation is regarding C++ DLL:s is that you can't free memory in a different C++ DLL than where it was allocated. Well, you _can_ but most of the time you shouldn't.

> I suppose the key reason is ABI is not defined by the language standard? Nor is it is something (AFAIK) that would be standardized on Windows. This is not the same thing as binary compatibility on windows but rather about the internals of virtual function tables and so on.

Not really. Windows already has specific ABIs that it follows (in NTDLL, MSVCRT, COM DLLs), etc., and that includes virtual functions and exception tables and unwinding and all that. MSVC follows them fine. There's no reason other compilers shouldn't be able to. They just don't seem to want to.

> There are other problems as well from the "hands off" quality of the standard.

Sure, but none of them imply "it's better to defy the convention by default". You still lessen the friction by following the convention.

> Endemic of how bad the situation is regarding C++ DLL:s is that you can't free memory in a different C++ DLL than where it was allocated. Well, you _can_ but most of the time you shouldn't.

That's an orthogonal problem, it's not like you're somehow making that problem any easier by switching to a different ABI than what you know is already conventional on the platform.

Other Windows native compilers do, like C++ Builder.

It is really only the UNIX folks refusing to adopt the platform idioms.

are they ABIs free or are they patented? I seriously don't know but that would be a sound reason.

That would be a sound reason for the things that were patented, but even the non-patented stuff didn't enjoy compatibility.

w.r.t. patents, the only thing I've heard of having been patented is SEH (by Borland, presumably after seeing GCC wasn't implementing the then-patent-less SEH), and even that I'm not sure about the relevance of. (I saw it in the context of WINE, which has to implement the OS-side of it, not the compiler side.)

IIRC there were patents around structured exceptions which made it hard to implement the WIN32 ABI. Not sure about the 64bit ABI.

> I suppose the key reason is ABI is not defined by the language standard?

It is hard to define ABI in the language standard, since ABI heavily depends on the architecture.

(The ABI specify, for example, the calling conventions and in what register goes what, which depends on what kind of register the CPU has)

It took Microsoft a pretty long time to settle on one C++ ABI. Until then, every major MSVC version had its own, incompatible C++ ABI - no "Windows ABI".

Are you sure? What are you calling "a pretty long time" here? When you say "until then" - when is "then"? What exactly was unsettled at that point that supposedly caused a divergence?

I would understand if *nix folks at least tried to adapt to the platform and were then forced to diverge due to a lack of convention for some new feature, but that isn't the case at all. The compilers follow *nix rules for even the most basic name mangling. They don't seem to have made any initial attempt at platform C++ ABI compatibility whatsoever; they just carried the *nix way into Windows.

The ABI is only compatible between versions since MSVC 2015: https://learn.microsoft.com/en-us/cpp/porting/binary-compat-...

Also I think the debug and release build are still incompatible.

That date is pretty misleading unfortunately. And note "it happened recently" and "it took a pretty long time" are different claims.

IIRC the aspects of the ABI that "broke" across (say) 2013 and 2015 were generally (if not entirely) for the support of newer features, like thread-safe static initialization. That stuff was only standardized in C++11, which is 4 years, not something I'd call a "long time" (especially considering how long it took everyone to finish implementing C++11).

But even worse than that, it's not like GCC had been compatible with the basics before this. It couldn't even bring itself to mangle "void foo() { }" in a platform-compatible way, and it's not like that took until 2015 for Microsoft to settle on.

I was working an a library at the time, that was using purely pre-C++11 features, and I remember we had to make a build of the library for each MSVC versions we supported at the time. Because they were not compatible, even for basic stuff.

Quote from the link I pasted in the grand-parent comment:

> The Microsoft C++ (MSVC) compiler toolsets in Visual Studio 2013 and earlier don't guarantee binary compatibility across major versions. You can't link object files, static libraries, dynamic libraries, and executables built by different versions of these toolsets. The ABIs, object formats, and runtime libraries are incompatible.

I don't know what you're calling "basic stuff" but it doesn't matter. I'm pretty sure "void foo()" has mangled to ?foo@@YAXXZ for at least two decades, and like I said, I'm pretty sure *nix tools never even attempted to be compatible with that. So there's no excuse here.

maybe calling a void function with no arguments did not change, but other things changed in various ways making it not stable.

What would be the point for the GCC team to even try to be compatible with a changing and non-documented ABI?

It wouldn't buy anything to just support how to mangle basic function, as they couldn't still be called. You either had to be fully compatible, or aren't.

And an ABI can be fairly complicated to implement. For example, in what register do we pass a class passed by value? It can change depending on whether some of its members are float, int or double, or whether it has copy constructor. You get anything wrong and the user suddenly get weird crashes that are going to be extremely hard to debug.

Yes it was recent. I looked it up - it's been stable since Visual Studio 2015.

LLVM/Clang does mostly support the MSVC C++ ABI today - https://clang.llvm.org/docs/MSVCCompatibility.html . I've not used it for anything major but in my limited experience it does work. Native PDB generation also makes debug much more pleasant with tools like WinDbg.

Kind of, but that's kind of my point. It's a relatively recent development, and like you also hint, it's still incomplete. (e.g., IIRC /DEBUG:FASTLINK doesn't work, and I think SEH doesn't have full support either... last time I tried using Clang/LLVM I ran into lack of support for some features I wanted and had to revert to MSVC.) For another, Clang is the exception (no pun intended) rather than the norm among these *nix tools. After refusing to adapt like the rest, it finally caved in. Most *nix folks still don't seem to accept that the sensible thing is to follow the platform (case in point being the comment I replied to). And lastly... it's not even always the default behavior for Clang. The versions you get bundled with (say) MinGW still refuse to follow the platform rules, due to the eagerness to cater to GNU. It's only the MSVC-flavored ones that have any respect for the platform they're trying to support. Then people keep bashing (again no pun intended) Windows for not following UNIX conventions...

I believe SEH (possibly for x86?) used a patented algorithm (Wikipedia says it expired in 2014), so it wasn't possible to actually do anything about it earlier than that.

I don't believe I was complaining about lack of SEH support pre-2014 anywhere. I just said I've run into issues around it with Clang today. Also note that SEH was never the sticking point for ABI compatibility. Even the mangling of "void foo()" wasn't something *nix compilers attempted compatibility with.

IIRC the Windows 64bit exception unwind table model was also different enough (and not well documented) from DWARF that it took a long time to implement it (I'm not sure if it is fully working yet).

Yeah, the usual any OS is good, as long as it is an UNIX clone.

I suspect part of the issue was that until Visual Studio 2015 MSVC did not have a stable C++ ABI [0].

[0] - https://learn.microsoft.com/en-us/cpp/porting/binary-compat-...

It's a long tradition of Windows to have some weird ABI. Even before C++ Windows API used "pascal calling convention". And then MS probably intentionally made it difficult to follow their C++ ABI to shun away all other compilers from their platform. They did a lot of this at the time.

Borland, Intel, Symantec and Watcom managed just fine.

> Why does every non-Windows compiler insist on bringing a non-Windows ABI to Windows?

Isn't the point of a using a different compiler to let it compile your code in different ways? Having to meet an arbitrary ABI is a limiting restriction you may not want.

That's when you add a flag (or annotation/attribute/whatever) to let users switch to an unconventional ABI when they really want to. That doesn't mean your default choice has to be something that deliberately defies the convention and makes life harder for everyone by default.

The main problem is it came about 20 years too late. Anybody writing libraries in C++ today either use C linkage or ship multiple versions.

Third option, we use COM.

COM boils down to a C ABI, aiui.

Not really, that is what people think, while forgetting that it comes along with a type system, has two models of method invocations.

And with the introduction of WinRT extensions, it got even richers.

Naturally one can try to call COM via C, and there are some COM APIs that offer C versions of them, however the tooling is already bad enough for .NET and C++ devs, doing it from C only for masochits or people that are religiouly against productive tooling.

I saw an assembler tutorial for coding against COM and it actually looked easier than in C. (There was no faffing about with types...)

I wish I could find that blog or whatever it was again.

Well, if you are fealing lucky that is a possibility as well,

This book has a Windows game, with DirectX, written in Assembly,


IIRC COM relies on vftables being ABI stable so you can pass interface pointers. As long as the interfaces don't pass any non-primitives by value.

It is how I often provide APIs that need to work across (many) compiler boundaries. It is not standardized or anything, but de facto reliable on Windows.

Does anyone have experience using Slint do develop GUI in Rust?

GUI in Rust is a hot topic, and to me it seems SlintUI may be the best general-purpose so far. The reason is rapid iteration and a flexible/intuitive layout system: Slint lets you write UI in a DSL with live preview and style/layout influenced by HTML and CSS, whereas competitors (egui, druid, iced) require you to write the GUI in Rust (which means you must rebuild and rerun to see changes) and have their own style/layout rules. Other competitors are GTK and Qt, but those have their own flaws and moreover, were designed way before Rust and seem to be less "idiomatic".

We are developing UI in slint-rs for our product. Our eventiual target is IoT devices (cyber defense) though we are starting with desktops. We are a young startup and our experience is very limited in UI space so please take all of it with a pinch of salt.

One of our intern got the UI working with Slint in a few days and since then we didn't feel a strong need to move away from it. We found a few missing features: it was not possible to tell if a Windows is hidden or not. We ended up sending a few patches.

So far our experience has been pretty good with slint-ui. We are not tested the UI on OSX and Linux yet, only windows. On AWS machines with Windows server, the UI fails to launch sometimes because of some missing GL libraries. Also note the licensing of Slint-UI https://github.com/slint-ui/slint/discussions/379.

Wow that linked request is really something. Requesting/Suggesting the change of license is one thing. But this is just another example of a blatant lack of respect towards open source devs and maintainers.

The misinformed GPL hatred displayed there is also ridiculous.

Could you name, specifically, the true things they do not know or the false things that they think they know? Or is 'misinformed' here simply a way of saying that you disagree, and have no such preference for people not requiring you to license your own code a certain way?

It's not about whether it is true or not.

Requesting a license change from a project that you are not a primary maintainer is more than a little rude.

However, continuing to request a license change even after the owners have explained why they chose the license is an astonishing level of entitlement.

If you don't like the license, don't use the project. That's the deal--take it or leave it.

"Misinformed" is, in fact, about whether something is true.

I use GTK3 with Rust for my video acquisition tool [1] and it's been quite pleasant so far. However, from my testing GTK3 & 4 don't play nice with OpenGL drawing areas, so the next project I'm writing (also in Rust) using Dear ImGUI.

[1] https://github.com/GreatAttractor/vidoxide

This blogpost should really mention the autocxx and crubit crates. There's a lot more to Rust/C++ interop than what they cover, and work on it is very much ongoing.

I think the link time optimization work is amazing as well. Calling out to different languages can really suck without whole program optimization, even if both the rust and the c++ sides are fully optimized, but taking the LLVM bit code of each and then running optimizations those bridges that gap.

I have used CXX in a project where I had to use a library available only in C++. There were Python bindings but somehow they were very slow. I could have rolled out my own pybind11 but wanted some of the Rust guarantees.

It was probably more convoluted than necessary since I also then exposed the Rust lib to Python, so C++ <=> Rust <=> Python , but it was indeed fun to implement it all.


There's also a more blasphemy-ish approach of interop between C++ and Rust if the C++ code already have good Python bindings: C++ <=> Python <=> Rust. It's not bad as you may think. My company uses it to adapt C++ to Rust without rewriting the 40K LOC Pybind11 boilerplate. Going through the Python interpreter is definitely slower than a native call but perhaps <3x since we only rely on Python being a hosted + GC environment, plus it's much easier to express lifetime in Rust given that everything is managed on Python heap.

The carbon project for c++ defines some fixed ABI types in the standard library to enable things like this. I hope that c++ can learn from that - I tried to propose fixed ABI types in the standard a few years back but didn't get very far

This seems to miss `cppmm` (C++--)[1].

This crate is currently mainly aimed at helping people from the Rust group of the Academy Software Foundation (ASWF) to make wrappers for the visual effects ecosystem of libs.[2]

It is nevertheless very useful for all types of other cases.

[1] https://github.com/vfx-rs/cppmm

[2] The above just means effort is prioritized regarding the blockers these libs present due to the resp. C++ features they use.

I missed more: There are a lot of tools out there addressing special cases. Those typically address certain C++ sub-eco-systems that follow certain rules that make the mapping in some special cases better.

Most of these are either built on top of cxx or are similar in spirit. So I did not list all of them. The original presentation was only 30 min and there is only so much you can ask a person to read in a blog post:-)

I did try to make it clear that my list is in no way exhaustive.

Great, so now we have cxx, autocxx, crubit and cppmm. The Rust folks should convene an actual Working Group on this issue, there's clearly a lot of demand for a solution.

The problem is that the different crates typically address certain sub-eco-systems and produce better results for that one dialekt spoken there. It is hard to generalize all that.

I really hope one of these approaches gets bundled with the official (or gcc) toolchain one day. C++ is widespread enough that it should be supported directly like C and not require downloading yet another deptree from crates.io.

Great article. Answered a lot of questions I've had as I toy with the idea of making part of a C++ project in Rust.

Your comment is worded exactly the same way as this person's comment: https://news.ycombinator.com/item?id=33591111

Are you the same person? I'm just curious. It's a funny coincidence :)

No. May be.


Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact