Hacker News new | past | comments | ask | show | jobs | submit login
Zig: programming language designed for robustness optimality and clarity [video] (youtube.com)
136 points by espeed on May 30, 2018 | hide | past | favorite | 70 comments



I've been building a toy project in zig in the last couple weeks. Zig is incredible. After only a short while working with it, it feels like deserves the title of a "better C". With no runtime overhead you get:

* Type inference - var name = "Bob";

* Maybe types (from Elm/Haskell) that prevent NULL ptr bugs and syntactic sugar encourages its use for function return values.

  if (doit()) |result| {
    // result is always a populated Struct
  } else |err| {
    // error is always a populated Error
  }
  
  // a return type of !Something means Error type or Something type
  fn doit() !Struct {
  }

* Nullable types - var ?&object = null; if you need it, and var &object = aObject; if you want the safety.

* Great C interop, though there is more work to be done here. (gtk.h for instance is too much for zig today) Zig's approach is unique here too. You specify .h files in the zig source code and the zig compiler translates C in the .h into zig. Its not a function bridge or wrapper, its zig objects and zig functions available to your zig code.

  const c = @cImport({
    @cInclude("curl/curl.h");
  });

  var curl = c.curl_easy_init();
  if(curl != null) {
    _ = c.curl_easy_setopt(curl, c.CURLoption(c.CURLOPT_URL), url_cstr.ptr);
  ...
* Standard library operations that allocate memory take an 'allocator object' parameter that should give great programmer control over memory management (I didnt get into this myself), webasm is a compiler target, and lots more


I think you mean Result or Either like Haskell etc., not Maybe.

Additionally, Swift has essentially the same approach to C FFI.


Do nullable types and maybe types add much over checked dereferences (a la Java)?

I imagine they might be good for performance (fewer checks), but does it really help with correctness/convenience/elegance/readability/etc?


In my experience from Rust & co, yep, this improves correctness a lot. Elegance, not so much.


I think some of the combinators can be elegant. E.g. `Option::map`.


Swift’s syntax sugar for Optionals makes working with nullable types much more elegant, too.


My personal experience of Swift vs. Rust is that Swift's syntax sugar is more readable for most examples, but Rust lack of syntax sugar scales better to more exotic examples.

YMMV


Yes, because you get told about problems at compile-time rather than at runtime.


I think I get it now: use different types for nullable and non-nullable pointers. Only non-nullable pointers can be dereferenced, and the conversion from nullable to non-nullable must be done explicitly, with different flow in the case that the pointer turns out to be null (which is to say, a non-nullable pointer will never enter scope).

Dereferencing null is impossible, and the programmer is forced to explicitly handle null values.

This contrasts with C, where the same type is used for a nullable and a non-nullable pointer, so the compiler can't help out, the programmer is at risk of forgetting/failing to keep track of the difference between the two, and null-dereferences may occur (and give you undefined behaviour).

Java references take the same approach as C pointers, except all dereferences are checked at runtime, and dereferencing null throws an exception.

I like it! It does better than the Never null, only cromulent values approach (like C++ references), as this can be inconvenient in practice. It makes null-dereferences impossible, and doesn't do anything funky that would introduce needless runtime overhead.


I have read quickly through the tutorial, and this looks interesting. Objectives are similar to Rust, with a few twists.

A few differences that I can see:

- At first glance, Rust's `enum` looks safer and more powerful than Zig's `union` + `enum`, while Zig's `union` + `enum` appears more interoperable with C.

- Zig's `comptime` is quite intriguing. In particular, types are (compile-time) values and can be introspected.

- Zig's generics are very different from Rust's generics. No idea how to compare them.

- In particular, Zig's `printf` uses `comptime`, while Rust's `print!` is a macro.

- Zig's Nullable types/Result types look bolted in and much weaker than Rust's userland implementation.

- I don't see closures in Zig.

- I don't see traits in Zig.

- I don't see smart pointers in Zig, and more generally, I have no idea how to deallocate memory in Zig.

- Zig's memory management encourages you to check whether your allocations have succeeded, while Rust's out-of-the-box memory management assumes that allocations always succeed - if you wish to handle OOM, you'll need a "let it fail" approach.

- Zig's alias checker seems to be much more lenient than Rust's.

- I don't see anything on concurrency in Zig's documentation.

- Most of the Zig examples I see seem to fall in the "unsafe" domain of Rust by default. For instance, uninitialized memory or pointer casts seem to be ok in Zig (if explicitly mentioned), while they must be labelled as `unsafe` to be used in Rust.

- According to https://andrewkelley.me/post/unsafe-zig-safer-than-unsafe-ru..., Zig performs some checks that Rust does not perform in an `unsafe` block.

- Zig supports varargs, Rust doesn't (yet).

For the moment, I'll keep coding in Rust, but I'll keep an eye on Zig :)


