Hacker Newsnew | past | comments | ask | show | jobs | submit | pyrolistical's commentslogin

It’s just both using c abi right?

Yeah, this isn't quite C++ interop on its own. It's C++ interop via C, which is an incredibly pertinent qualifier. Since we go through C, opaque pointers are needed for everything, we can't stack allocate C++ values, we need to write extern C wrappers for everything we want to do (like calling member fns), and we don't get any compile-time type/safety checking, due to the opaque pointers.

Direct C++ interop is doable, by embedding Clang into Zig and using its AST, but this is significantly more work and it needs to be done in the Zig compiler. As a Zig user, going through C is about as good as you can do, probably.


It's a bit more than your typical "interop via C". With a "sized opaque" type you actually can stack allocate C++ values in Zig (and vice versa stack allocate Zig values in C++), i.e.

fn stackExample() void {

    var some_cpp_type: c.SomeCppType = undefined;
    c.some_cpp_type_ctor(&some_cpp_type);
    defer c.some_cpp_type_dtor(&some_cpp_type);

    // ...

}

Seems like it. And the sizes are all hard-coded, which means you are probably wedded very tightly to a particular C++ compiler.

Check out dawn. It’s what chrome implemented for webgpu. It uses the available hardware/api for the local hardware.

So while most systems will be using Vulkan, macOS will be using metal.


Windows will most likely be using DirectX. Much to people's surprise, Vulkan is not in fact, available everywhere. Further, there is integrating other Windows features (DirectVideo, DirectComposite, etc...)

When I first heard about zig, I sat down and read the entire doc from start to end. It was so refreshing and was an instant fan

It doesn't seem bad, (though with some of rewrite of the std library? It doesn't seem quite ready) Im just not sure when I'll reach for it. I guess I'm comfortable enough with rust and both seem like languages that compile with llvm. Maybe if I didn't know rust it would feel like more useful.

But they are not even competitors.

Zig is a better C. No abstractions. Close to bare metal.

Rust is a better C++. Abstractions to simplify application level programming.


I hear this a lot, but there are many things for which you can use both, so they're competitors. Also most Rust developers want to use it everywhere, including areas where you normally reach out to C, so if people that like Rust want to use it for things that are usually made with C, and Zig is a replacement for C, it seems to me they're competitors, no?

The authors section on comptime makes me believe they have not used zig anything non trivial.

I don’t think comptime as just some macro system. It is closer to a reflection system. I use comptime to access types. You can create specialized paths depending on type.

Also imo generics are “first class” in zig. Comptime is first class and thus generics are too.


> X is first class and thus Y [implemented in terms of X] is too.

Is everything first class, then?


I would argue C preprocessor macros are not first class. They are optional.

This anything implements with macros are not first class


In theory it can encode 0 and 1 so anything really, right?

It violates one of zigs principle of no hidden control flow

If exceptions count as hidden control flow because suddenly a function call can jump to a `catch` block, then defer absolutely counts as hidden control flow because suddenly the end of your function jumps to a `defer` block defined at an arbitrary point higher up. If Zig wanted to be consistent with its own philosophy, it would require some sort of keyword at the end of every scope to indicate that a defer was happening (and require multiple uses of the keyword to indicate multiple defers).

It's not the same, defer does not conditionally interrupt the flow of execution, it will always run at the end of a block. If I see a defer, I am absolutely certain that whatever is deferred will run. The same is not true for exceptions. If I am not mistaken defer just moves the code at the end of the block, no jumping is involved.

> It's not the same, defer does not conditionally interrupt the flow of execution, it will always run at the end of a block.

But destructors also don't conditionally interrupt the flow of execution, and always run at the end of a block.

> If I see a defer

The point is that you're not seeing it. In order to know if there's a defer happening at the end of a function you can't just read the end of the function, you need to read the entire function. That's non-local reasoning, which is what Zig professes to abhor.

And in fact defer is worse than destructors here, because a destructor runs the exact same code every time, whereas defer allows arbitrary code, so you need to review every single usage. And you also need to remember not to forget to use it in the first place (which is the classic footgun with defer), so in addition to vetting everywhere you use it, you also need to vet everywhere you might have forgotten to use it.


> But destructors also don't conditionally interrupt the flow of execution, and always run at the end of a block

Why bring up destructors, I was talking about exceptions. Destructors and exceptions are orthogonal concepts, one can be implemented independently of the other. I'm specifically referring to try-catch blocks like those in java.

Compare this:

  try { foo(); }
  catch { bar(); } 
to this:

  defer bar();
  foo();
In the first one, bar() may or may not run depending if there's an exception. In the second one, bar is guaranteed to run. Thus, it means defer does not conditionally interrupt the flow of execution.

> The point is that you're not seeing it. In order to know if there's a defer happening at the end of a function you can't just read the end of the function, you need to read the entire function.

What? You don't need to read the entire function, you only to scan or check for defers scoped in a block, or in some cases, just the top of a function or block. Wanting to just read the end of a function is unreliable anyway with the existence of early returns (which defer fixes by the way).

