Hacker News new | ask | show | jobs
by lucideer 2612 days ago
Question (for any more knowledgable readers here) from someone with a somewhat shallow understanding of the topics discussed:

Does this end up being primarily a negative reflection on the general structure of:

(a) Rust

(b) wl-roots

(c) Wayland

(d) all three

(e) none of the above, it's merely the incidental reality of trying to write code that's compatible/usable across multiple language ecosystems and none of the 3 projects can do much to improve this situation.

4 comments

> Does this end up being primarily a negative reflection on the general structure of

A core problem in this case is wlroots using a memory management paradigm that isn't easily modeled in Rust. This isn't unexpected per se, since C leaves MM entirely to the developer, while Rust is opinionated.

I would say it's a bit more subtle than that. Rust can express this, but not safely. This is what the end bit is about.

The question then becomes, is it worth it if it's largely unsafe? That's a complex question. Unsafe Rust still does give you a lot of advantages, namely that the checked constructs are still safe, even in an unsafe block. Unsafe Rust is slightly more annoying to write than safe Rust, and so that's a downside.

It's also possible, and again, this is more in theory since I know nothing about wayland internals, that the safe abstraction was chosen to be a bit too low-level. That is, rather than trying to make the primitive operations safer, designing an external API you'd want users to use, rather than one defined in terms of some of the primitives, may make sense. This has a lot of pros and cons, as you'd imagine. And that's also more work to do.

Author here.

Ignoring the social impetus in the Rust community to not use unsafe, I also don't feel like unsafe Rust is something I want to program in all the time.

When I program in safe Rust I can be happy once it compiles because I can ignore all of the safety problems that come from C and C++.

However in unsafe Rust not only is it much more difficult to express what I want syntatically (the lack of auto deref is very annoying, having to write (*base).value all the time gets very old) and semantically (there is no standard for the unsafe parts of the language - not so much a problem if only smallish parts of this usage is used (because once a standard comes out just that can be updated) but a problem if a whole program is written in it).

Unsafe Rust is "good enough" to try to encode these abstractions but I would not use it over C or C++.

Speaking as someone who dabbled in Wayland in Rust before (but a few years ago so my knowledge might be outdated), has not tried wl-roots, and uses Rust a lot

(a) Only when talking about interacting with C libraries written in a certain style. So not in general, and not with most C libraries, but yes for implementing wayland compositors.

(b) Don't know

(c) Absolutely a negative reflection, there are two ugly things going on here. The official wayland libraries use a ownership style that really only makes sense in C imho, and despite the claim that wayland is just a protocol you can't actually not use the official libraries, because the drivers only work with the official libraries.

>(c) Absolutely a negative reflection, there are two ugly things going on here. The official wayland libraries use a ownership style that really only makes sense in C imho, and despite the claim that wayland is just a protocol you can't actually not use the official libraries, because the drivers only work with the official libraries.

Not really sure what you're talking about here. Wayland is just a protocol, and a pretty simple one to boot. There also exists a pure Rust implementation of the Wayland protocol:

https://github.com/Smithay/smithay

The problem is that Rust and libwayland, the C implementation of Wayland, don't get along well, and that wlroots is designed to work with libwayland and inherits a lot of those design decisions that make it difficult to deal with in Rust. And to be honest, Rust is special in this regard. Other wlroots bindings exist for Go, Haskell, Common Lisp, OCaml, and Chicken Scheme - and all seem to do fine.

I think this points more to a failing in Rust, in that it's not designed to cope with this particular model well. Since this is a fairly common model, that seems like a big flaw.

> Wayland is just a protocol, and a pretty simple one to boot.

For a Wayland client to render using hardware acceleration, it has to link to an OpenGL library (typically mesa, specifically EGL to set up the OpenGL context). Mesa (a library written in C) links to libwayland-client (also written in C). The problem is that mesa is expecting a pointer to a C struct that is defined in libwayland-client. It will cast that pointer and call libwayland-client functions.

Mesa could have been written to use a different level of abstraction (e.g. take a struct of function pointers) it would be possible to wire it up with your own wayland library.

It doesn't help that libwayland-client was designed to be stateful (since the protocol itself it stateful).

So, while technically you can write a wayland client without using libwayland-client, you cannot create an OpenGL context without rewriting mesa. You also cannot link directly or indirectly with ANY libraries that use libwayland-client.

So people give in, use C, and move their abstraction layers higher up in the stack.

Edit: grammar.

Thank you for spelling out the details, I knew the issue existed but forgot the details.
At a glance, smithay depends on wayland-server depends on the official wayland libraries. More specifically the issue that makes this necessary is that it's the only way to get an OpenGL context (AIUI - it's been quite awhile since I worked on this).

Rust is special in that's it's even worse at representing it, but the C controlled event-loop/fd-based-dispatching/ownership model wayland uses isn't idiomatic in any language other than C. Maybe wlroots makes this better, like I said I've never used it and can't speak to it.

Smithay has both a pure Rust and libwayland-based implementation, you can pick iiuc.

And the OpenGL problem isn't fair, OpenGL is such a flaming heap of poor design that binding to libwayland to use it is the least of your problems. Better to use Vulkan instead for this purpose imo.

Event loops are common in many languages.

> Event loops are common in many languages.

Including rust - I'd have to dig in again to remember exactly why the wayland one was so bad at interacting with rust to be honest. I know it had something to do with how ownership of callbacks and objects interacted with the event loop. I know I didn't think I could represent it much better in python.

OpenGL may be a shit show, but it's necessary (to be able to expose it to clients) for a modern desktop. I think I was doing this either before or in the really early days of Vulkan so I don't really know how that changed things.

Rust can handle event loops just fine; our entire asynchronous IO story is based on them!
I read it as friction between how Rust sees its application model and the real world. Total ownership of a resource can't be maintained if some jackass goes and pulls the physical hardware out from under you. So now the Rust implementation gets crufted up with a zillion checks for unexpected changes to the underlying system that are clunky in Rust because that's supposed to be an exotic condition, not something you're doing on practically every line.
This can also indicate a problem in architecture: if the monitor can go away any time, you shouldn't store long time references to it and instead, for example, call a function to get current default monitor every time you want to do something. Or at least validate those references before usage.
I suspect querying the monitor for its parameters every time you want to blit pixels to it is wildly impractical. EDID runs over a slow serial link, and you have 124.4 million pixels to blit out every second.

The sensible way to handle this is for the system to throw an interrupt when the state of the connection changes, but interrupt handling is something Rust's ownership model struggles to accommodate.

It reflects on the suitability of Rust as a systems programming language.