Rust does support varargs in extern functions (FWIW): https://play.rust-lang.org/?gist=92fbdf9bdc95c09d16e30c03ffa...


Ah, right.


To elaborate on a few of your remarks/questions.

> At first glance, Rust's `enum` looks safer and more powerful than Zig's `union` + `enum`, while Zig's `union` + `enum` appears more interoperable with C.

A `union(TagType)` in Zig is a tagged union and has safety checks on all accesses in debug mode. It is directly comparable to a Rust enum. Any differences are probably more down to the ways you are expected to access them and Rust's stronger pattern matching probably helps some here.

> I don't see traits in Zig.

Nothing of the sort just yet although it is an open question [1]. Currently std uses function pointers a lot for interface-like code, and relies on some minimal boilerplate to be done by the implementor.

See the interface for a memory allocator here [2]. An implementation of an allocator is given here [3] and needs to get the parent pointer (field) in order to provide the implementation.

It isn't too bad once you are familiar with the pattern, but it's also not ideal, and I would like to see this improved.

> I don't see smart pointers in Zig, and more generally, I have no idea how to deallocate memory in Zig.

You would use a memory allocator as mentioned above and use the `create` and `destroy` functions for a single item, or `alloc` and `free` for an array of items. Memory allocation/deallocation doesn't exist at the language level.

> I don't see anything on concurrency in Zig's documentation.

There are coroutines built in to the language [4]. This is fairly recent and there isn't much documentation just yet unfortunately. Preliminary thread support is in the stdlib. I know Andrew wants to write an async web-server example set up multiplexing coroutines onto a thread-pool, as an example.

> Zig supports varargs, Rust doesn't (yet).

It's likely that varargs are instead replaced with tuples as a comptime tuple (length-variable) conveys the same information. I believe this fixes a few other quirks around varargs (such as using not being able to use varargs functions at comptime).

[1] https://github.com/ziglang/zig/issues/130

[2] https://github.com/ziglang/zig/blob/15302e84a45a04cfe94a8842...

[3] https://github.com/ziglang/zig/blob/15302e84a45a04cfe94a8842...

[4] https://github.com/ziglang/zig/blob/15302e84a45a04cfe94a8842...


> A `union(TagType)` in Zig is a tagged union and has safety checks on all accesses in debug mode. It is directly comparable to a Rust enum. Any differences are probably more down to the ways you are expected to access them and Rust's stronger pattern matching probably helps some here.

But if I understand correctly, out-of-the-box, Zig's `union` doesn't get a tag type, right? That's what I meant by Rust's `enum` being safer: you can use it safely in Zig, but you have to actually request safety, because that's not the default behavior.

I probably should have phrased it differently, though.

And the "more powerful" is about the fact that a Rust enum can actually carry data, while it doesn't seem to be the case with Zig.

>> I don't see smart pointers in Zig, and more generally, I have no idea how to deallocate memory in Zig. > >You would use a memory allocator as mentioned above and use the `create` and `destroy` functions for a single item, or `alloc` and `free` for an array of items. Memory allocation/deallocation doesn't exist at the language level.

So, it sounds like deallocations are not checked by default, right?

> I know Andrew wants to write an async web-server example set up multiplexing coroutines onto a thread-pool, as an example.

That would be a nice example, for sure!


> But if I understand correctly, out-of-the-box, Zig's `union` doesn't get a tag type, right? That's what I meant by Rust's `enum` being safer: you can use it safely in Zig, but you have to actually request safety, because that's not the default behavior.

Sure. I think that's more an effect of the choice of keyword defaults here. A straight union is very uncommon and is typically solely for C interoperability.

> And the "more powerful" is about the fact that a Rust enum can actually carry data, while it doesn't seem to be the case with Zig.