You could have made a more compelling (but not necessarily a valid) case by citing zig's try-catch, which makes me think that you are just arguing in abstract without actually having tried writing code that uses defer, or any zig code for that matter.


I cite destructors specifically because Zig denounces destructors as "hidden control flow" while presenting defer as a non-hidden alternative to destructors, which I find to be an incoherent philosophy.

But you were originally talking about exceptions. As I said, destructors and exceptions are distinct concepts, you can't just suddenly interchange those two terms as that would invalidate my whole disagreement, unless you wanted to shift the goal posts a bit to the side.

Honestly, I don't know if I'd argue on grounds of no hidden control flow, but rather on grounds of fine-grained deinitialization. If I allocate a bunch of data structures in an arena, I don't want their destructors to be run, because I'm managing the memory a different way now. I suppose in Rust you could use `Box::leak` to prevent a destructor from running, but that's not exactly ergonomic (plus iirc you can't free a 'static reference safely). Especially if you've needed to tamper with a data structure's internals, you can't use the normal deinit functions.

The main reason why C is used everywhere is because the C ABI is the de facto ABI.

This is why Zig was smart to have first party support for C libs.

In fact the fact Zig pointer types are more precise, it allows imported C libs to be more correct than the original C.


I can see pros and cons. Preventing data being attached to an error forces more clear and precise errors.

Whereas lazy devs could just attach all possible data in a giant generic error if they don’t want to think about it.


> Preventing data being attached to an error forces more clear and precise errors.

Okay maybe theorically, but in the real world I would like to have the filename on a "file not found", an address on a "connection timeout", a retry count on a "too many failures", etc.


But also in the real world I may not be interested in any error information for the library I’m using. I’d like to be able to pass a null for the error information structure and have the compile optimize away everything related to tracking and storing error information.

I’d like my parser library to be able to give me the exact file, line and column number an error occurred. But I’d also like to use the library in a “just give me an error if something failed, I don’t really care why” mode.


I don't follow, because there's a possibility that someone somewhere might create a bad overly-generic error set if they were allowed to stuff details in the payload when those should be reflected in the error "type", it's a good idea to make the vast majority of error reporting bad and overly-generic by eliminating error payloads entirely?

Id rather have data in a generic error type than no data in a specific error type.

How useful is a file not found error type without data (the filename) when the program is looking for 50 files? Not very.

How useful is a generic error type with '{filename} not found' as generic string data packed in? Quite.


I totally vibe with the intro but then the rest of the article goes on to be a showcase bits of zig.

I feel what is missing is how each feature is so cool compared to other languages.

As a language nerd zig syntax is just so cool. It doesn’t feel the need to adhere to any conventions and seems to solve the problems in the most direct and simple way.

An example of this declaring a label and referring to a label. By moving the colon to either end it makes labels instantly understood which form it is.

And then there is the runtime promises such as no hidden control flow. There are no magical @decorators or destructors. Instead we have explicit control flow like defer.

Finally there is comptime. No need to learn another macro syntax. It’s just more zig during compilation


matklad did it justice in his post here, in my opinion

https://matklad.github.io/2025/08/09/zigs-lovely-syntax.html



I was also curious what direction the article was going to take. The showcase is cool, and the features you mentioned are cool. But for me, Zig is cool is because all the pieces simply fit together with essentially no redundancy or overloading. You learn the constructs and they just compose as you expect. There's one feature I'd personally like added, but there's nothing actually _missing_. Coding in it quickly felt like using a tool I'd used for years, and that's special.

Zig's big feature imo is just the relative absence of warts in the core language. I really don't know how to communicate that in an article. You kind of just have to build something in it.


> Coding in it quickly felt like using a tool I'd used for years, and that's special.

That's been my exact experience too. I was surprised how fast I felt confident in writing zig code. I only started using it a month ago, and already I've made it to 5000 lines in a custom tcl interpreter. It just gets out of the way of me expressing the code I want to write, which is an incredible feeling. Want to focus on fitting data structures on L1 cache? Go ahead. Want to automatically generate lookup tables from an enum? 20 lines of understandable comptime. Want to use tagged pointers? Using "align(128)" ensures your pointers are aligned so you can pack enough bits in.


Having spend a year tinkering in zig and it's absence of features has made me want to drop c#/java professionally and pick up Golang. Its quiet annoying when you see a codebases written in C#/java and you can tell in which year/era it was written because of the language features. The way of writing things in C# changes like every 4 years or so.

There's a certain beauty in only having to know 1~2 loops/iteration concepts compared to 4~5 in modern multi paradigm languages(various forms of loops, multiple shapes of LINQ, the functional stuff etc).


You already have have Go, before and after modules, before and after generics, before and after ranges over function types.

Skipping other minor changes.

However I do agree C# is adding too much stuff, the team seems trying to justify their existence.


Thats true with Go but it feels those features are more rare. Compared to say what feels like almost yearly changes in C# to the point a lot of people think its overwhelming and it usually not clear how much of those features really add to the language and dev experience.

My experience with Golang so far is biased because i only recently looked at golang, for the past decade i have been working mostly in java and c#, so most of those newly added features in golang is stuff i'm already deeply familiar with conceptually.


Yeah, the real strength of Zig isn't what's there, but what isn't.

