Comment by beeflet

7 hours ago

I agree with the sentiment somewhat. Some rust libraries are dying, while some great new ones thrive (recently found iroh and wgpu to name a few). Everyone wants to write a game engine or some fun project and then abandon it, but no one wants to write a game. No application software has really "cemented" itself in the global ecosystem. Except for maybe ripgrep?

I would like to see support for more compilers (https://rust-gcc.github.io/), more interoperability with C/C++, better support for cross-compilation. Maybe less reliance on crates.io, static linking, and permissive licenses.

Still, I see Rust as the natural progression from C++. It has enough momentum to flatten all competitors (Carbon, Zig, Nim, Go) except scripting languages

> Still, I see Rust as the natural progression from C++

I don't; Rust has its niche but currently can't replace C++ everywhere.

From what I'm aware of, Rust has poor ergonomics for programs that have non-hierarchical ownership model (ie. not representable by trees), for example retained mode GUIs, game engines, intrusive lists in general, non-owning pointers of subobjects part of the same forever-lived singleton, etc.

> Go

To displace Go you must also displace Kubernetes and its ecosystem (unlikely, k8s is such a convenient tool), or have k8s move away from Go (not gonna happen considering who developed both)

  • > intrusive lists in general

    Not quite an intrusive list, but a particular data structure that's often called "intrusive" is a map where keys borrow from values. This turns out to be a super useful pattern.

    https://www.boost.org/doc/libs/1_59_0/doc/html/boost/intrusi...

    Note that the signature here is `const key_type & operator()(const value_type &)`, or in Rust terms `fn key(&self) -> &Self::Key`. This means that the key must exist in a concrete form inside the value -- it is impossible to have keys that borrow from multiple fields within the value, for example.

    At Oxide, I recently wrote and released https://docs.rs/iddqd, which provides similar maps. With iddqd, the signature is `fn key(&self) -> Self::Key<'_>`, which means that iddqd does allow you to borrow from multiple fields to form the key. See the ArtifactKey example under https://docs.rs/iddqd/latest/iddqd/#examples.

  • Things that live forever can be immutably borrowed, no problem.

    So rather than a non-owning pointer, you'd just use a &'static here - the immutable borrow which (potentially) lives forever

    Years ago it was tricky to write the "forever, once initialized" type, you'd need a third party crate to do it stably. But today you can just use std::sync::LazyLock which lets you say OK, the first time somebody wants this thing we'll make it, and subsequently it just exists forever.

    [If you need to specify some runtime parameters later, but still only initialize once and re-use you want OnceLock not LazyLock, the OnceLock is allowed to say there isn't a value yet]

    Intrusive lists are one of those things where technically you might need them, but so often they're just because a C or C++ programmer knew how to write one. They're like the snorkel on the 4x4 I see parked in my street. I'd be surprised if they've ever driven it on a muddy field, much less anywhere the snorkel would help.

    A retained mode GUI looks like a hierarchy to me, how do you say it isn't?

    • > A retained mode GUI looks like a hierarchy to me, how do you say it isn't?

      The problem with retained mode GUIs is that, while the widget tree is, well, a a nice hierarchical tree, the widgets and the data model often need mutable references to each other, and it's these cross-references that break the tree-based ownership model of Rust.

    • > But today you can just use std::sync::LazyLock which lets you say OK, the first time somebody wants this thing we'll make it, and subsequently it just exists forever.

      This corresponds to a C++ feature that has been added in C++11 (and other languages have this too AFAIK); good for libraries but but this has a runtime performance cost.

      Since C++20 you can use constinit, or std::construct_at to avoid static initialization woes entirely

      > Intrusive lists are one of those things where technically you might need them, but so often they're just because a C or C++ programmer knew how to write one.

      True about C, and also true of most userspace applications.

      However, the whole point of intrusive list (and other intrusive structures) is to flip the ownership relationship - the object owns the node, instead of the node owning the object. In other words, the intrusive list is a non-owning view over its elements. More importantly, they allow the same object to be present in multiple lists at once (as long as you add enough node hooks).

      For example, you can have a pool-allocated Thread object that is simultaneously part of a queue of inactive threads of priority N, and also part of a wait queue for something else.

      > A retained mode GUI looks like a hierarchy to me, how do you say it isn't?

      Right, I conflated it with the other examples, my bad; there it's because of shared mutable state, children elements needing to update their parents, and lack of inheritance in Rust

  • > From what I'm aware of, Rust has poor ergonomics for programs that have non-hierarchical ownership model (ie. not representable by trees)

    Yeah, non-hierarchical references don't really lend themselves to static safety enforcement, so the question is what kind of run-time support the language has for non-hierarchical references. But here Rust has a disadvantage in that its moves are (necessarily) trivial and destructive.

    For example, the scpptool-enforced memory-safe subset of C++ has non-owning smart pointers that safely support non-hierarchical (and even cyclical) referencing.

    They work by wrapping the target object's type in a transparent wrapper that adds a destructor that informs any targeting smart pointers that the object is about to become invalid (or, optionally, any other action that can ensure memory safety). (You can avoid needing to wrap the target object's type by using a "proxy" object.)

    Since they're non-owning, these smart pointers don't impose any restrictions on when/where/how they, or their target objects, are allocated, and can be used more-or-less as drop-in replacements for raw pointers.

    Unfortunately, this technique can't be duplicated in Rust. One reason being that in Rust, if an object is moved, its original memory location becomes invalid without any destructor/drop function being called. So there's no opportunity to inform any targeting (smart) pointers of the invalidation. So, as you noted, the options in Rust are less optimal. (Not just "ergonomically", but in terms of performance, memory efficiency, and/or correctness checking.) And they're intrusive. They require that the target objects be allocated in certain ways.

    Rust's policy of moves being (necessarily) trivial and destructive has some advantages, but it is not required (or arguably even helpful) for achieving "minimal-overhead" memory safety. And it comes with this significant cost in terms of non-hierarchical references.

    So it seems to me that, at least in theory, an enforced memory-safe subset of C++, that does not add any requirements regarding moves being trivial or destructive, would be a more natural progression from traditional C++.

I find a lot of Rust libraries "seem" dead, based on github activity, but looking into it, they are actively used in many projects. I think Rust projects just tend to have less open issues, and don't need to be maintained as often. This is also the case internally at my company.

Rust will not "flatten" Go. They have some overlap but generally don't serve the same purpose and don't appeal to the same crowd. Go's popularity is undeniable, both in open source and in the industry. Outside of major shops and the odd shady Fintech startup, there are still very little Rust jobs out there. It's not necessarily bad, it's just the nature of it. Rust adoption is a slow thing because it addresses problems faced by slow moving software.

  • Go jobs are not that popular either especially outside US. Java is way more popular and with recent improvements on JDK Go does not offer that match advantages over it.

    And compared with Go Java is fully memory-safe and have independent implementations.

    • I like modern Java and agree it's a much better language than Go and an excellent platform in general. But Java will not beat Go where small and fast native executables are required. The native story for Java will remain a sad one, builds being hyper slow (aot) or resulting in huge binaries (jlink).