A tagged union can store data as in Rust. See the examples in the documentation [1]. Admittedly Rust's pattern matching is nicer to work with here.

To summarise the concepts:

- `enum` is a straight enumeration with no payload. The backing tag type can be specified (e.g. enum(u2)).

- `union` is an unchecked sum type, similar to a c union without a tag field.

- `union(TagType)` is a sum type with a tag field, analagous to a Rust enum . A `union(enum)` is simply shorthand to infer the underlying TagType.

> So, it sounds like deallocations are not checked by default, right?

If referring to if objects are guaranteed to be deallocated when out of scope then no, this isn't checked. There are a few active issues regarding some improvements to resource management but it probably won't result in any automatic RAII-like functionality. This is a manual step using defer right now.

[1] https://ziglang.org/documentation/master/#union


> Sure. I think that's more an effect of the choice of keyword defaults here. A straight union is very uncommon and is typically solely for C interoperability.

Fair enough. That's why Rust also has a `union` keyword, which is always `unsafe`.

> A tagged union can store data as in Rust. See the examples in the documentation [1]. Admittedly Rust's pattern matching is nicer to work with here.

Ah, right, it could be any struct instead of being a bool or integer. I missed that.

> If referring to if objects are guaranteed to be deallocated when out of scope then no, this isn't checked.

I was wondering about that and double-deallocations.

> There are a few active issues regarding some improvements to resource management but it probably won't result in any automatic RAII-like functionality.

Out of curiosity, what kind of improvements?


> - `union` is an unchecked sum type, similar to a c union without a tag field.

My understanding was that Zig unions are tagged, but if you don't explicitly specify the tag type the compiler will choose for you. Indeed, the first example in the documentation suggests normal unions are tagged:

  // A union has only 1 active field at a time.
  const Payload = union {
      Int: i64,
      Float: f64,
      Bool: bool,
  };
  test "simple union" {
      var payload = Payload {.Int = 1234};
      // payload.Float = 12.34; // ERROR! field not active
      assert(payload.Int == 1234);
      // You can activate another field by assigning the entire union.
      payload = Payload {.Float = 12.34};
      assert(payload.Float == 12.34);
  }
Or do straight unions only get a hidden tag for debug/safe builds? My memory is a little fuzzy.


This programming setup is the bomb.

Zig is designed not only for robustness, optimality, and clarity, but also for great justice.

It's great that Zig is an open source project so all your codebase belongs to us.

I hope 'Zig' takes off everywhere.


From the slides, these bullets struck me as things I have wanted for a while (to the extent that I have my own toy language that addresses some of them):

- compiles faster than C

- Produces faster machine code than C

- Seamless interaction with C libs

- robust/ergonomic error handling

- Compile-time code execution and reflection

- No hidden control flow

- No hidden memory allocations

- Ships with build system

- Out-of-the-box cross compilation


> - No hidden control flow

That means no destructors, right?

edit Ah, if I read the documentation correctly, there are destructors.


I think it means no destructors, no overloaded operators, no automatic getter/setters for properties. Nothing that does a function call unless you can immediately tell by looking at it that it’s a function call.