OK, but this is true for most younger languages. The older they get, the more they tend to accumulate.

Check back in on Zig after another decade.


out of curiosity, what feature do you want?

The feature I want is multimethods -- function overloading based on the runtime (not compile time) type of all the arguments.

Programming with it is magical, and its a huge drag to go back to languages without it. Just so much better than common OOP that depends only on the type of one special argument (self, this etc).

Common Lisp has had it forever, and Dylan transferred that to a language with more conventional syntax -- but is very near to dead now, certainly hasn't snowballed.

On the other hand Julia does it very well and seems to be gaining a lot of traction as a very high performance but very expressive and safe language.


I think this is a major mistake for Zig's target adoption market - low level programmers trying to use a better C.

Julia is phenomenally great for solo/small projects, but as soon as you have complex dependencies that _you_ can't update - all the overloading makes it an absolute nightmare to debug.


For what it's worth, that hasn't been my experience with Julia – I've found it easier to debug than Python, Scala, or Clojure (other languages I've used at jobs.)

The tooling makes it easy to tell which version of a method you're using, though that's rarely an issue in practice. And the fact that methods are open to extension makes it really easy to fix occasional upstream bugs where the equivalent has to wait for a library maintainer in Python.

500kloc Julia over 4 years, so not a huge codebase, but not trivial either.


Ada has them, and I guess we all agree on its systems programming nature.

NOOOO!

What Ada (and Rust) calls generics is very different -- it is like template functions in C++.

In those languages the version of the function that is selected is based on the declared type of the arguments.

In CLOS, Dylan, Julia the version of the function that is selected is based on the runtime type of the actual arguments.

Here's an example in Dylan that you can't do in Ada / Rust / C++ / Java.

    define method fib(n) fib(n-1) + fib(n-2) end;
    define method fib(n == 0) 0 end;
    define method fib(n == 1) 1 end;
The `n == 1` is actually syntactic sugar for the type declaration `n :: singleton(1)`.

The Julia version is slightly more complex.

    fib(n) = fib(Val(n))
    fib(::Val{n}) where {n} = fib(n-1) + fib(n-2)
    fib(::Val{0}) = 0
    fib(::Val{1}) = 1
    
    println(fib(30))
This is perhaps a crazy way to write `fib()` instead of a conventional `if/then/else` or `?:` or switch with a default case, but kinda fun :-)

This of course is just a function with a single argument, but you can do the same thing across multiple arguments.

    define method ack(m, n) ack(m-1, ack(m, n-1)) end;
    define method ack(m == 0, n) n+1 end;
    define method ack(m, n == 0) ack(m-1, 1) end;

You missed the way Ada does OOP, and went completely overboard talking about generics.

As you can see from my comment history, I am quite aware of CLOS, Lisp variants and Dylan.


Last I checked, Ada does not have multimethods/generic functions in the sense of CLOS, Dylan and Julia. It has static function overloading, and single-argument dispatch, just like C++.

>The feature I want is multimethods -- function overloading based on the runtime (not compile time) type of all the arguments.

>Programming with it is magical, and its a huge drag to go back to languages without it. Just so much better than common OOP that depends only on the type of one special argument (self, this etc).

Can you give one or two examples? And why is programming with it magical?


For a start it means you can much more naturally define arithmetic operators for a variety of built in and user-defined types, and this can all be done with libraries not the core language.

Because methods aren't "inside" objects, but just look like functions taking (references to) structs, you can add your own methods to someone else's types.

It's really hard to give a concise example that doesn't look artificial, because it's really a feature for large code bases.

Here's a tutorial example for Julia

https://scientificcoder.com/the-art-of-multiple-dispatch


Thanks.

Erlang/Elixir also has that

Something akin to interfaces, but weaker. Right now people roll their own vtables or similar, and that's fine...I actually don't expect these to be added. But because of Zig's commitment to "everything structural is a struct", a very very simple interface type would likely end up being used more like ML's modules.

The need for this jumped out at me during Writergate. People had alot of trouble understanding exactly how all the pieces fit together, and there was no good place to document that. The documentation (or the code people went to to understand it) was always on an implementation. Having an interface would have given Zig a place to hang the Reader/Writer documentation and allowed a quick way for people to understand the expectations it places on implementations without further complications.

For Zig, I don't even want it to automatically handle the vtable like other languages...I'm comfortable with the way people implement different kinds of dynamic dispatch now. All I want is a type-level construct that describes what fields/functions a struct has and nothing else. No effect on runtime data or automatic upcasting or anything. Just a way to say "if this looks like this, it can be considered this type."

I expect the argument is that it's unnecessary. Technically, it is. But Zig's biggest weakness compared to other languages is that all the abstractions have to be in the programmer's head rather than encoded in the program. This greatly hampers people's ability to jump into a new codebase and help themselves. IMO this is all that's needed to remedy that without complicating everything.

You can see how much organizational power this has by looking at the docs for Go's standard library. Ignore how Go's runtime does all the work for you...think more about how it helps make the _intent_ behind the code clear.


Just means the browsers can catch up.

Initially slower but then faster after full compilation


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

Search: