1. 73
    1. 21

      This post is a good illustration of why I keep an eye on the evolution of Zig. Rust’s core values are performance, safety, and ergonomics. Simplicity is not a core value of Rust and it’s becoming an increasingly complex language. (That’s not to say that Rust doesn’t care about simplicity: only that when simplicity and another core value are in conflict, simplicity will always lose.) If I were still in my twenties, in might not bother me as much, but as I’m inching to 40, I find it hard to keep up with the evolution of the language. I worry that there will come a point where I’m locked out of the ecosystem, because I haven’t been able to keep up. I think that as Rust releases go by, my Rust style will become less and less “modern”. In a sense, Zig is a possible escape hatch.

      And it’s a shame, because I really like the first two core values of Rust, performance and safety. Zig has the same focus on performance as Rust, but since Rust’s memory safety often comes at the expense of simplicity, which is a Zig core value, I expect that Zig will not be able to match Rust’s guarantees against use-after-free, double-free, iterator invalidation, data races, etc.

      1. 12

        My concern with Rust is that not knowing it in 2029 will be like not knowing C or C++ today; it’s going to be ubiquitous in systems programming and a lack of knowledge will be a really career-limiting state.

        My other concern with Rust is that it just collapses under its weight, complexity, feature creep, and speed of change (and this is said by someone who is growing to really like Rust, at least in its synchronous form).

        1. 11

          My other concern with Rust is that it just collapses under its weight, complexity, feature creep, and speed of change

          C++ has enormous complexity, both intentional and accidental, yet it remains ubiquitous. Language complexity is sometimes justifiable.

          Besides, I’d much rather learn numerous obscure details of how Rust keeps me safe, instead of numerous obscure details of how C++ is screwing me over. And I say this as someone who genuinely likes C++.

        2. 5

          This is part of what’s pulling me to brush up on Ada again.

          Mostly because it is still used to build complex systems but it’d stayed relatively stable. Granted it’s user base is still pretty small but it’s there and enough to sustain a company to back some development of tooling.

        3. 3

          My concern with Rust is that not knowing it in 2029 will be like not knowing C or C++ today; it’s going to be ubiquitous in systems programming and a lack of knowledge will be a really career-limiting state.

          Good point. But can you imagine many devs knowing all this crazy syntax, I doubt!

          1. 7

            It’s no more crazy than C++, its competition.

      2. 9

        I share your feelings and have also been curious about Zig. I’m not excited about going back to the land of memory leaks, use-after-free, and so on, though… I’d be curious to hear about others’ experience here; maybe zig’s additional creature comforts mitigate those issues somewhat.

        1. 7

          I have been using Zig in earnest for about 8 months now. At that time my default choice for new projects was Rust, so I picked up the biggest Rust project I had (comrak, perhaps the 2nd most popular CommonMark/GFM formatter in the ecosystem) and converted it to Zig. It was quite refreshing.

          Since then I’ve built a number of tools with Zig, and I have honestly not had to deal with memory leaks, use-after-free, double-free, etc. almost ever. The general purpose allocator does what you would otherwise have to reach for Valgrind for, but better. The careful design of defer, errdefer and control flow around error handling make it quite a joy to use.

          If you already grok manual memory management as a concept (say, you’ve used C quite a bit before), then Zig feels like it gives you all the tools you need to do it sanely without much overhead, like what you always wanted. A lot of people are repelled by the idea of doing it ‘manually’, but my experience is that it is the poor affordances C gives that generates that repulsion. I do recommend it.

      3. 9

        Yes, people who want simplicity to win in conflict at least once, should look elsewhere than Rust. This has been the case even before Rust 1.0. It’s just not what Rust is for. Simplicity is the lowest priority item in Rust.

      4. 7

        And it’s a shame, because I really like the first two core values of Rust, performance and safety. Zig has the same focus on performance as Rust, but since Rust’s memory safety often comes at the expense of simplicity, which is a Zig core value, I expect that Zig will not be able to match Rust’s guarantees against use-after-free, double-free, iterator invalidation, data races, etc.

        …which is kind of the point where I fall of the bandwagon with Zig. Ok, so it’s simple, and in return for that simplicity it will merrily let you shoot entire limbs off in familiar ways that have had familiarly terrible consequences for users for as long as I’ve been alive.

        What’s the value proposition here again? C but not better, just different?

        1. 14

          What’s the value proposition here again? C but not better, just different?

          No macros and no void pointers thanks to comptime, slices (fat pointers) by default and a type system that can encode when a pointer-to-many expects a sentinel value at the end, optionals so no null ptr derefs, runtime checks for array boundaries, sentinels, non-tagged unions, and arithmetic programming errors, error unions to make it harder to mistakenly ignore an error, defer and errdefer for ergonomic cleanup of resources, etc.

          You might want to take a look at the language reference and report your impressions on how better or not Zig is, compared to C.

        2. 1

          merrily

          Not quite… Zig purposefully makes the ergonomics of major footguns poor and the resulting code ugly. For example, converting an array of u32 to u8 is 3 lines of ugly code that screams out “please CR this”

          Also zig async is amazing. It’s modestly hard, but the way it’s structured forces you to think about what the hardware is actually doing instead of abstracting away. Once you get it, it’s very easy, and there is no colored async, and you will probably have a correct mental model of what your code is doing

    2. 17

      I love reading this talking-to-yourself style of writing, thank you.

      1. 8

        I think, done poorly, it can be very confusing if you’re not in the author’s headspace. But Amos’s writing is very readable.

    3. 10

      Although I like this article because it shows the details of writing your own implementations of Future, it would be unfair to assume that one encounters all these issues in the wild when writing async rust. You may encounter one or two depending on the types involved but this post is contrived to show you them all in one go.

      In my experience writing my own futures has been great and not at all as tricky as the article suggests. I think that is because (at least in my case) a Future is a way to provide an async interface to a long process in a different context. Usually you have that context to work with - ie the browser’s DOM or some other callback scenario.

      tldr; I do enjoy these articles. They’re more about grokking rust deeply as you won’t run into most of these problem in practice.

      1. 6

        I get your point. Having written a couple of Futures used in production code, I have questioned the wisdom of doing so.

        I’m not anti async in rust, it just doesn’t feel like the documentation and patterns have caught up enough in a “canonical” way. That is, there’s a lot of smart and well written notes on doing it but at least the last time I did it (~4 months ago), there’s still a lot of rough edges.

        For now, I’ve decided to wait for the dust to settle a bit more before investing anymore time with async/await and Rust.

        I’ve had good luck with channels and threads the last 3.5 years of writing Rust so I’ll probably just stick to that until the dust settles.

      2. 2

        I feel like the article made it fairly clear that this is not something you normally do as a matter of course of developing software.

    4. 6

      Wow, good god! Okay, I hope I don’t need to use Rust for… at least couple of years. It’s funny how many people say that “rust is not changing”, but lib devs require the most fresh rust…

      1. 22

        It’s funny how many people say that “rust is not changing”, but lib devs require the most fresh rust…

        I’m a “lib dev” (and a member of the library team), and several of my libraries still compile with Rust 1.28, which was released almost 2.5 years ago. That includes the regex crate. If I pushed its MSRV up to the most stable release, not a lot would change. I’d get some new niceties from an updated std and maybe a small convenience in some syntax. But that’s it. And there is absolutely nothing released in the last 2.5 years that has compelled me to upgrade its MSRV. (Nothing in particular is making me stay on Rust 1.28 either. I’ll eventually upgrade, but I do it at my leisure.) The last major feature (platform specific vector functions and runtime CPU feature detection) relevant to the regex crate was released in Rust 1.27.

        What’s more, aside from my role as a member of the library team, I’ve never needed to worry, care or use Pin. Likely because I don’t use async Rust because I don’t have a need to.

        There is a really simple explanation for this: different parts of the Rust ecosystem move at different speeds. Async Rust is still pretty young and there’s a lot of rough edges that need to be smoothed out.

      2. 11

        Just say no to async (I do) and you will be fine. Rust is a stable language that is joy to use (at least for me). Just avoid async.

        1. 7

          Yeah that’s basically my strategy with Python. Similar thoughts from a prominent Python developer:

          https://lucumr.pocoo.org/2016/10/30/i-dont-understand-asyncio/

          The number of problems that require async/await is very small. You generally need it for huge scalable cloud services, which is a problem most people don’t have. And even then most of the code can dispatch to threads; in fact it’s almost required to dispatch to threads in such settings for utiilization (to use all your cores).

          The existing cloud services are already written with non-Rust technologies (C++, Go, Erlang, etc.).

          Or maybe if you’re writing a BitTorrent client. You can do that in a bunch of different languages or with a manual event loop.

          Honestly there have been so many BitTorrent clients written that I wonder if any of the authors actually thinks async/await is an improvement for that problem (I have not written one). My guess is that 90%+ of them are written without any such language features, given their age.

      3. 9

        I mean, you don’t have to use async at all.

        I actually done use it in most of my code bases due to the poor patterns/ergonomics around async/await.

        I’ve worked on async/await patterns in other languages and Rust’s definitely has the leakiest abstraction. That may or may not be a good thing depending on how much you want that stuff to be transparent.

        As I noted in another comment, I find channels and threads easier to reason about.

        1. 4

          This is the really weird thing to me; it seems like there was this big push in the last decade towards async that was largely driven by the rise of a runtime (Node and browsers) in which async was literally unavoidable. On a runtime that has access to real threads, there are a handful of cases where the overhead of threads causes bottlenecks, but in my experience these are exceedingly rare and easy to avoid. On top of that, they tend to be domains in which the BEAM runtime dominates so conclusively that it’s difficult to justify building these systems without OTP.

          How is it happening that “the async question” is such a dominant factor in the discussions around Rust? Is it just due to people coming from other runtimes and assuming that “you can’t scale without async” or is there more to it?

          1. 3

            I’m not entirely sure. I spent most of my career was spent in C/C++ and so I got comfortable with multithreading in those languages early on. I wouldn’t argue that this is the best way, but it’s a way that has familiar patterns. In this model, Rust actually shines due to blocking a lot of the bad behaviors - sharing objects between threads and not properly locking, etc.

            However, async/await in Rust has felt awkward from the start to me. I don’t care enough about the async/await pattern to be too broken up about it though. If the community manages to iron it out and make it less rough around the edges, then I’ll invest more of my time into it.

          2. 2

            I believe early on two big use-cases without threads influenced the need for async. That’s not a first-hand knowledge, so it might be wrong. First, Fuchsia uses async Rust for its network stack, including the user-space drivers. Fuchsia devs were a major force shaping async. Second, you need async for wasm to interop with JS promises, and wasm was early or recognized as an important thing for Rust.

            I don’t know what explains “today”s discussions, I suspect it’s a combination of many less-technical factors. Async vs threads is important for web, and a lot of people do web. Async vs threads is controversial so you can argue a lot which is better than. Async in rust specifically is young and complicated, so there’s a lot to explain.

            1. 1

              That makes sense - I’ve definitely encountered the wasm scenario. You need async there simply because you’ve only got one thread, period. Instead of managing the cooperative multithreading aspect on a single thread, it’s easier to just use async.

              I think these are good uses of async.

          3. 1

            I still maintain that async/await is probably one of the best “bang-for-your-buck” concurrency patterns out there, up there with Actor models (popularized by BEAM and Akka), balancing developer simplicity with performance. I think the question of whether async is more effective than modern threads (with smaller thread stack sizes, and memory that’s only virtually allocated by the kernel until used) at solving C10K is different altogether. For the average developer, being able to do await func() to have a function run “asynchronously” is a lot simpler than thinking about thread pool sizes, sharding, and other things dealing with threads. I do think for the average concurrent application (so I’m talking about low-to-medium scale), threads are just as effective as async and perhaps even more so, but the developer experience of working with async is compelling enough that users are interested in async.

            Julia also has spent time baking in async into the language. It’s increasingly a higher level pattern that I think many developers enjoy using. I know here a lot of folks tend to prefer threads, but I think that’s not very applicable to the average application developer looking to use concurrency in their application without much work. Libraries like Rust’s rayon do really offer a compelling way to use threads without thinking too hard about the underlying complexities of spawning and retiring threads, but rayon style threading is only applicable to certain types of workloads.

    5. 3

      Honestly I was just happy it worked?

      I laughed.

    6. 2

      I could simply not get my head around why Pin is a thing in the context of Rust, or its implications, and then I found this page, in particular Figure 1.

      The best explanation I could find for myself was that if you have a pointer to a reference (for example strings end up being pointers to these heap-allocated lists of characters), then you can be sure that the value behind that reference isn’t going to get changed from under your nose. But it’s still a bit hard for me to really nail down the feeling that I get it.

    7. 2

      There are many things that can go wrong here that the Rust compiler is not catching. Just saying.