You're right - there's no proper RAII (but there's a 'defer' keyword, which runs your expression on scope-exit, like BOOST_SCOPE_EXIT), no operator overloading, no exceptions, and all function calls look like function calls. It does have a language feature for handling error-codes though. [0] [1] [2]

Contrast with C#'s 'properties', where a method call (which might throw) is disguised as reading/writing a member.

My favourite example of unexpected semantics is D's lazy keyword, where, at the call-site, you have no idea whether your argument will be evaluated lazily or eagerly! [3]

C# has a pass-by-reference keyword which modified the way an argument is treated, but it has the sense to force use of the keyword at the call-site too, so that everything is clear. [4]

I like the language's philosophy, I'll have to keep an eye on it. I suspect they'd do well to have the language compile to C, though. Is there any reason that wouldn't be a good fit? I see it has a templates system, but at (very) first glance I don't see anything that wouldn't map cleanly to C.

[0] https://ziglang.org/documentation/master/#defer [1] https://ziglang.org/download/0.1.1/release-notes.html [2] https://andrewkelley.me/post/intro-to-zig.html [3] https://dlang.org/articles/lazy-evaluation.html [4] https://docs.microsoft.com/en-us/dotnet/csharp/language-refe...


I think it means no exceptions


I experimented with Zig for embedded programming. The language is very promising for this application. It still needs some features and maturity, but it's getting there. The cross-platform support is also has some missing stuff and bugs, but many of those were actually in LLVM in my case.

To me, Zig is the most promising replacement to C right now. It seems to do everything right.

Rust is of course another candidate, with a different philosophy, but I don't consider it a pure C replacement due to core language features requiring an advanced compiler. Zig, for the moment, should be pretty simple to write a compiler for. I consider this a strength of C, that something like TCC can exist.


> So let's talk about memory. I know it's every programmer's least favourite topic

I thought that was time zones? (https://news.ycombinator.com/item?id=17181046)


What the hell are we going to do when humans are multiplanetary? Keeping computers in track of time is already a mess and we aren't even on mars yet.

I've worked in some dirty gross code, but messing around with time is one of things I can just live without.


Every time there is news about a language, the first thing I am interested in is reading the syntax, I don't really care about anything else.

Unfortunately it's often hard to find code examples.

Zig doesn't fail that rule, I browsed 3 or 4 pages or so on the website, and could not find decent code examples.

It's frustrating. You should be proud of the syntax choices you're making and it should be the first thing you see so that developer might get a little interested, look at rust and go and nim.


I just clicked on "documentation" and found examples:

https://ziglang.org/documentation/master/


He's wrong about Lisp not being capable of handling heap exhaustion through "exceptions" (conditions in Common Lisp).


I've been advised to a write a preprocessor so that I can make a language that compiles to C. I'm mostly fine with C, except I want to make it more readable and with a 'sweeter' syntax.

Features I want are: * pythonic indent * no trailing semicolon * vector, hashmap and map containers * immutable strings


> Features I want are: * pythonic indent * no trailing semicolon

I would recommend against making up a completely new syntax, since there would be no support in any existing tools (parsers, linters, IDEs, formatters, ...). Instead, I'd suggest picking a general-purpose syntax with the properties you want (indentation for grouping, newlines for sequencing). One excellent example is called "sweet-expressions", which was originally invented for Scheme but can actually be used to represent any structured data (especially code):

https://srfi.schemers.org/srfi-110

You can use tools like "sweeten" and "unsweeten" from https://sourceforge.net/projects/readable/files to convert between sweet-expressions and s-expressions. S-expressions are just raw syntax trees, so "unsweeten" is basically a ready-made parser which you can pipe into any other program you like, e.g. linters, macro expanders, pretty-printers, etc.

To make your language compile to C, you would need some way to "render" those syntax trees into C code. There are existing tools to do this, for example the C-Mera system can read in C syntax trees and write out C code https://github.com/kiselgra/c-mera

Your preprocessor could then be as simple as `unsweeten | cm c`

To add completely new features like immutable strings you would just need to define some representation for them in the syntax trees (or, if you want to make them the default for "double quotes" you should define an alternative representation for non-immutable strings), then write some find/replace macros to convert occurrences of this representation into an equivalent (but more verbose and elaborate) set of C syntax trees. Stick this find/replace step in between `unsweeten` and `cm` and you've got brand new syntax for very little work.


Have you tried Nim? It has (almost?) everything you're looking for and compiles to C, C++ or JS.

https://nim-lang.org


> It has (almost?)

No, it has all the features mentioned in GP. It's worth noting, though, that Nim is a higher-level language than Zig appears to be. Nim is garbage-collected, has an object system with multi-methods, has proper lambdas and higher-order functions, uses a kind of uniform access principle (`foo.func()` is the same as `func(foo)`, basically), has destructors and defer blocks, exceptions, iterators, generics, operator overloading, AST based (but procedural) macros and templates, a kind of type-classes (called concepts), built-in concurrency (thread pool) support, and more.

I'm not sure how well it would work on microcontroller, for example, although its garbage-collector is tunable in terms of memory and times constraints. But for anything higher-level than that, Nim is a really nice language, which reads very similar to Python but is natively compiled and much faster (among other features). A quick example to back up the similarity claim:

    proc getMem() : tuple[total: int, free: int] =
      let
        (output, _) = execCmdEx("free")
        fields = output
          .split("\n")[1]
          .split()
          .filterIt(it != "")
      return (fields[1].parseInt(), fields[^1].parseInt())
Really worth taking a look at, if you want conciseness and performance without compromising readability.


It works on microcontrollers just fine using the new GC or by disabling it.


His distinction with hidden memory allocations vs perfect software fails even in his C boolean example.

Calling a C function takes up stack space. That's a hidden memory allocation which can exhaust just like heap does. But it's even worse because stack allocation failures don't have a place that return a nice clean NULL like malloc does. It's pretty strong to still call this case "perfect software" under his definitions.


Right, he answers a question about this. His plan seems to be to pre-calculate the stack space required at compile time using static callgraph analysis.

This is not yet implemented.


Yes, but I'm talking about his assertion about that C example earlier in the talk, not about Zig. It's pretty fundamental to his arguments.


How would that work with: 1/ recursion; 2/ calling C code; 3/ being called from C?


Where are the generative capabilities? It seems there is no macro-like thing in Zig, though there are comptime parameters (and I guess: monomorphized templates).



Not nearly what you have in D.


There are too many cool languages and too few weekends. It's becoming more problematic, but at the same time it would seem impractical to optimize my life around learning every single thing that I want to learn.

How do I decide which things are important enough? I am running out of time in my life also.

I watched a video a while ago about how the universe is expanding faster than light can travel, which means the portion of the universe that we can observe is an increasingly small subset of what's out there.

Sometimes my hobbies and wish-i-had-time-for-that projects feel the same way, they're expanding faster than I'll ever catch up to.

I wonder if zig will ever pursue some sort of memory safety. I find rust very difficult and unwieldy, but I can totally grok the appeal of RAII.


If Zig (or some other language) turns out to be the next big thing, we'll be hearing about it again and again.


> I wonder if zig will ever pursue some sort of memory safety.

That's exactly what Zig is designed for [1].

Andrew Kelley (andrewrk) discusses this in the talk. Zig is similar to Rust, but with memory safety designed into the core, not bolted on as an afterthought. And as the SHA-256 demo tests in the talk show, Zig is as fast or faster than C.

[1] http://ziglang.org

[2] https://github.com/ziglang/zig


I don't understand. Rust has memory safety as a core design principle. Zig has manual memory allocation like C.


Zig is memory-safe if you keep runtime checks enabled (e.g. with debug or release-safe optimization levels) but it does not have the compile-time guarantees of Rust. I don't think the parent comment is a fair reflection.

That being said, some other potentially interesting safety aspects that are present (or are being explored) to give some idea of the target audience:

  - compile-time alignment checks [1]
  - maximum compile-time stack usage/bounding [2]
I would expect safety at the end of the day in Zig will be more similar to modern C++ and its smart pointers, alongside (optional) runtime checks, than a full lifetime system. Will have to see what the future holds.

[1] https://ziglang.org/documentation/master/#Alignment

[2] https://github.com/ziglang/zig/issues/1006


I may be wrong, but if I read the documentation correctly, Rust looks more memory-safe than Zig.

What am I missing?


Rust has lifetimes and ownership as language concepts so that you have to be explicit about who owns a resource, for example memory, but it does not give you a lot of control about what to do when an allocation fails.

Zig is designed so that you can still peogrammatically deal with a failing allocation, as does well-written C, but it does not have a ownership system like Rust.


Rust’s standard library types do not give you that control. The language doesn’t know abou allocation, and you can build whatever you want on top of it.


#-#-# for Steve only

"you can build whatever want on top of it" is a flashy way to say that "rust can't do that" in this instance.

I suspect rust adoption would be much more effective if it wasn't presented as a panacea-like solution (it isnt) that the entirety of computer science has always dreamed of (it hasn't) and is ready to be used to write literally everything on earth in.

The hubris of the rust team empowers projects like zig, which are clearly communicating what their offering can and cannot do.

You're influential in the rust microcosm, why not communicate this? Why not build a campaign directly from the utility rust is actually presenting and not the pseudo-philosophical utility that mozilla erroneously assigns to rust? For a team that hacks bits and registers all day, I'm shocked how far rust evangelism has deviated from just the simple truth of the code.

I wanted to share this with you privately, but I couldn't find you on any platform I feel comfortable using, and for some reason the best tech news aggregator in the world still doesn't do privmsg yet, so I hope I can ask you to take the perspective if what I'm trying to communicate here and not the "why you talking so loud?!" perspective.

I see these little rust-isms where a defect or some aspect of rust is manipulated in a way as to empower it rather than illustrate the technical reality of it.

Rust not having the ability to deal with failed allocation is or will be a show stopper for somebody, somewhere, eventually. It should be portrayed as such (or fixed and loudly announced) rather than saying, "oh, build whatever you want on top of that behavior" as if it's some brilliant idea.

Disclaimer: I'm not a rust contributor.

Disdisclaimer: there's love in my heart for rust but the marketing is reaching counterintelligence levels of euphemism and misdirextion and I'm confident that you have the influence and awareness to begin to fix this in the rustverse


> "you can build whatever want on top of it" is a flashy way to say that "rust can't do that" in this instance.

It most definitely is not. "It" in "on top of it" refers to the language itself and the "core" subset of "std", which designed to cater for situations without any OS-level allocation at all, not the "std"s library's handling of OOM.

Given that Rust can work without allocation, it can work with custom allocation-failure handling too: as a minimal proof, start with core only (no std) and create custom Box and Vec types (this probably isn't the best path, but it shows it is possible).

> I suspect rust adoption would be much more effective if it wasn't presented as a panacea-like solution (it isnt) that the entirety of computer science has always dreamed of (it hasn't) and is ready to be used to write literally everything on earth in.

> The hubris of the rust team empowers projects like zig, which are clearly communicating what their offering can and cannot do.

The Rust team is good at clearly communicating the boundaries of Rust. Others may misinterpret that/be over-enthusiastic, but they're often called out (even by members of the Rust team) when it is noticed.


Yes, thank you. To your parent, I know you said this was for me only, but this is what I’d say.

Well, with one more addition; you can see me doing exactly what you say in this very thread: https://news.ycombinator.com/item?id=17187648

I quite often tell people that Rust is not a panacea, or to not use it if it doesn’t fit their use-case: https://www.reddit.com/r/cpp/comments/8mp7in/comment/dzr3eot


Ok, got it. Apparently, espeed was using an unusual definition of "memory safety", which made their comment a bit odd :)


