1. 65
    1. 19

      I think zig does offer more safety than the author lets on. It definitely offers flexibility, a good bootstrap story, and will work to make memory mistakes visible to the programmer.

      Rust is better for critical stuff than C. Zig seems nicer to use, to me, than both.

      1. 34

        I think Zig’s approach to memory safety is not reasonable for any new system language. Temporal memory errors is about the single biggest problem large code bases, and Zig is the only new language that does not make any real attempt to fix this. We’ve got decades of evidence demonstrating that manual memory management is fundamentally unsafe as it requires no mistakes by a developer, but is also infeasible to analyze without language level support for tracking object life time. There are numerous ways a language can do this, from zero runtime overhaad (rust w/borrow check) through to a full tracing GC - though I believe the latter simply is not feasible in kernel level code so seems impractical for a general “systems language”.

        I know whenever I bring up temporal safety someone chimes in with references to fuzzing and debug allocators, but we know that those are not sufficient: C/C++ projects have access to fuzzers and debug allocators, and the big/security sensitive ones aggressively fuzz, and use aggressive debug allocators while doing so, and those do not magically remove all security bugs. In fact a bunch of things the “debug” allocators do in Zig are already the default behaviour of the macOS system allocator for example, and the various webkit and blink allocators are similarly aggressive in release builds.

        1. 5

          I think it depends on your priorities and values and what you’re trying to do. Rust does not guarantee memory safety, only safe Rust does. If what you’re doing requires you to use a lot of unsafe Rust, that’s not necessarily better than something like Zig or even plain C. In fact, writing unsafe Rust is more difficult than writing C or Zig, and the ergonomics can be terrible. See for why wlroots-rs was abandoned for example.

          From my experience, I’ve found Rust to do great when no unsafe code or complex lifetimes are involved, such as writing a web server. It also works great when you can design the memory model with lifetimes and the borrow checker in mind. When you can’t, or you have to deal with an existing memory model (such as when interacting with C), it can fare very poorly in terms of ergonomics and be more difficult to maintain.

          1. 12

            Rust does not guarantee memory safety, only safe Rust does.

            And that’s great, because people are writing very small amounts of code in unsafe Rust.

            I’ve also implemented a programming language VM in Rust and found that you can usually define good abstractions around small amounts of unsafe code. It isn’t always easy (and wlroots legitimately seems like a hard case, but I don’t have enough domain expertise to confirm) but given how rarely you need to do it it seems manageable.

            But I think focusing on a few hard cases is missing the forest for the trees. If you can write the vast majority of code in a pleasant, safe and convenient language it very quickly makes the small part that you do need to be unsafe or fight lifetimes worth it.

            I would love to see a language that does even better and handles more cases. But until that comes along I would strongly prefer to use Rust which helps me write correct code than the alternatives just because in a few sections it doesn’t help me.

            1. 3

              But I think focusing on a few hard cases is missing the forest for the trees. If you can write the vast majority of code in a pleasant, safe and convenient language it very quickly makes the small part that you do need to be unsafe or fight lifetimes worth it.

              That’s fine, but we don’t need to have only one be all end all programming language. We can have different options for different situations, and that’s what Zig and other languages provide in contrast to Rust.

      2. 15

        I really enjoy using Zig more than using Rust. I have to work with existing C codebases, and Rust seems to hold C code at arms length, where Zig will let me integrate more fluidly. Both Rust and Zig seem like great replacements for C, but Zig seems to play more nicely with existing software.

        1. 5

          I enjoy using Zig a lot more than Rust as well. While Rust has many fancy features, I am frequently fighting with them. And I’m not talking about the borrow checker, but rather limitations in the type system, tooling, or features being incomplete.

          But all that aside I think where I would use Zig in a commercial setting is limited, only because it is too easy to introduce memory use issues, and I don’t think the language is doing enough, at least yet, to prevent them.

      3. 9

        This. I’ve been using Rust way more than Zig, and even used Rust professionally, for about 3 years, and I have to say Zig really gets a lot right. Zig’s memory safety system may not be on-par with Rust but that’s because it opts to make unsafe code easier to write without bugs. Traversing the AST (which is available via std.zig.Ast), you can get the same borrow checker system in place with Zig 🙂 Now that Zig is maturing with the bootstrapping issue figured out, I bet we’ll see this sooner than later, as it’s a real need for some.

        Zig is builder friendly. You ever try C interop with Rust? Rust is enterprise friendly.

        1. 17

          But lifetimes in rust are based on the control-flow graph not on the AST. Lexical lifetimes got replaced by non-lexical lifetimes back in 2018. Also there are lifetimes annotations, how would you get those?

          1. 1

            I know neither zig nor rust, and do not plan to, but: usually, a control-flow graph (along with more complicated structures besides) is built from an ast; is there some reason you can not do this for zig?

            1. 8

              It’s like the “draw two circles and then just draw the rest of the owl” meme.

              You can make CFG from AST easily, but then liveness and escape analysis is the hard part that hasn’t been solved for half a century, and even Rust needs the crutch of restrictive and manual lifetime annotations.

        2. 7

          Traversing the AST, you can get the same borrow checker system in place with Zig

          Do you have more reading on this? Because my understanding is that Rust’s borrow checker isn’t possible to infer all lifetimes from the AST alone. Hence it’s infamous lifetime annotations ('a). While Rust inference is generally pretty good, there are definitely plenty of scenarios I’ve run into where the inferred lifetimes are wrong, or ambiguous in which both cases require the additional syntax.

          1. 1

            Because my understanding is that Rust’s borrow checker isn’t possible to infer all lifetimes from the AST alone.

            It would absolutely be a learning experience for it to be explained to me why :) I don’t see why not: the AST describes the full program.

            1. 13

              If you could prove the safety (or lack thereof) of mutable aliasing entirely through syntactic analysis, the issues we have with C would be a lot less glaring. Having a parser in the standard library is incredibly cool, as is comptime, but you still have to build a static verifier for borrow checking your language/runtime of choice.

              I do think there’s a good opportunity for conventions and libraries to emerge in the Zig ecosystem that make writing code that’s “safe by default” a lot easier. You could absolutely force allocations and lookups in your code to use something like a slotmap to prevent race-y mutation from happening behind the scenes.

              Unfortunately that confidence doesn’t extend to 3rd-party libraries. Trying to transparently interface with arbitrary C code (which may in turn drag in embedded assembler, Fortran, or who knows what) means bringing all of the memory safety issues of C directly into your code base. See: literal decades of issues in image format decoding libraries, HTML parsers, etc. that get reused in nominally “memory-safe” languages.

              All of which is precisely why Rust does keep C/C++ at arm’s length, no matter how appealing trivial interop would be. Mozilla, MS, and Google/Android have custodianship over hundreds of millions of lines of C++ code, and yet they also understand and want the safety benefits Rust provides enough to deal with the more complicated and toilsome integration story vs. something like Zig or D.

              1. 1

                Right right, I made a big mistake to say “same”. Others mention annotations - those could be created in comments or in novel ways Im sure ! We’re starting to see things like CheckedC so yea, my thoughts still stand…

            2. 10

              But there isn’t enough information in the AST without annotations. Else you wouldn’t need mut and lifetime annotations in rust. The reason for those annotations is that the compiler couldn’t proof that the safe rust code doesn’t introduce new memory bugs (safe code can still trigger memory bugs introduced by unsafe code) without those annotations. The AST describes the whole program, that doesn’t mean that every property that is true for your code can be automatically inferred by an general algorithm. A well known example would be if your program computes in a finite amount of time.

              1. [Comment removed by author]

        3. 3

          Doesn’t zig still require manually freeing memory at the “right time”?

    2. 16

      Ada/SPARK offers even more securities than Rust and is as low-level as C/C++, however, it has no hype-train behind it that pushes it into every discussion. One of the strongest points it offers is that it’s actually ISO-standardized and not simply specified by implementation (i.e. rustc). Ada also has a really really strong type-system that let’s you go beyond the system-level and really nail down consistent program state, and it is more readable in my opinion.

      I sometimes get the feeling that Rust evangelists truly believe nothing has happened between 1980 and today in regard to high-assurance low-level languages, and the claim that Rust was the only thing filling this niché is nothing short of arrogant and preposterous.

      And I say that as someone who truly values the advantages Rust brings and looks closely how it develops. Given human nature, though, one ends up alienating a lot of people with very outlandish remarks like that, because people invest themselves in ecosystems and will rather discard cognitive dissonance than try something new. This is, I think, one big reason why Haskell, which set off with a great start, kind of poofed away over the years.

      1. 20

        Ada/SPARK offers even more securities than Rust

        I think I’ve finally formulated what make me uneasy about this claim. Ada/SPARK doesn’t offer more, it offers a different thing. With SPARK, you can write a formal mechanically checked proof that your program is correct. Which is great for the kinds of software where you want to spend extra effort to make it provably correct. Without SPARK though, Ada is just a run-of-the-mill language in terms of safety, which is trivially unsound if you use heap, but also unsound even without heap (via “change the variant in a variable of enum type” trick).

        1. 7

          Thanks for your input! Surely Ada/SPARK and Rust address different audiences, but the point still stands that Ada offers more. Rust is merely focused on memory safety (which is a big thing, nonetheless of course, and the major source of bugs), but does not prevent invalid program state, which is another big thing that needs to be overcome if you want to end up with more formally verifiable software.

          Even with vanilla Ada you have full control over your types, where SPARK merely adds on static rather than dynamic guarantees. I can define a type that can only hold prime numbers using dynamic predicates, a type that only holds a certain temperature range or cyclic types with predefined intervals. But the best part is that I also have full control over their memory layouts. So bitwise interfaces at the lowest level are still as flexible as in C, but without boilerplate.

          While Rust offers so much more than C/C++ in terms of memory safety, which is good, it still does not transcend the hardware. Ada is an ingenious link between the two. Regarding SPARK: There is not a single level of SPARK conformance, but multiple. I think that this is a great approach, given you can choose how much you want to focus on formally proving a certain part of a program.

          Don’t get me wrong, though: I am not trying to subtract from Rust, but rather reflect that Ada gets unjustly little reflection in the community. It may be a bit boring and “corporate” given its applications, but there’s no reason to deny it’s very big intersection in problem domain with Rust. Admittedly, Rust’s borrow checker is more flexible than Ada’s memory model, but a borrow-checker-model will be added to Ada 202x, bringing the languages at least on par again. These are exciting times! :)

          1. 9

            To be fair, I have no idea how a language that powered comet landers, space probes, and launch vehicles can be seen as boring in a bad way. ;)

            1. 1

              Good point! It’s the good kind of boring. :)

          2. 8

            Fortunately, there may be more collaboration between Rust and Ada than is widely known. AdaCore was (and is, as far as I know) helping to write a Rust specification (Ferrocene). Last month, the Rust language design team started arranging meetings with people from formal verification systems including SPARK.

            1. 1

              Follow-up: Minutes from the initial meeting were posted, along with a draft RFC: https://lobste.rs/s/nqj4th/rust_contracts_rfc_draft_minutes_from.

      2. [Comment removed by author]

    3. 9

      I am really looking forward to this shift. I don’t work on particularly low-level stuff, but I do work in Rust; so with more and more low-level code being written in Rust, at least the language is not a blocker (the way C or C++ is) for me to expand my knowledge in that direction.

      My point isn’t only one of personal satisfaction, but that my situation isn’t unique. Rust is not only memory safe and all that. It is also a language that can bridge low and high(er) levels of coding/coders. :)

    4. 5

      Is anyone using Pony?

      1. 0

        Realtalk: The only reason I have not and will not ever try it is the embarassing “brony”-name, and I say that as someone working with Coq.

        1. 8

          This makes me deliberately want to create and promote programming language whose name is an explicit My Little Pony reference. “Fluttershy” or something like that.

        2. 6

          wut? nothing on pony-lang’s website suggests anything at all to do with my little pony or “bronies”

          https://www.ponylang.io/blog/2017/05/an-early-history-of-pony/

          1. 0

            Indeed, but the association can easily be falsely made.

            1. 16

              I think you’re projecting pretty hard here.

              That article clearly gives the origins of the name.

              Back in the flight sim days, when I would make my friends groan by telling them yet again about all the things I was going to do when I wrote a programming language, one of the people I would tell was Nathan Mehl. And one time, when I gave him yet another laundry list, he said: “yeah, and I want a pony”.

              1. 2

                That’s also the origin of the “pony” mascot for Django.

    5. 8

      It is inevitable, and Zig does not offer anywhere near the protections that Rust currently has. The Zig/Rust memory errors blog post thing springs to mind as it was revitalized a few months back, and in it’s current state Zig doesn’t do anything fancy with regards to bounds checks or lifetimes.

      The open source world will start to embrace Rust slowly over time as it’s ecosystem expands, but that story isn’t exactly new; we’ve seen the trend before with Python, with Ruby, with PHP, and with JavaScript. Each have had their glow-ups with new packages/libraries developed and updated each year. Rust will get there, as more dedicated people cluster together to create cool new things to use within Rust. And hey, for us to get more critical software to use Rust for safety purposes, the better.

      Personally, I just don’t really like using Rust. There are simple things, then there are straight up annoying things. Boxing every type, putting Arc everywhere, writing really long and cumbersome match statements, writing really weird looking function signatures that have trait boundaries and you can do basic addition on these trait boundaries. I’ve been using Rust on and off for a few years now following it’s progress and it never feels fun for me. Even due to the nature of lifetimes and the borrow checker, it’s hard to write classical computer science data structures in it without having to use the Pin type to pin pointers to keep linked list nodes. There’s only so many times you can find it cute to have the compiler fight you over something that mentally seems okay to you.

      I started using Zig a while back and I find it a breath of fresh air and in some sense, relieving. I’m not overwhelmed with fancy trait boundaries, the standard library really doesn’t fight you, and it’s easier to tell when you do memory allocation in Zig because you need to share an allocator in your code to do so. I even find it fun in some sense to try and write some old C/C++ programs and convert it over to Zig. Interop with C/C++ is easier because Zig compiles C/C++ and can drop-in as a C/C++ compiler for old projects.

      I wouldn’t expect the world to start suddenly using Zig over Rust, given what Rust can provide large projects at scale. I enjoy that Zig is a small community of people working together on something cool. Rust’s governance is a little too large and feels like that of yet another C/C++ standards committee, while Zig has Andrew overseeing things, and you’re more likely to bump into him on IRC and have chill conversations with him. I like that a lot about the Zig space right now. A lot of time was spent on Zig getting it to the self-hosted compiler, and with that out of the way we can all look forward to what comes next in the Zig era.

      1. 3

        I’m a rust newbie and some of this rings true, at least so far. I finished all the rustlings exercises so was feeling pretty confident until I hit a brick wall trying to read & modify two elements of a vector within the same code block. After struggling for a while and doing a lot of internet searches I finally posted on stackoverflow where I was directed to a duplicate question and learned about the slice::split_at_mut function. I would not say this experience has left me feeling energized. Here I am doing complex index arithmetic and the thing that gives me the most trouble - and that the compiler finds most alarming - is just accessing two different elements within the same scope? I haven’t even thought about writing a data structure with it.

    6. 6

      nothing else fills the niche for ‘C/C++ but strongly safe’.

      Swift and Nim both have pretty good safety properties, compile to native code, and interop pretty well with C APIs. But neither offers as clearly delineated a boundary between safe and unsafe code as Rust does. (A few years ago I argued for adding something like Rust’s unsafe keyword to Nim, but most of the community opposed it.)

      Go has good safety, but the NIH attitude of its runtime (aka “let’s pretend we’re running on Plan 9”) makes it unpleasant to interoperate with non-Go code.

      We need a replacement C because in general we can’t write safe C/C++ at scale.

      Agreed, although I believe C++ with modern idioms and tools is a much, much safer language than C. By which I mean avoiding new and delete, using RAII, enabling and paying attention to most compiler warnings, and using the UB and address sanitizers during development.

      1. 8

        I like Go, but it lacks the concurrency safety that Rust has, and also the runtime minimalism common to Rust and C (partly because Go has both garbage collection and concurrency support). There are a lot of projects that either need or want C style minimalism in resource usage and runtime requirements; they’re not going to adopt Go. Go is also hard to partially adopt due to its runtime requirements among other things, while Rust offers a migration path of gradual and partial adoption in a codebase.

        (I’m the author of the linked-to entry.)

        1. 19

          I think concurrency in Go has been somewhat overblown, and over time it’s become clearer that while goroutines are very cool, they’re a very blunt instrument that are useful for a smaller set of problems than originally thought.

          In particular, Bryan Mills has a very good talk on this: https://drive.google.com/file/d/1nPdvhB0PutEJzdCq5ms6UI58dp50fcAN/view (I would note a number of people in the YouTube comments say the examples are hard to understand… IMHO this isn’t Bryan’s fault but rather an indicator that the underlying problem of concurrency being difficult to understand has been there the whole time; writing this stuff safely in a simple manner in Go is quite hard).

          My view is you either need goroutines at a very low-level (working directly with I/O) or a very high level (spawning multiple listeners of something like an HTTP server). You’ll generally find a library that does these things better than you can, and most people most of the time shouldn’t be reaching for goroutines at all.

          The change that has really modified the state of the concurrency debate is container orchestration like Kubernetes or managed orchestrators like Google Cloud Run. Scaling horizontally is almost certainly cheaper in the long run than the wasted engineering hours debugging some concurrency failure (Go’s TSAN does help with this a lot but it’s non-deterministic so you do have to hope for the best there).

          TL;DR don’t reach for Go because of goroutines, reach for Go because it is good at solving some other kind of problem you have.

          1. 9

            Consider submitting the talk as a lobsters story. I am only 30 slides in, and it’s already a gem!

          2. 1

            Scaling horizontally, aka “throwing more servers at the problem”, isn’t applicable in a lot of domains … like client-side or embedded software, or anything else not involving a server.

      2. 4

        Go has good safety, but the NIH attitude of its runtime (aka “let’s pretend we’re running on Plan 9”)

        It’s not NIH if they are reimplementing the standard APIs from an existing operating system (one that was literally inventing in the same place by the same people as UNIX). Use your words instead of throwing initialisms around.

        1. 3

          I said NIH because Not-Invented-At-Bell-Labs takes longer to type.

          It’s not the APIs I’m referring to, rather the ABI that make Go kind of an alien in the OS. It makes normal debugging/profiling tools kind of useless since they can’t even get a stack trace, and it creates overhead and complexity when calling between Go and anything else.

        2. 1

          After all not every language requires initialising

          I’ll see myself out

      3. 2

        Swift and Go aren’t touching all the same niches because they’re heavily runtime-dependent.

        1. 1

          Swift has a big standard library, and runtime code for managing object ref-counts. But so does Rust (viz. the RC and ARC classes.)

          I’m not denying Rust is currently better positioned for working in embedded environments. But I don’t think the difference is as stark as you say.