
Zig: programming language designed for robustness optimality and clarity [video] - espeed
https://www.youtube.com/watch?v=Z4oYSByyRak
======
donpdonp
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

~~~
MaxBarraclough
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?

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

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

~~~
Yoric
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

------
Yoric
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...](https://andrewkelley.me/post/unsafe-zig-safer-than-unsafe-rust.html),
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 :)

~~~
tiehuis
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](https://github.com/ziglang/zig/issues/130)

[2]
[https://github.com/ziglang/zig/blob/15302e84a45a04cfe94a8842...](https://github.com/ziglang/zig/blob/15302e84a45a04cfe94a8842318f02a608055962/std/mem.zig#L8)

[3]
[https://github.com/ziglang/zig/blob/15302e84a45a04cfe94a8842...](https://github.com/ziglang/zig/blob/15302e84a45a04cfe94a8842318f02a608055962/std/heap.zig#L41)

[4]
[https://github.com/ziglang/zig/blob/15302e84a45a04cfe94a8842...](https://github.com/ziglang/zig/blob/15302e84a45a04cfe94a8842318f02a608055962/test/cases/coroutines.zig)

~~~
Yoric
> 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!

~~~
tiehuis
> 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](https://ziglang.org/documentation/master/#union)

~~~
Yoric
> 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?

------
wgjordan
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.

------
gavanwoolery
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

~~~
Yoric
> \- No hidden control flow

That means no destructors, right?

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

~~~
iainmerrick
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.

~~~
MaxBarraclough
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](https://ziglang.org/documentation/master/#defer)
[1] [https://ziglang.org/download/0.1.1/release-
notes.html](https://ziglang.org/download/0.1.1/release-notes.html) [2]
[https://andrewkelley.me/post/intro-to-
zig.html](https://andrewkelley.me/post/intro-to-zig.html) [3]
[https://dlang.org/articles/lazy-
evaluation.html](https://dlang.org/articles/lazy-evaluation.html) [4]
[https://docs.microsoft.com/en-us/dotnet/csharp/language-
refe...](https://docs.microsoft.com/en-us/dotnet/csharp/language-
reference/keywords/ref#passing-an-argument-by-reference)

------
audunw
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.

------
tomsmeding
> 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](https://news.ycombinator.com/item?id=17181046))

~~~
qop
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.

------
jokoon
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.

~~~
Yoric
I just clicked on "documentation" and found examples:

[https://ziglang.org/documentation/master/](https://ziglang.org/documentation/master/)

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

------
jokoon
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

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

[https://nim-lang.org](https://nim-lang.org)

~~~
klibertp
> 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.

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

------
white-flame
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.

~~~
janvidar
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.

~~~
white-flame
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.

------
p0nce
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).

~~~
AndyKelley
[https://ziglang.org/documentation/master/#Case-Study-
printf-...](https://ziglang.org/documentation/master/#Case-Study-printf-in-
Zig)

~~~
p0nce
Not nearly what you have in D.

------
qop
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.

~~~
espeed
> 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](http://ziglang.org)

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

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

What am I missing?

~~~
mschwaig
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.

~~~
steveklabnik
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.

~~~
qop
#-#-# 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

~~~
dbaupp
_> "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.

~~~
steveklabnik
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](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](https://www.reddit.com/r/cpp/comments/8mp7in/comment/dzr3eot)

------
himom
_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)

~~~
flohofwoe
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.