What was unusual about that? Here is Wikipedia: "Memory safety is the state of being protected from various software bugs and security vulnerabilities when dealing with memory access, such as buffer overflows and dangling pointers.[1] For example, Java is said to be memory-safe because its runtime error detection checks array bounds and pointer dereferences."

This is the mainstream definition. Do you think Zig doesn't have this, or Rust has more of this than Zig?

What Rust does indeed have is freedom from race conditions, which can be viewed as a concurrent memory safety property. And what Rust also has is static checking of one of the above properties, namely dangling pointers (but not the other, namely buffer overflows). Do you think that if it's not statically checked, it's not safe? Because if you think that, you are wrong.


I was referring to espeed's "Zig is similar to Rust, but with memory safety designed into the core, not bolted on as an afterthought", which he later explained with two examples, one of which actually justifies the words "bolted on as an afterthought", but refers to local handling of fallible allocation.

While local handling of fallible allocation is a desirable feature for many applications, I confirm that neither me nor Wikipedia had never heard it classified as "memory safety" :)


> I agree that that aspect isn't relevant to what I would call memory safe. But how do you get from "espeed claimed something unreasonable" to "Rust looks more memory-safe than Zig"?

Well, there are several reasons. For instance:

- Rust tries very hard to prevent accidental mutations through aliases. In all the common cases and most of the uncommon ones, it works quite well.

- Rust ensures that your memory can't be deallocated twice and that you can't dereference a dangling pointer.

- Rust ensures that any object that needs to be accessed from several threads is either read-only or somehow protected.

- Rust ensures that a context may not deallocate an object it does not own, nor mistakenly believe that it still has ownership of an object after it has transmitted this ownership to another context.

At this (early) stage in the development of Zig, I have the impression that these aspects of memory safety do not exist in Zig, hence my comment.

Now, I realize that Zig has at least one memory-safety check that `unsafe` Rust does not have, so it is entirely possible that both languages concentrate on different aspects of memory safety and/or different implementation techniques.

I also believe that Zig is interesting and has lots of potential. But I suspect that Zig needs to grow a little before anyone can claim that Zig is more memory-safe than Rust (which is what I understand from the comment I was answering).


I hate to say it, but this might be an instance where more jargon might help.

"Memory safe" can have numerous implications because there are numerous methods for attacking memory of a program. As far as we know, there's no language that handles EVERYTHING, so maybe we need to start qualifying what these languages mean. Rust landing page does this well, "no race conditions, move semantics" but then rust users will parrot "it's memory safe, it's memory safe" and they can't explain how to deal with failed allocations in rust. Not that that's necessarily a big deal, but it's just bad terminology for what is a rather complex issue.

It's almost like saying a language is "type safe". While reassuring, wtf does that actually mean? What kind of types? Is there polymorphism? Is there row types? Is there substructural typing? Type theorists generally have lots of terms to specify what flavor of types they're talking about, and that works well for them.

Rust is kind of bringing a new generation of younger hackers to systems programming and you darned kids need to get off the grass and start being more specific about memory semantics in PLT! /s but kinda serious


I agree that that aspect isn't relevant to what I would call memory safe. But how do you get from "espeed claimed something unreasonable" to "Rust looks more memory-safe than Zig"?


Rust does no have freedom from race conditions. It does have freedom from data races. They’re different things.


There was a HN discussion ~4 months ago that touched on some of this:

"Unsafe Zig Is Safer Than Unsafe Rust" https://news.ycombinator.com/item?id=16226235

Also see this previous thread on Rust's stdlib OutOfMemory errors:

"Containers should provide some way to not panic on failed allocations" https://github.com/rust-lang/rust/issues/29802


> "Unsafe Zig Is Safer Than Unsafe Rust"

Yes, I've seen that blog post. Definitely a useful analysis, but it doesn't strike me as a fundamental difference, i.e. adding this check (as a warning) to rustc doesn't look too hard and wouldn't break existing code.

Also, as pointed out in the title, that's code explicitly marked as `unsafe` in Rust, so it might not be an entirely fair comparison :)

> "Containers should provide some way to not panic on failed allocations" https://github.com/rust-lang/rust/issues/29802

Definitely a problem, but it's not what people usually call "memory safety". The problem you mention is that of "fallible allocation". A typical definition of "memory safety" is that you can never get a memory access error.

Rust's behavior is indeed to panic in case of failed allocation, letting the developers catch the error at higher level. Barring any bug in rustc, that behavior is memory-safe.

Now, this behavior is quite opinionated and it turns out that it doesn't match all applications, so handling failed allocations is indeed being "bolted on as an afterthought". Just not memory safety :)

(especially since it's pretty hard to get a memory access error in Rust without deactivating safety checks, and it doesn't seem to be nearly as difficult in the current version of Zig)


> i.e. adding this check (as a warning) to rustc doesn't look too hard and wouldn't break existing code.

One point of distinction: Zig does not have compile-time warnings, only errors. Simplicity by design. Either it will complie or it won't. Removing the uncertainty here means less greyarea, no space for edgecases in between.


I realize that this should be an error rather than a warning. But one of the policies of Rust since 1.0 is to not break existing code, so any mechanism that detects error that were not previously detected must make it a warning, rather than an error, otherwise some existing applications and libraries would stop compiling overnight.

I generally find this a sane policy, but YMMV.


OT FYI: I just looked at your profile and noticed we have something in common. Your HND matches my DOB ~> 224


Just to clarify, allocation is entirely implemented in the Rust standard library, not the language/compiler.


> And as the SHA-256 demo tests in the talk show, Zig is as fast or faster than C.

Do you know at what timestamp (roughly) this is mentioned in the video? YouTube makes it hard to quickly skip around in the video to find this. Or, better, a link to a written source? I'd be interested in what exactly is being compared. It's easy to cheat on benchmarks, especially when you aren't doing it on purpose.


Right at about the 20m mark: https://youtu.be/Z4oYSByyRak?t=20m2s


Thanks!

Grabbing the sources from https://www.nayuki.io/page/fast-sha2-hashes-in-x86-assembly and compiling them more or less as recommended (and unlike they are compiled in the talk):

    $ clang -O3 sha256-test.c sha256.c -o sha256-test ; for i in 1 2 3; do ./sha256-test ; done
    Self-check passed
    Speed: 197.2 MB/s
    Self-check passed
    Speed: 196.1 MB/s
    Self-check passed
    Speed: 196.1 MB/s
    $ gcc -O3 sha256-test.c sha256.c -o sha256-test ; for i in 1 2 3; do ./sha256-test ; done
    Self-check passed
    Speed: 209.7 MB/s
    Self-check passed
    Speed: 209.2 MB/s
    Self-check passed
    Speed: 209.1 MB/s
This is a baseline for us. It has nothing to do with Zig and nothing to do with Andrew's machine (hardware or compiler versions).

But wait, the page above suggests that adding -march=native might help. Indeed it does:

    $ clang -O3 -march=native sha256-test.c sha256.c -o sha256-test ; for i in 1 2 3; do ./sha256-test ; done
    Self-check passed
    Speed: 255.6 MB/s
    Self-check passed
    Speed: 259.0 MB/s
    Self-check passed
    Speed: 254.3 MB/s
    $ gcc -O3 -march=native sha256-test.c sha256.c -o sha256-test ; for i in 1 2 3; do ./sha256-test ; done
    Self-check passed
    Speed: 275.1 MB/s
    Self-check passed
    Speed: 268.4 MB/s
    Self-check passed
    Speed: 270.0 MB/s
In the talk Andrew suggests that the difference might be due to using rorx instructions, which Zig might be able to do due to aggressive loop unrolling. Does -funroll-all-loops help GCC? It turns out that it doesn't, and that it cannot, on this program, because the C code for sha256_compress is already fully unrolled.

But anyway, are we using rorx instructions at all? We are, but only with -march=native:

    $ clang -O3 -S sha256.c -o - | grep -c rorx
    0
    $ clang -O3 -march=native -S sha256.c -o - | grep -c rorx
    542
And:

    $ gcc -O3 -S sha256.c -o - | grep -c rorx
    0
    $ gcc -O3 -march=native -S sha256.c -o - | grep -c rorx
    576
[Edit: Changed the grep from "ror" to "rorx", which changed GCC's numbers a bit; it does generate "ror" without x without -march=native.]

So. Testable theories:

(a) On Andrew's machine, the same setup but with Clang using -march=native would outperform or at least match Zig.

(b) Zig's compiler internally uses the equivalent of -march=native, possibly implicitly, at least in --release-fast mode.

Nothing here is meant to imply that Andrew is dishonest. There are just lots of variables to take into account, and sometimes we don't. Also, "slower/faster than C" by a few percent is not very meaningful if even C compilers disagree by 6% or so, and the same C compiler with the right flags disagrees with itself by a lot more.


Personal taste, not factual One-True-Way™

It looks on the surface like almost Rust meets JS with a sprinkling of Ruby/Smalltalk.

Semicolons in 2018, really? Indentation and line-oriented parsing makes code more beautiful. Heck, in general, enums, arrays and dictionary literals shouldn’t even need commas if there’s one tuple per line. Extra typing is a waste of time and clutters-up code with distracting, Christmas ornament “blink tags” that go “Ho, ho, ho” when anyone walks by.

    # package name is the same as the directory path
    # module name is the same as the filename sans extension
    # big modules can be broken up into separate include files

    con X: int = 6   # constant

    var M: int = 17  # module-public variable

    mix any          # module-private mixin
        λ   any? -> bool
            each {x| if (Block? ? yield(x) : x): return true}
            false

    ext []: mix any  # module-private type extension

    typ T: int[10][8]
        λ Zero? -> bool: any? {x| x.any? {y| y == 0}}

    typ S
        a, b: int
        x, y: float
        s[7..6], t[5], u[4..2], _[1], v[0]: byte

        λ   Good?  -> bool: xGood? & yGood?   # no & / && distinction, precedence by expression type
        λ   xGood? -> bool: x > 0
        λ   yGood? -> bool: y > 0

    uni Q        # union
        I: int
        F: float

    λ   thisIsPrivate(x: int) -> int
        x + M + 1

    λ   ThisIsPublic(x, y: float) -> float
        π * (x + y - X)


That's just your personal opinion, obviously. You need some sort of separators for putting several statements on the same line anyway, and requiring them everywhere is better than Javascript's or python's optional semicolons. Also I guess zig's main audience is C programmers, and semicolons are not one of the problems that need fixing in a "better C" language.




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

Search: