[HN Gopher] Problems of C, and how Zig addresses them
       ___________________________________________________________________
        
       Problems of C, and how Zig addresses them
        
       Author : synergy20
       Score  : 164 points
       Date   : 2023-07-03 13:33 UTC (9 hours ago)
        
 (HTM) web link (avestura.dev)
 (TXT) w3m dump (avestura.dev)
        
       | pjmlp wrote:
       | Naturally it doesn't show how it tackles use-after-free cases.
        
         | bornfreddy wrote:
         | Yup. It offers tests as a solution, which is nice I guess, but
         | kind if reminds me of Valgrind.
         | 
         | Another thing that caught me by surprise is passing arrays as
         | references. Ages ago we used to pass arrays (`int arr[100]`) as
         | pointers (`int*`). I'm sure C still supports this?
        
           | patrakov wrote:
           | Just wondering - how well does Valgrind work on Zig projects?
        
             | AndyKelley wrote:
             | Exceptionally well. Zig by default outputs valgrind client
             | requests to annotate undefined memory. This makes Valgrind
             | even more an effective tool for zig code than for C code.
        
         | gorjusborg wrote:
         | Rather than offer drive-by criticism, perhaps you could
         | illustrate 'the problem' you are suggesting is being hidden?
        
           | pjmlp wrote:
           | The problem is that Zig is basically Modula-2 for C syntax
           | lovers, plus metaprogramming.
           | 
           | It is hardly safer than Modula-2 already was over C already
           | in 1978.
           | 
           | We are in 2023 now.
        
             | renox wrote:
             | You're right, but it doesn't matter: Modula-2 didn't catch
             | up, Zig has a small chance to succeed thanks to its focus
             | on embedded and on its good tooling for cross-compilation.
        
           | tmtvl wrote:
           | The problem I believe is being discussed is that
           | dereferencing a pointer after the memory it references is
           | freed does not throw a warning at compile time. This means
           | the programmer has to manually keep track of pointers, making
           | sure not to free the relevant memory until there are no more
           | references to it lingering about. While there are programming
           | techniques which to lesser or greater extent prevent problems
           | from occurring, e.g. by allocating all memory needed at the
           | begin of the program and freeing all of it at the end, doing
           | no (de-)allocation while the program is running; such
           | solutions still require some discipline on part of the
           | programmer and for some it is a tall ask.
        
             | k__ wrote:
             | So, Zig set out to solve all but the biggest issue with C?
        
               | tmtvl wrote:
               | That seems to be what OP is implying. I haven't used Zig,
               | so I can't say. I suppose people who want to avoid memory
               | management related bugs who can't acccept a GC language
               | can use Carp.
        
               | flohofwoe wrote:
               | The biggest issue with C is arguably buffer overflows,
               | not use-after-free (Zig offers spatial but not temporal
               | runtime memory safety).
               | 
               | If you want both, but don't want a Rust-style borrow
               | checker (with all the restrictions this entails), use
               | 'tagged-index-handles', those work just fine across all
               | languages (and even make sense in Rust):
               | 
               | https://floooh.github.io/2018/06/17/handles-vs-
               | pointers.html
        
           | dagmx wrote:
           | Isn't that a pretty clear, if terse, description of the
           | problem?
           | 
           | Use-after-free is a well known problem in the memory managed
           | programming space and one that Zig infamously does not
           | tackle.
           | 
           | For those who aren't familiar, it's the use of a resource (a
           | pointer) after it is no longer available for use (or has been
           | freed). Which can in turn result in accidentally accessing
           | unexpected data or crashing.
        
       | matrix_overload wrote:
       | It is nice and innovative, no doubt. But reinventing the syntax
       | from scratch instead of introducing just the minimal amount of
       | changes to the original C is a barrier that will deter 90% of
       | possible adopters.
       | 
       | The reason C# took off is that it's as close to C/C++ as
       | possible. If there is a difference, it's due to a fundamental
       | semantic change. E.g. it moved from painstakingly reinterpreting
       | hundreds of small header files for each compiled source file to
       | loading efficiently serialized hierarchies of class definitions.
       | Hence #include got replaced with using. It changed the semantics
       | of pointers vs. references, hence 'ref' instead of '*', and so
       | on. But there is no "fn square(x: u32)" instead of "uint32_t
       | square(uint32_t x)" just for the sake of it.
       | 
       | And that's the main reason why many people will never consider
       | even looking into the advantages Zig offers. Keeping another
       | syntax in your head is just not worth it.
        
         | flohofwoe wrote:
         | IIRC even K&R Second Edition acknowledges that the C type
         | syntax is awkward for non-trivial cases.
         | 
         | The "new" style used by Go, Rust, Zig, Typescript etc... is a
         | lot easier to read because it always 'resolves' from left to
         | right.
        
         | Joker_vD wrote:
         | Counterpoint: C itself invented lot of syntax compared to the
         | contemporary ALGOLs and PL/I. Yet, it became immensely popular.
        
           | matrix_overload wrote:
           | I would dare say, different combination of early
           | adopters/pragmatists in your target audience. C/C++ these
           | days is mostly legacy stuff, so if you want to cater to that
           | audience, you deliver small incremental improvements.
           | 
           | Many people that could be bothered to learn Zig syntax,
           | jumped ship to Python/Java/whatever already.
        
             | gpderetta wrote:
             | Legacy stuff like the compiler and runtime used by the new
             | hip languages or the webserver, browser, database,
             | libraries needed to run those programs, or the OS,
             | hypervisor, device drivers needed by the machine those
             | programming run on.
        
             | Night_Thastus wrote:
             | Zig and Python/Java are completely different beasts. One is
             | a low-level systems language on the order of Rust or C. The
             | other two are much higher level, easier to work with
             | languages more attuned to desktop, mobile applications and
             | enterprise work.
             | 
             | I don't think anyone is seriously "jumping ship" from Zig
             | to those.
        
               | matrix_overload wrote:
               | Low-level programming isn't the holy grail. If a person
               | gets fed up with limitations of C, they might as well
               | move to a higher-level domain area as well. Especially,
               | given the higher pay there.
               | 
               | Those who haven't will have a much lower tolerance for
               | changes. Survivor bias of a kind.
        
               | Night_Thastus wrote:
               | It's not really a choice of low-level or high-level.
               | Neither is better than the other.
               | 
               | It's about choosing the right language for the task at
               | hand. Some work will really be suited (or only be
               | feasible) with a low-level language and vice-versa.
               | 
               | If I'm writing a command-line tool on the order of
               | ripgrep or working on a microcontroller embedded in a
               | dishwasher, I'm not going to go to Java. That would be
               | weird and awkward. And if I'm writing a new 3D AAA-level
               | game, I'm going to jump to something maybe even higher
               | level like UE5 - trying to do all that in Rust would be a
               | PITA.
        
         | tialaramex wrote:
         | If you just keep C ideas you might almost just as well give up
         | altogether.
         | 
         | C# actually only resembles C rather superficially,
         | idiomatically there's a large gap and of course you should
         | write idiomatic code, for example it's true you can write a
         | C-style for loop in C#, but you almost never should, C# has a
         | (not great, but it's something) for-each loop that's idiomatic.
         | 
         | C#'s primitive types look superficially like C types, but
         | behave more like the modern sized types from a language like
         | Rust. They're technically structures, albeit with a more
         | convenient alias keyword. For example "long" is a signed 64-bit
         | integer type, like i64, not some arbitrarily "maybe bigger than
         | int" type as it is in C.
         | 
         | 123.ToString() is a reasonable thing to write in C#, it means
         | "Call the ToString method on this integer 123" much like Rust's
         | 123.to_string() -- you can't do anything similar in C or even
         | in C++
        
           | matrix_overload wrote:
           | >for example it's true you can write a C-style for loop in
           | C#, but you almost never should, C# has a (not great, but
           | it's something) for-each loop that's idiomatic.
           | 
           | This makes the learning curve way easier. You can start
           | meaningfully using C# while using C-style for loops, and
           | eventually switch to 'foreach' once you realize it's better.
           | 
           | If I wanted to give Zig a try today by using it for some
           | small low-priority task, I would keep stumbling on these
           | minor syntax differences all the time, and would eventually
           | give up and do it in C/C++ because the overhead outweighs my
           | curiosity.
           | 
           | Most pragmatists don't have infinite time to learn a new
           | programming language for fun. They have a very limited amount
           | of attention and tight time constraints, and will move to the
           | next pragmatic solution if it starts looking like the current
           | one is not cutting it.
        
             | tialaramex wrote:
             | > I would keep stumbling on these minor syntax differences
             | all the time, and would eventually give up
             | 
             | I can't necessarily fault Zig for the state of this so
             | early in its lifetime, but - that should not be a big
             | problem, handling transition is work for the diagnostics.
             | 
             | Suppose I write this in Rust: printf("%d", count);
             | 
             | Rust says it can't find a function named printf, but it
             | suggests perhaps I want the print! macro instead?
             | 
             | OK, let's try again: print!("%d", count);
             | 
             | No, says Rust, % style format strings aren't a thing in
             | Rust, use {curly brackets}
             | 
             | Sure enough: print!("{count}"); // compiles and works.
        
         | Night_Thastus wrote:
         | I disagree, I argue that you've got it backwards.
         | 
         | C has a fair amount of kludge in its syntax. C cannot change
         | its syntax because it would massively break backwards
         | compatibility, which is bad.
         | 
         | New languages do not have this problem - they don't have to
         | worry about backwards compatibility. Because they have that
         | freedom, they should _always_ opt for what they believe is the
         | best possible syntax. I 'd say they're _obligated_ to do so.
         | Otherwise, we 're stuck with another 10-20+ years of dealing
         | with bad syntax, for no good reason!
         | 
         | If the opportunity for improvement is there, and it's nearly
         | close to free to do so, it should absolutely be taken.
        
         | xcv123 wrote:
         | > And that's the main reason why many people will never
         | consider even looking into the advantages Zig offers.
         | 
         | I think you got it upside down. Syntax is the least of its
         | problems. Any experienced developer has already learned several
         | languages and can pick up new syntax quickly. Zig syntax is
         | straightforward, similar to other languages, and can be learned
         | in a couple of hours. Easy stuff. The problems with Zig are
         | more related to uncertainty around long term support, the
         | network effect, and so on.
        
         | cellularmitosis wrote:
         | In practice it only takes a weekend or two to pick up the
         | syntax changes, and the benefits easily outweigh that
         | investment.
         | 
         | Heck, the simple fact that type signatures can be read
         | literally from left to right (instead of using the spiral rule)
         | is enough for me to switch. https://zig.news/toxi/typepointer-
         | cheatsheet-3ne2
        
         | TwentyPosts wrote:
         | Minor differences in syntax are not what makes programming (in
         | general, and when it comes to learning a new language) hard.
         | 
         | As an aside, I really like that I can grep source files for a
         | keyword such as `fn ` and get a good idea of how many functions
         | I am defining, and where they are. This gets especially
         | powerful with multi-cursor editing.
        
       | augustk wrote:
       | Does Zig have a formal grammar (like C)?
        
         | fallat wrote:
         | There's a grammar that tries its best to be maintained
         | alongside the language but it's nothing solidified yet.
        
       | travisgriggs wrote:
       | Tangentially... I'm grateful that these new batches of systems
       | languages have chosen to use the u8, i32, etc, style integer
       | types. Rather than the uint8_t and int32_t style. First thing I
       | do in any of my low level embedded C projects is put in the
       | various uNN/iNN types.
        
         | andikleen2 wrote:
         | Having spent a lot of time porting 32bit system code to 64bit,
         | I developed a dislike for these explicit types. It's a slippery
         | slope to hard code your bitness with people making assumptions
         | where size_t or pointers fit.
         | 
         | Now maybe if you're already 64bit that's fine (it's unlikely
         | that we'll ever need 128bit, and code is unlikely to grow
         | down), but for anything starting smaller it's a pain.
        
           | staunton wrote:
           | The sizes of floating point are growing "apart" in the AI
           | space. Who's to say what might happen with sizes of types.
           | Maybe it's great to use mostly u8 if you're controlling a
           | worldwide asynchronous networked megacomputer.
        
           | diarrhea wrote:
           | People are already working on a 128bit Linux kernel.
        
           | dralley wrote:
           | Just because your code compiles on a new platform doesn't
           | mean the prior assumptions of the prior behavior of the types
           | remains valid.
        
             | duped wrote:
             | But it _should_ , and that's what stricter typing can help
             | with.
        
         | WalterBright wrote:
         | D uses byte, short, int, long for i8, i16, i32, i64, and ubyte,
         | ushort, uint, and ulong for the unsigned versions. After 5
         | minutes with the language there is no longer any point to
         | emphasizing the number of bits. Besides, they're just easier to
         | touch type.
        
           | tialaramex wrote:
           | I think that trade off made more sense twenty years ago than
           | it does today. So it's understandable that D did this, but
           | doesn't make it a good choice for Zig.
           | 
           | Zig programmers don't need to keep talking about types, so
           | the type (and thus in this case its size) is getting
           | mentioned mostly when that matters, e.g. API boundaries.
        
           | eps wrote:
           | Not sure if I parse your argument right, but there's lot of
           | cases when one would care how wide specific integer type is.
           | Especially in the code that works with data in bulk (as in
           | millions/billions of data objects), where not wasting bits
           | really pays off.
        
         | pavlov wrote:
         | The C standard effectively can't add new types without the "_t"
         | suffix, so they're stuck with limitations that a brand new
         | language won't have.
        
           | pitaj wrote:
           | Can't they add a new header file?
        
             | flohofwoe wrote:
             | What if a library API needs to include this new header
             | because it wants to use the new typedefs in its API
             | declarations but your own code (which needs to include the
             | library header) also has its own 'u8' typedefs?
             | 
             | (PS: it's probably ok because C compilers seem to accept
             | redundant typedefs)
        
               | sieabahlpark wrote:
               | It sounds like you got some refactoring to use since you
               | didn't namespace your own core types?
        
               | flohofwoe wrote:
               | C doesn't have namespaces.
               | 
               | The only sane way to deal with name collisions is either
               | to use C stdlib types in library APIs (e.g. uint8_t), or
               | use a library specific prefix (e.g. mylib_u8) - which is
               | of course even more awkward than just using the standard
               | uint8_t.
        
               | naasking wrote:
               | A prefix and a namespace aren't really that different in
               | practice.
        
               | staunton wrote:
               | They are different. Namespaces are syntactically enforced
               | and you can opt-in or out to names. To have syntactically
               | validated qualified access.
               | 
               | Naming _might_ go well and have the same utility, but you
               | never know what evil things are going on in all those
               | headers, redefining each other 's symbols and yours.
        
               | stephc_int13 wrote:
               | I think that "namespacing" basic types such as u32 is
               | completely unecessary.
               | 
               | In what world would an u32_MYLIB would be different than
               | an u32_SOMELIB?
               | 
               | Basic types are common enough.
        
               | pavlov wrote:
               | The preprocessor doesn't know that u32 defined in foo.h
               | is the same type as u32 defined in bar.h. So you'll get
               | an error when importing both.
        
         | jjtheblunt wrote:
         | do you mean just to save the _t and int substrings, over and
         | over? (if so, i agree)
        
       | LexiMax wrote:
       | One thing that was unclear to me based on my prior noodling with
       | Zig was what the plans were for supporting interface or trait-
       | based polymorphism.
       | 
       | I've been falling in love with C++'s
       | std::experimental::is_detected and am looking forward to being
       | allowed to use C++20 in my environment since it will bring
       | Concepts, but in my cursory examination it seems Zig seems to
       | prefer vtable abstractions like Allocator.VTable.
        
         | v4vvdq wrote:
         | Zig does have some kind of interfaces, check [1]. I am
         | interested to learn, how Traits in Rust and Interfaces in Go
         | behave differently from this concept.
         | 
         | [1]
         | https://github.com/ratfactor/ziglings/blob/main/exercises/09...
        
       | jjgreen wrote:
       | Earlier discussion https://news.ycombinator.com/item?id=36497574
        
         | nunuvit wrote:
         | Is a single comment really a "discussion"?
        
       | bodge5000 wrote:
       | I'm no expert on either languages, but I tried Zig for the first
       | time properly the other day. I really liked it up until I hit
       | hashmaps. One thing that I think goes under-appreciated about C
       | (and other languages with a similar paradigm) is thats its pretty
       | upfront and clear about what it can and can't do out of the box.
       | If you want hashmaps in C, you need to create your own
       | implementation, otherwise think of a way round them. In Zig
       | meanwhile, they exist in the standard library, but at least for
       | me, were more work than they were worth to work with. The result
       | was the same, when making my test project in both C and Zig I
       | ended up not using hashmaps, the difference is that in C I came
       | to this decision in 2 minutes rather than 2 hours.
       | 
       | I imagine if I had more experience in Zig, none of that would be
       | a problem, and its not aiming to be a beginner-friendly language
       | anyway, but that was just my experience of it so far
        
         | hazebooth wrote:
         | Would you mind elaborating on the complications you ran into? I
         | ask because I think you used std.HashMap instead of
         | std.AutoHashMap, the latter of which automatically chooses a
         | hash function based on the types provided.
        
           | bodge5000 wrote:
           | It was a little over a week ago now so my memory is a bit
           | hazy, but I'll try to the best of my knowledge.
           | 
           | Without getting into the weeds of why (happy to do so, just
           | want to keep this readable), basically I needed to define and
           | populate a hashmap in a new script and then import it into my
           | main script, which to my mind left me with two options:
           | 
           | * Define and initialise it at the same time (my preferred
           | method) as a constant. I don't have much to say on this as
           | iirc, I had no luck with it at all, never even got close.
           | 
           | * Define it in a (public) function, add each field in with
           | "put" and then return the hashmap. I tried with
           | std.AutoHashMap and various other things, but to what I could
           | work out there was no type of hashmap, so it wouldn't accept
           | my return type.
        
             | hazebooth wrote:
             | I think for the first point you wanted a block that
             | evaluated to the map, unsure if that's what you wanted
             | though
        
             | ForkMeOnTinder wrote:
             | Here's how you return a hashmap from a function:
             | fn buildMap(allocator: std.mem.Allocator)
             | !std.AutoHashMap(u64, u64) {           var result =
             | std.AutoHashMap(u64, u64).init(allocator);
             | errdefer result.deinit();           try result.put(10,
             | 100);           try result.put(20, 200);           return
             | result;       }
             | 
             | Your #1 option should be possible once Zig has comptime
             | allocators -- it's on the roadmap, but not possible yet
             | iirc.
        
         | matklad wrote:
         | As counter point, the way that Zig implements hash maps is
         | absolutely instrumental for TigerBeetle.
         | 
         | Zig hashmaps _are_ quite a bit more cumbersome than, eg, in
         | Rust --- you need to decided whether you want the allocator to
         | be bundled with the hasmap, and its also up to the user of the
         | hash map to provide equality and hash code (and, of course,
         | there's manual defer instead of RAII).
         | 
         | However, this flexibility and verbosity comes with a super-
         | power --- you can pass an allocator to a hash map at creation
         | time, and then _not_ pass an allocator when you actually use
         | the hash map. This is huge. This means that we get compile-time
         | guarantee that we follow rule 3 of NASA's 10 rules
         | 3. Do not use dynamic memory allocation after initialization.
         | 
         | And we _still_ can enjoy using a hashmap from the standard
         | library! No other language I know has an equivalent tool.
        
           | vitaminCPP wrote:
           | > No other language I know has an equivalent tool.
           | 
           | I completely agree: this has been my experience as well.
        
           | the__alchemist wrote:
           | Not quite std, but Rust's heapless lib let's you use a
           | statically-allocated Hashmap. The distinction is its max size
           | is declared on construction.
        
             | matklad wrote:
             | That's not quite it: with heapless, memory is fully static,
             | the size of hash map is a compile-time parameter, which is
             | a part of HashMap's type.
             | 
             | In Zig, the map could be initialized and sized at runtime,
             | but you still can enforce, statically, that it doesn't do
             | any allocations after that.
             | 
             | In both nightly Rust and Zig, heapless version can be
             | expressed by passing a fixed-buffer-backed allocator to the
             | standard hash map.
        
               | menaerus wrote:
               | I don't quite understand how do you change the size of a
               | zig hashmap in runtime by, for example, inserting 1M
               | items at some point but at the same time being able to
               | enforce in compile-time that such operation will not
               | result with dynamic allocation?
        
               | 8n4vidtmkvmk wrote:
               | I don't know, but you could avoid resizing the hashmap
               | and allow the collisions but then your buckets would
               | still need resizing
        
               | matklad wrote:
               | Compare these two methods:
               | 
               | https://github.com/ziglang/zig/blob/0dffab7356685c7643aa6
               | e3c...
               | 
               | https://github.com/ziglang/zig/blob/0dffab7356685c7643aa6
               | e3c...
               | 
               | One of them requires passing an allocator (and can
               | allocate), the other doesn't have an allocator argument
               | (and thus can't allocate). If you only pass allocator to
               | `init` method of your application, and don't store it
               | anywhere, only init will be able to call allocating
               | methods, the rest of the app will be allocation free, by
               | construction.
        
               | menaerus wrote:
               | If the second one can't allocate then how does it handle
               | the case where you don't have enough capacity to insert
               | the new (k,v) pair?
               | 
               | I can see that the difference between the two is in
               | self.growIfNeeded() call, https://github.com/ziglang/zig/
               | blob/0dffab7356685c7643aa6e3c..., which the one that
               | doesn't allocate really doesn't have. Does it assume that
               | the predefined capacity will not be reached?
        
               | e4m2 wrote:
               | > Does it assume that the predefined capacity will not be
               | reached?
               | 
               | Yes, the function name says exactly that as well, though
               | admittedly what this actually means and what consequences
               | it might have if the assumption is false are probably
               | fairly opaque to a novice user.
               | 
               | > how does it handle the case where you don't have enough
               | capacity to insert the new (k,v) pair?
               | 
               | Breaking the invariant results in safety-checked
               | undefined behavior, that's what the "asserts" in the doc
               | comment signifies[0]. Basically, if something goes awry
               | at runtime you'll get a crash along with a nice error
               | trace in Debug/ReleaseSafe mode or with the appropriate
               | @setRuntimeSafety call.
               | 
               | If instead you'd like to have errors that you can handle,
               | you could use your real allocator where you expect to
               | actually use it and pass a failing allocator[1]
               | everywhere else (but that's sort of abusing the API IMO,
               | I don't know if I'd actually recommend you do this).
               | 
               | [0] https://ziglang.org/documentation/master/#Doc-
               | Comment-Guidan...
               | 
               | [1] https://github.com/ziglang/zig/blob/0dffab7356685c764
               | 3aa6e3c...
        
           | veber-alex wrote:
           | You can achieve the same thing in Rust or any other language
           | that offers some sort of field privacy (which Zig doesn't
           | btw) by wrapping the hash map with your own type and exposing
           | the interface you want.
        
         | jeltz wrote:
         | I can't remember having any issues with using hash maps in Zig
         | when I tried it out during Advent of Code. They were almost as
         | easy to use in Zig as in Rust. Only extra complication was
         | returning error on failed allocation rather than panicking.
        
       | tayistay wrote:
       | From the article:                 const result = comptime
       | square("hello"); // compile time error: type mismatch
       | 
       | ok, cool. But if the error occurs deep in some hierarchy of
       | comptime calls, do you get the same kind of long errors that you
       | do with C++ templates? Does zig have a way of achieving better
       | ergonomics? One nice thing about generics in Rust and Swift is
       | they are constrained by traits/interfaces so you get a concise
       | error at the call site.
        
         | quic5 wrote:
         | A good api (like std.debug.print for example) can implement
         | nice compile errors that fail early with readable messages
         | manually at comptime. Still not as convenient as traits though
        
       | quelsolaar wrote:
       | Technically there is no "compile time" in C. Any part of the
       | program can be computed at compile time or at runtime. Its
       | entirely up the the implementation. The AS-IF rule in the C
       | standard lets implementations do whatever they want as long as
       | the program outputs the same things. The pre-processor, const
       | expressions, constexpr (a for this reason compliably broken
       | feature in my opinion), even text parsing and tokenization can
       | happen at run time.
       | 
       | Not having the standard define what is compile time and not is a
       | feature not a bug, and one of the reasons that C continues to
       | enjoy healthy compiler development. (I'm not taking a stand for
       | or against Zig, other then that i support the development of new
       | languages)
        
         | steveklabnik wrote:
         | C23 includes constexpr for some constructs, by the way.
        
           | quelsolaar wrote:
           | I know, and its very broken.
        
             | steveklabnik wrote:
             | In what way? I haven't had the opportunity to try it out
             | myself yet.
        
               | quelsolaar wrote:
               | It is a feature that sounds like it promises to make
               | something compile time but it doesn't. At the same time
               | it has a bunch of limitations, that in no way reflects
               | what modern compilers can do. Plenty of times you can
               | write an expression and if you say its a constexpr, the
               | compiler is forced to tell you that its not a valid
               | constexper, but if you just remove the constexper
               | qualifier, the compiler the compiler can solve the
               | expression at compile time just fine. So using constexpr
               | gives you:
               | 
               | -No guarantee that its computed at compile time. -Limits
               | what the compiler lets you do when using the constexper
               | keyword. -Adds no performance benefits or guarantees.
               | -Makes your code not portable to most C compilers.
               | 
               | On top of this it adds implementation burden for
               | compiler, and complicates the specification
               | significantly.
        
               | steveklabnik wrote:
               | Gotcha. To be honest, this description sounds like what
               | constexpr is in C++ as well, and various `const` things
               | in Rust. I thought you were saying that the feature
               | doesn't work, but it sounds more like you don't like how
               | the feature was designed. Thank you for letting me know!
        
               | quelsolaar wrote:
               | Well, its broken in the sense that one part of the
               | standard describes some functionality, and then an other
               | part of the standard, says "Oh you can ignore that."
        
       | gwenzek wrote:
       | I wrote a similar article but focused on how Zig design enable
       | better optimizations than C. https://zig.news/gwenzek/zig-great-
       | design-for-great-optimiza...
        
       | sfpotter wrote:
       | C is standardized and is one of the most (the most?) widely used
       | programming language on the planet. If you write code targeting a
       | C standard and don't include many unstable dependencies, it has a
       | good chance of running correctly for a very long time. If you
       | move from C to Zig, you lose that stability. Are Zig's
       | convenience features really enough to compensate for this loss?
        
         | jorangreef wrote:
         | One of the main reasons, in fact, that we picked Zig for
         | TigerBeetle (over C, which was the alternative, given we needed
         | to handle memory allocation failure), was because of Zig's
         | excellent interoperability with the C ABI.
         | 
         | For example, we write the reference TigerBeetle client
         | implementation completely in Zig, then wrap this with the C
         | ABI, and then bind to this C ABI from all target languages, to
         | increase our velocity in how quickly we can ship language
         | clients.
         | 
         | More details, in general, around our clients here:
         | https://tigerbeetle.com/blog/2023-02-21-writing-high-perform...
        
           | menaerus wrote:
           | > given we needed to handle memory allocation failure
           | 
           | Isn't it that the OOM killer is likely to be a much bigger
           | problem? With the overcommit enabled and OOM enabled I don't
           | think I can envision the case where you would run into a
           | failed allocation - allocations on Unices practically never
           | fail. It is the OOM-killer that will likely kill your process
           | once it detects that the pressure on your ram+swap is high so
           | you won't even have a chance to run into the OOM.
           | 
           | OTOH if you disable the OOM-killer there's another and much
           | bigger problem to solve - a kernel panic. I guess that's not
           | the condition you want your system to run into.
           | 
           | So, I think that the only combination where you can
           | deterministically detect and run into allocation failures is
           | when both OOM-killer and overcommit are disabled. That's what
           | I think Windows is doing by default.
        
           | sfpotter wrote:
           | Sounds cool but doesn't address my question. Do you think the
           | relative instability of Zig will hurt you? Or do the benefits
           | outweigh the costs? Lots of languages make it easy to talk C
           | ABI (including... C itself). So why Zig?
           | 
           | I guess the people downvoting my assume I'm hostile to Zig.
           | I'm not. I'm saying that the stability of C is a selling
           | point and I'm curious what features of Zig are so valuable
           | that people are giving that up.
        
       | andrewedstrom wrote:
       | I remain confused about how Zig makes it to the front page of
       | Hacker News so often. As far as I know, no one is using it in
       | production after 7 years of development.
       | 
       | Are people just really excited about this project? Are the people
       | behind it just really good at marketing?
       | 
       | Honest question: why do we all keep talking about Zig?
        
         | bryancoxwell wrote:
         | Zig is kind of fun to write to be honest. I mean about as fun
         | as a systems programming language can be.
        
         | mtlynch wrote:
         | TigerBeetle is using Zig in production.[0] I imagine some of
         | Zig's other corporate sponsors are using it as well.[1]
         | 
         | [0] https://tigerbeetle.com/
         | 
         | [1] https://ziglang.org/zsf/
        
         | pixelpoet wrote:
         | Andrew Kelley's intro[0] is probably the best talk I've ever
         | seen, and his continuing leadership seems really great to me
         | and worth supporting. Besides my reservations for its use in
         | graphics programming (see my other comment) it looks very
         | attractive in many practical ways (build system!! fuck CMake!)
         | I could go from how I currently write my hobby code, sometimes
         | a prototype for future commercial code.
         | 
         | [0] https://www.youtube.com/watch?v=Gv2I7qTux7g
        
           | PartiallyTyped wrote:
           | This is easily one of the best programming talks I have ever
           | seen, I loved the "no compiler magic" compile-time-known
           | string formatting, and generics literally becoming a thing
           | without magic either.
        
         | kristoff_it wrote:
         | > As far as I know, no one is using it in production after 7
         | years of development.
         | 
         | - Uber uses Zig to produce hermetic builds of their backends
         | and was able to move their C/C++ codebases to arm64 thanks to
         | Zig's C/C++ cross-compilation support.
         | 
         | https://www.uber.com/en-US/blog/bootstrapping-ubers-infrastr...
         | 
         | - Bun is written in Zig and its sudden success was big enough
         | to cause Deno to have an identity crisis.
         | 
         | - TigerBeetle is written in Zig and it's probably the most
         | promising upcoming database company out there.
         | 
         | - There are a few more notable examples, but I haven't been
         | given permission to talk about them publicly yet.
         | 
         | > Honest question: why do we all keep talking about Zig? > Are
         | the people behind it just really good at marketing?
         | 
         | The project is simply inherently interesting. For one reason or
         | another, nobody figured out a proper build & cross-compilation
         | experience for C/C++ before Zig did it, just like nobody has
         | figured out a good package manager yet for C/C++ and we're
         | about to do it.
         | 
         | When Apple Silicon came out Zig was the first compiler able to
         | cross-compile for it (way before LLVM btw) thanks to our custom
         | linker, and thanks to it and the fact that we're working on our
         | own custom backends, we're also going to achieve incremental
         | compilation with in-place binary patching, ultimately reducing
         | linking time to basically zero for incremental rebuilds.
         | 
         | Zig is also the only language that has async/await but that
         | doesn't have an ugly & wasteful split between blocking and
         | evented versions of the same networking library.
         | 
         | I'm stopping here with technical arguments, but there would be
         | more to talk about.
         | 
         | Lastly and most importantly, the project has an interesting
         | approach to finances and governance: we've had a non-profit
         | foundation for longer than Rust and we have made a point to
         | never, ever, let big tech companies influence the governance of
         | Zig.
         | 
         | The Zig Software Foundation pays its developers (instead of
         | hiring copywriters & marketing people) and 90+% of what we make
         | through donations or support contracts goes to developers. The
         | rest is infrastructure and administrative costs (eg CI,
         | accountant).
         | 
         | I'm the guy who's most in charge of marketing and I'm currently
         | working on the automated documentation system because Zig
         | pretty much markets itself.
        
           | eichin wrote:
           | Was apple silicon the thing that brought "caring about cross
           | compilation" back from the dead in general? small-scale
           | embedded use never really went away, but it was always a
           | distinct niche...
        
         | Cyberdog wrote:
         | I can't answer you why Zig keeps showing up on HN (except for
         | the surface-level answer that people keep submitting and
         | upvoting it) but in terms of not being used in production, the
         | Bun project, a Node/Deno alternative, is seeing a good deal of
         | momentum by the people who like JavaScript a bit too much. It's
         | probably the most widely used Zig project so far, including in
         | production.
         | 
         | https://bun.sh
        
           | veber-alex wrote:
           | Who uses bun in production?
           | 
           | Seems totally insane to me to use a pre-1.0 application
           | written in a pre-1.0 language in production considering Node
           | and Deno basically do the same thing.
        
             | lpapez wrote:
             | When the company you work for is burning VC money with no
             | hope or intention of profitability, the only rational
             | technology choice you can make as an engineer is to pick a
             | promising but unproven technology which could boost your
             | resume and help you land your next gig more easily. Or you
             | simply pick it because you like and want to work with it
             | and there is no one to stop you.
             | 
             | I'd be very happy to hear I am wrong about this and see a
             | profitable company with an established product using it.
        
         | Ygg2 wrote:
         | HN needs a way to counteract all Rust hype.
        
       | square_usual wrote:
       | You can repeat "C is a minimal abstraction over assembly" as many
       | times as you want, it doesn't make it true.
        
         | pavlov wrote:
         | I'm just old enough to remember when C and Pascal were called
         | high-level languages.
         | 
         | "C is actually assembly" was quite a plot twist from there.
        
         | jstimpfle wrote:
         | May I suggest you give arguments why it is not (and why we
         | should care about those arguments from a practical standpoint),
         | and what language should better earn the title?
        
           | square_usual wrote:
           | Others have already made a few in response, but for
           | reference, this ACM queue article [1] is a good place to
           | start. It has been widely discussed both on HN and elsewhere,
           | and a simple search of the title can bring up several
           | counter-arguments etc. if you're interested and want to know
           | more.
           | 
           | > what language should better earn the title?
           | 
           | None.
           | 
           | 1: https://queue.acm.org/detail.cfm?id=3212479
        
         | quirino wrote:
         | Could you specify the main ways it isn't? I have limited
         | experience in both but I haven't seen much that suggests
         | otherwise
        
           | chc wrote:
           | Many features of C do not directly correspond with most
           | modern assembly languages. You cannot predict the exact
           | assembly it will generate without knowing a lot of details
           | about your compiler and platform, and even then it's often
           | iffy. It seems like a bit of a leap to call something "a
           | minimal abstraction" if you can't even correctly describe how
           | an operation in the abstraction corresponds to operations in
           | the lower level.
        
             | flohofwoe wrote:
             | That's mainly the result of optimizer passes, C itself
             | doesn't have much to do with it.
             | 
             | Assembly languages actually haven't changed all that much
             | since the 70's, but compilers have improved a lot. The
             | output of early C compilers did indeed match the source
             | code quite closely (and not just on the PDP-11), but even
             | today that's true if you disable optimizations (and even
             | with optimizations is usually pretty straightforward to map
             | the C source to the assembly listing - if you're somewhat
             | aware what optimizer passes in modern compilers are doing).
             | 
             | Of course CPU ISAs are already human-friendly abstractions
             | over what's actually happening down in the hardware.
        
           | [deleted]
        
         | PhilipRoman wrote:
         | It's true and false at the same time. The operations C gives
         | you map 1-to-1 with assembly. Given some C code you can quite
         | accurately predict which loads/stores will be elided by the
         | compiler and what the resulting assembly will be.
         | 
         | I can't name another language for which this is true.
         | 
         | I get that you're hinting at the insane level of undefined
         | behaviour enforcement by compilers, but I don't think it
         | matters all that much once you understand how optimizing
         | compilers work.
        
           | Joker_vD wrote:
           | > The operations C gives you map 1-to-1 with assembly
           | int test(int x, int y) {             return x % y;         }
           | test:                                   // @test
           | sdiv    w8, w0, w1             msub    w0, w8, w1, w0
           | ret
           | 
           | Surprisingly, assembly doesn't have the remainder
           | instruction, but instead it has a multiply-then-subtract
           | instruction which is not corresponding 1-to-1 to anything in
           | C.
        
             | IshKebab wrote:
             | x86 doesn't have remainder. Other instruction sets do. But
             | yeah I agree with your point. Some targets might not even
             | have multiply.
        
               | messe wrote:
               | > x86 doesn't have remainder
               | 
               | The code in the comment you replied to is 64-bit ARM
               | assembly.
        
               | IshKebab wrote:
               | Oops of course.
        
               | Joker_vD wrote:
               | x86 _has_ remainder: IDIV calculates both the quotient
               | and the remainder at the same time and places them in
               | different registers just like many other ISAs before it
               | did. In fact, DIV instruction of PDP-11 worked that way
               | too:                   Description:    The 32-bit two's
               | complement integer in R and Rv1 is divided
               | by the source operand. The quotient is left in R; the
               | remain-                         der in Rv1. Division will
               | be performed so that the remainder
               | is of the same sign as the dividend. R must be even.
               | Example:        CLR RO                         MOV
               | #20001,R1                         DIV #2, RO
               | Before          After                         (RO) =
               | 000000   (RO) = 010000   Quotient
               | (R1) = 020001   (R1) = 000001   Remainder
               | 
               | PDP-11 also had ADC and SBC (add/subtract with carry)
               | instructions which weren't (and still aren't) exposed in
               | C either.
        
           | halayli wrote:
           | > The operations C gives you map 1-to-1
           | 
           | It does not. Implicit casting, inlining, volatile, args
           | passed via registers vs stack etc can significantly change
           | what you expect to be generated.
        
             | jstimpfle wrote:
             | Pretty much a matter of optimization isn't it? Try
             | disabling them. But I take it that this was never the point
             | anyway. I think the point is that the representation of
             | language objects and the runtime are rather straightforward
             | compared to many other languages.
        
               | chc wrote:
               | Only one of those (inlining) is an optimization. Two are
               | language features (implicit casts and volatile) and the
               | other is a calling convention (passing arguments on
               | registers vs. stack).
        
               | flohofwoe wrote:
               | Calling conventions aren't (mainly) a C feature either
               | though, but are defined by the ABI specified for a
               | specific OS/CPU combination (and _all_ languages which
               | want to talk to system APIs need to implement that ABI,
               | not just C).
        
           | simias wrote:
           | > _It 's true and false at the same time. The operations C
           | gives you map 1-to-1 with assembly. Given some C code you can
           | quite accurately predict which loads/stores will be elided by
           | the compiler and what the resulting assembly will be._
           | 
           | I mean, can you though? After all you won't even have the
           | same output depending on compiler and flags. And of course
           | not all architectures have the same capabilities, so the same
           | code can compile to a various number of instructions
           | depending on the target architecture.
           | 
           | Not to mention things like bitfield access that can result in
           | non-atomic load/stores for a simple `foo->bar = 1;`
           | 
           | I'm not sure in what sense you could say that C operations
           | map 1-to-1 with assembly any more than Rust, C++ or basically
           | any compiled language.
        
           | xigoi wrote:
           | In C, unlike pretty much every other language, you can't even
           | know how big an integer is.
        
             | menaerus wrote:
             | Sure you can, it's called sizeof(int). Now read the other
             | comment to understand why.
        
             | coliveira wrote:
             | Of course you can, but this is implementation defined. So
             | you need to create code that does different things based on
             | the underlying implementation. Every reasonably large C
             | code base checks the current implementation to define what
             | type of integers, pointers, etc. they're dealing with.
             | 
             | This is by design, because C was created do deal with
             | disparate processors and OSs. When it was crated it would
             | be difficult and unwise to assume that an integer has size
             | of 2 bytes, for example, since each machine would define
             | its own preferred length.
        
               | IshKebab wrote:
               | > When it was crated it would be difficult and unwise to
               | assume that an integer has size of 2 bytes
               | 
               | It was still a bad design. Of course this is in
               | hindsight, but history has taught is it would have been
               | much better if the _default_ was for specific sized
               | integers, with an _option_ to use `uint_fast32_t` or
               | whatever.
        
               | gpderetta wrote:
               | Then we would be stuck with 16 bits integers. We are
               | lucky to be in a point in time where integer types have
               | been stable for a while, but it wasn't always so.
        
         | cconroy wrote:
         | C is an abstraction over assembly. It is also minimal compared
         | to prolog.
        
         | flohofwoe wrote:
         | Technically that's true because of the "C virtual machine", but
         | pragmatically, C is still the "lowest-level high-level
         | programming language" at least among the popular programming
         | languages (arguably only Forth is lower level, but Forth isn't
         | exactly mainstream).
         | 
         | (and I'd argue that C is closer to assembly than assembly is to
         | what's actually happening inside the CPU, e.g. assembly itself
         | is a high-level abstraction layer that's still pretty close to
         | C - which isn't all that surprising because both probably
         | developed as a symbiosis over time - especially when you look
         | at all the non-standard language extensions in various C
         | compilers)
        
           | tialaramex wrote:
           | You're thinking of the C _abstract_ machine, not a virtual
           | machine. Abstract art is when this is a painted blue circle
           | but it 's _about_ the feeling of sadness when losing somebody
           | close to you - virtual art is when somebody persuades you a
           | crappy GIF of a monkey is worth a million dollars.
           | 
           | And no, it's just not usefully true to model things this way.
           | The C abstract machine is pretty weird even compared to a
           | PDP-11, and your modern computer is nothing like a PDP-11.
           | 
           | C was intended to be efficiently implementable, so that's
           | nice, but it has numerous defects in practice in this regard,
           | because it pre-dates a lot of discoveries about how to
           | implement programming languages.
           | 
           | The machine doesn't have types. At all. They're just not a
           | thing. C has types. They're not very good types, and they're
           | poorly implemented, but they are definitely types. Several
           | other languages from that era don't bother, C does because
           | it's a "high level language" and you'll do better embracing
           | that understanding than trying to pretend it's assembler.
        
       | revskill wrote:
       | OK, so Zig to C is like Typescript to Javascript.
        
         | flohofwoe wrote:
         | TS is just modern JS with type annotations.
         | 
         | Zig OTH is "just C" but with a modern syntax, much more
         | correctness, comptime, reflection, generics, a rich standard
         | library, an integrated build system, cross-compiling that 'just
         | works'.
         | 
         | (I'm sure I forgot a couple of things)
        
         | sgeisenh wrote:
         | I don't think I fully understand the analogy.
         | 
         | Typescript has almost identical semantics to Javascript but
         | adds typing syntax to improve developer experience and make it
         | easier to manage a large-scale JS codebase.
         | 
         | Zig is a fundamentally different language than C that has a lot
         | of new features. Two great examples are comptime and allocators
         | which have complements in C, but are really very different from
         | what C provides.
         | 
         | If you're suggesting that zig is aiming to provide a C
         | alternative with better ergonomics, then I agree; but zig and C
         | have a lot more differences than Typescript and Javascript.
        
           | throwawaymaths wrote:
           | Typescript also compiles to C, zig explicitly seeks to
           | replace C
        
           | esrauch wrote:
           | Zig has more things for C interop than you're suggesting
           | though, namely the extern keyword, calling convention, etc.
           | Like ts/js, zig/c seems first class designed be used together
           | in one codebase, compared to eg Rust where is obviously
           | possible but more deliberately discouraged (since it breaks
           | the safety guarantees, which Zig never claimed to begin with)
        
       | ben20440 wrote:
       | Problems of C:
       | 
       | - memory safety
       | 
       | - concurrency
       | 
       | How Zig addresses them:
       | 
       | - it doesn't.
        
       | ARandomerDude wrote:
       | Hug of death.
       | 
       | https://web.archive.org/web/20230703133352/https://avestura....
        
       | Lyngbakr wrote:
       | That was a nice, clear explanation that even I could understand.
       | One thing I wasn't sure about, though.                 var arr =
       | [_]u32{ 1, 2, 3, 4, 5, 6 }; // 1, 2, 3, 4, 5, 6       const
       | slice1 = arr[1..5];             //    2, 3, 4, 5       const
       | slice2 = slice1[1..3];
       | 
       | If I were to use indices out of range, presumably that would that
       | give me a compile time error? Could I use _variables_ as the
       | indices and, if so, could I get a seg fault at runtime or are
       | there some sort of guards on there?
        
         | stefncb wrote:
         | Zig has bounds checking built-in. You can decide to remove it
         | if you compile for ReleaseSafe instead of ReleaseFast, but it's
         | always in debug builds.
        
           | throwawaymaths wrote:
           | Also note that you can compile your entire program in safety
           | checked mode and compile functions with hot loops with
           | release fast
        
         | wredue wrote:
         | Slices in zig are a fat pointer with a runtime known length,
         | which have bounds checking at runtime and will panic if you
         | access out of bounds.
         | 
         | Unless you build in releasefast, which disables that check.
         | There is releasesafe for a release build that keeps bounds
         | checking panics.
        
         | gcoakes wrote:
         | I think that's runtime safety protected. It should segfault if
         | the program is built with "ReleaseSafe", the variable value is
         | only known at runtime, and the variable overruns the array.
         | There's a "ReleaseFast" mode which disables things like this.
         | Also, if that variable is somehow known at comptime, then I'm
         | pretty sure it will fail while compiling which is what you
         | would want.
        
         | ForkMeOnTinder wrote:
         | > If I were to use indices out of range, presumably that would
         | that give me a compile time error?
         | 
         | Yes, if all operands are comptime-known (or comptime-length-
         | known), bounds checks happen eagerly at compile time.
         | 
         | > Could I use variables as the indices and, if so, could I get
         | a seg fault at runtime or are there some sort of guards on
         | there?
         | 
         | Yes you can use variables. If they're out of range, behavior
         | depends on the build mode:
         | 
         | - In Debug and ReleaseSafe, you get a guaranteed panic (which
         | aborts the app with a stack trace)
         | 
         | - In ReleaseSmall and ReleaseFast, you get undefined behavior
         | 
         | On their discord I suggested a version of slicing that bounds
         | checks in all modes (something like `slice?[1..5]`, syntax
         | doesn't matter) and returns an optional slice, which is null if
         | the bounds are out of range. But they didn't seem too keen on
         | it. So I've been using a little wrapper function instead.
        
           | throwawaymaths wrote:
           | Why not just compile your whole program safe and then
           | demarcate which functions you want to _not_ be safety
           | checked?
        
             | estebank wrote:
             | Careful, you're dangerously close to suggesting an effects
             | system. If the function explicitly marked as doing unsafe
             | math operations calls another that doesn't specify a
             | preference, should its math operation be safe or unsafe? If
             | the former, this makes it likely a better default, but
             | limiting in its usefulness. If the later, then your
             | compiler now has to keep track of this information for the
             | entire call graph.
        
               | throwawaymaths wrote:
               | The form of safety we are talking about here is not like
               | rust's "safety", it does not color functions, and is
               | strictly scoped to the compiled unit (in this case,
               | function)
               | 
               | https://ziglang.org/documentation/master/#setRuntimeSafet
               | y
        
               | estebank wrote:
               | I am aware, my point is about how the following is dealt
               | with:                 fn foo(x: i32) i32 {
               | return x + 100;       }       fn bar(x: i32) i32 {
               | @setRuntimeSafety(false);           return foo(x + 100);
               | }
               | 
               | Particularly when you have arbitrarily deep call graphs
               | where some explicitly enable and disable runtime safety.
        
         | llimllib wrote:
         | > If I were to use indices out of range, presumably that would
         | that give me a compile time error?
         | 
         | yup                   $ zig test test.zig
         | test.zig:8:56: error: index 3 outside array of length 2
         | std.debug.print("\ncompile error: {d}\n", .{slice2[3]});
         | 
         | > Could I use variables as the indices and, if so, could I get
         | a seg fault at runtime or are there some sort of guards on
         | there?
         | 
         | Bounds are checked by default:                   $ zig test
         | test.zig         test.zig:9:56: error: index 3 outside array of
         | length 2             std.debug.print("\ncompile error: {d}\n",
         | .{slice2[invalid_index]});
         | 
         | But you can disable them by building in `ReleaseFast` mode[1]
         | 
         | Here's a test file you can use to play around with it if you
         | like:                   const std = @import("std");
         | test "bounds" {             var arr = [_]u32{ 1, 2, 3, 4, 5, 6
         | }; // 1, 2, 3, 4, 5, 6             const slice1 = arr[1..5]; //
         | 2, 3, 4, 5             const slice2 = slice1[1..3]; //    3, 4
         | const invalid_index: usize = 3;
         | std.debug.print("\nslice2: {d} {d}\n", .{ slice2[0], slice2[1]
         | });             std.debug.print("\ncompile error: {d}\n",
         | .{slice2[invalid_index]});             try
         | std.testing.expect(slice2.len == 2);         }
         | 
         | 1: https://ziglang.org/documentation/master/#Build-Mode
        
       | Dwedit wrote:
       | One thing Zig is missing is Exception Handling. Now before you
       | complain about exception handling grossly bloating the size of a
       | program, know that there are ways to trigger exceptions that do
       | not involve the "throw" keyword. You get processor exceptions
       | when you access a bad pointer, divide by 0, etc. If you don't
       | want the program to instantly terminate, you need to be able to
       | handle those exceptions.
       | 
       | So far, it seems that the only way to handle exceptions is to use
       | another programming language. Zig allows you to mix in C or C++
       | code.
        
         | masfuerte wrote:
         | I know basically nothing about Zig, but you don't need
         | language-level exceptions to handle platform exceptions. You
         | can write the handler code in C. Why can't you write it in Zig?
         | The usual problem is that the language runtime gets in the way,
         | but I thought Zig had a very minimal runtime like C.
        
           | Dwedit wrote:
           | __try and __except are win32-specific language extensions for
           | C that do not exist in Zig. They become code which adds a
           | linked-list item (exception handler entry) somewhere into the
           | TEB. Win32 requires that you call all the Stack Unwind
           | entries within your exception handler. When your exception
           | handler is done, you longjmp out, or return to the offending
           | code. Using the language extensions for exception handling is
           | far more friendly than doing it manually with low-level code.
        
       | pixelpoet wrote:
       | I'm super sold on Zig except that it doesn't make graphics/vector
       | coding with operator overloading possible :( I've heard what
       | Andrew Kelley has to say about it ("that ONE little feature from
       | C++...") but it's just a very sad situation for what otherwise
       | looks like a lovely basis for graphics coding.
       | 
       | Actually, I don't even want operator overloading in general
       | (leading to stuff like the C++ stream API), it's JUST for vector
       | stuff (ideally with swizzles); same for all the shading
       | languages.
       | 
       | Perhaps it's doable to make a little domain-specific language
       | (DSL) in Zig for this purpose?
       | 
       | With vector extensions (and as floh [hi!] points out, also
       | matrices please) I'm 100% ready to seriously consider
       | transitioning from C++, given the smooth C integration.
        
         | flohofwoe wrote:
         | +1 (don't want general operator overloading, but a more
         | powerful @Vector builtin type, basically Clang's
         | ext_vector_type: https://clang.llvm.org/docs/LanguageExtensions
         | .html#vectors-...), plus maybe a similar @Matrix builtin up to
         | 4x4.
        
           | xgkickt wrote:
           | Are Clang's builtin types interoperable with __m128
           | intrinsics?
        
           | naasking wrote:
           | I think operator overloading should be fine even for
           | puritans, as long the language requires the type to be
           | numerical in a strict sense, and so they have a well defined
           | semantics. So integers, floating point, complex numbers,
           | vectors, matrices, etc.
        
             | justinpombrio wrote:
             | How would the language require it to be numerical? For
             | example, if you're defining complex numbers in a library,
             | what would the compiler be checking about your
             | ComplexNumber type before allowing you to define `+` for
             | it?
        
           | Gibbon1 wrote:
           | Maybe I'm small brained but built in vector types align with
           | heavily platform optimized libraries. Meaning you don't want
           | this stuff implemented in the compilers front end. You want
           | it handled in the compilers back end.
        
             | pixelpoet wrote:
             | Performance is equivalent in both representations, but the
             | important thing is being able to write                 vec4
             | c = a * 4 + b;
             | 
             | instead of                 vec4 c = vec4_add(vec4_mul(a,
             | 4), b);
             | 
             | i.e. infix vs postfix order, with simple * and +/-
             | operators etc. I would very much like to appeal to the Zig
             | authors to see the beauty in the former expression compared
             | to the latter, for a huge class of real-world mathematical
             | applications.
        
         | jacquesm wrote:
         | It would be nicer still if vectors and matrices were first
         | class citizens. That way you can hide all of that stuff and it
         | opens up across the board optimization routes that are
         | otherwise much harder.
        
           | pixelpoet wrote:
           | Agreed, that's basically what I'm asking/begging; don't need
           | operator overloading, but please do give me vectors and
           | matrices.
        
         | chongli wrote:
         | Don't even need operator overloading for vectors/matrices. It
         | would be fine if we could simply define new operators
         | (functions with infix notation) and use those. This would allow
         | us to define an operator for matrix multiplication, matrix-
         | vector product, dot product, and cross product.
         | 
         | Having to define these operators as functions with unique text
         | names is extremely ugly and serves no purpose that I can see.
        
           | luckystarr wrote:
           | What was the language which used backticks to transform any
           | function into an infix binary operator? Ruby? Perl?
           | 
           | Something like this could actually fulfill Andrews goal of
           | not having any hidden control flow, as the function would be
           | plainly visible.
           | 
           | The other thing of course would be, what then the valid
           | function names would be? Definitely not `*` but probably `x`
           | or whatever.
        
             | chrisoverzero wrote:
             | > What was the language which used backticks to transform
             | any function into an infix binary operator? Ruby? Perl?
             | 
             | Haskell.
        
             | tyg13 wrote:
             | Haskell does this.
        
             | rscho wrote:
             | Racket
        
         | quic5 wrote:
         | Not all that useful but fun: you can implement swizzling with
         | comptime
        
         | Laremere wrote:
         | Last year I dabbled in making a DSL like solution for operator
         | overloading: https://github.com/Laremere/alg
         | 
         | It ends up slightly more verbose in usage, but the statements
         | themselves remain concise. Unfortunately I got a real job that
         | isn't using Zig, so I've stopped working on this. Others can
         | feel free to take up the torch, though.
        
         | syntheweave wrote:
         | As a comptime macro, yes, you should be able to slot in a DSL
         | for all your vector math and essentially "pass in a string, get
         | the function calls back". It would not be that hard, if you're
         | up on your parser-writing skills, and definitely much cleaner
         | than any C-style equivalent.
        
           | pixelpoet wrote:
           | Yep that seems comptime possible but a little heavy handed
           | with string work, no? With everyone writing their own parsers
           | and language tools...
           | 
           | In any case, if something practical is done here I'd be
           | interested. Aesthetically I prefer the "vectors and matrices
           | are first class types" (and maybe complex numbers could be
           | too), but I guess Andrew isn't so sympathetic to these types
           | being part of the standard language :/
        
         | dsego wrote:
         | Odin does this afaik.
        
       | igor_akhmetov wrote:
       | I really want to like Zig. Honest question: why is Zig so
       | resistant to some kind of RAII? RAII is the biggest improvement
       | that C++ brings over C. Leaving it out of a system programming
       | language 50 years later is just strange? And no, manual defer is
       | not the solution.
        
         | mk12 wrote:
         | I felt that way at first too but I'm less concerned about it
         | the more I use Zig. Defer and errdefer are already a huge
         | improvement over C. I won't deny there's more risk of
         | forgetting to clean up than in Rust or C++, but Zig's approach
         | forces me to think more clearly about what's going on. For
         | example, instead of doing a slow tree traversal to free little
         | bits of memory all over the place (which RAII would do
         | implicitly), in Zig I'd probably use an arena and free it all
         | at once. A similar principle applies to other non-memory
         | resources too (though maybe not mutexes for example). I also
         | think it would be difficult to achieve the flexibility of Zig's
         | unmanaged containers (where deinit takes a parameter) in a RAII
         | language.
        
         | throwaway17_17 wrote:
         | The most frequently given reason is that a core principle for
         | Zig is 'No implicit control flow' or just the more general
         | 'Explicit is always better'. RAII, both for constructors and
         | destructors are inherently implicit control flow constructs,
         | also they usually involve access to allocation primitives,
         | which Zig also rejects when implicit.
         | 
         | You can disagree with the language principles if that is one's
         | position. However, the 'resistance' to RAII is a clear effect
         | of those principles.
        
           | gpderetta wrote:
           | I assume that zig requires the programmer to also manually
           | allocate and free stack frames? And it eschews functions as
           | they can hide allocations and control flows?
        
             | schemescape wrote:
             | I would assume "allocation primitives" is referring to the
             | heap.
        
         | kristoff_it wrote:
         | > I really want to like Zig.
         | 
         | Don't try to like Zig and instead make sure your cup is empty
         | before entering a new tea shop.
        
       | apatheticonion wrote:
       | For me as a high level application developer, I liked C but felt
       | that it needed a rational module system, generics and structural
       | types. The addition of methods on structs would be nice too (no
       | need for inheritance).
       | 
       | The only reason I thought about it is because I have been trying
       | (and failing) to contribute to open source Linux projects - which
       | are practically all written in C and the C++ projects are
       | impossible to read.
        
         | staunton wrote:
         | C++ has all the things you "felt that C needed". So there must
         | be something more that you dislike.
        
           | lmm wrote:
           | The problem with C++ isn't too few features, it's too many.
        
             | staunton wrote:
             | Agreed. However, my point about GP stands.
        
       | sharikous wrote:
       | I think it should be
       | 
       | const x: i16 = -1 * 32768; // valid
       | 
       | In the text it's i32
        
         | kevin_thibedeau wrote:
         | That explanation is bogus anyway. It's a unary operator applied
         | to a positive integer literal. No multiplication involved. In
         | this case the range issue can be addressed in C by using the
         | "L" suffix to upgrade the literal to 32-bit and let the
         | compiler truncate it back to 16-bit.
        
       | rkagerer wrote:
       | The [?] prettification of != threw me for a second. I was like,
       | "Does Zig use special characters for operators"?
        
       | astrobe_ wrote:
       | About macros and comptime: why is the compiler unable to detect
       | that a function is a pure function, and if it is called with
       | constants, the result can be computed ahead of time? At least in
       | the simple cases such as the _square_ example, which are I
       | believe quite common, that would work. Maybe still add an
       | annotation to make sure the compiler will do what the user
       | expects, because humans are terrible compilers after all.
       | 
       | Because the "footgun" here is really that when using macros we
       | are switching languages and strategies - in one case it's eager
       | evaluation, and in the other is sort of like lazy evaluation.
        
         | flohofwoe wrote:
         | C/C++ compilers will do this when optimization is enabled (I
         | guess the reason that's not done without optimization is to
         | preserve "debuggability").
         | 
         | Notice how the add() function is completely 'disolved' into its
         | result '5' here:
         | 
         | https://www.godbolt.org/z/q98svvacW
         | 
         | (the main difference to comptime is that this guarantees that
         | the code is resolved at compile time, and if that's not
         | possible you'll get an error).
         | 
         | The big downside of comptime is that it isn't debuggable, apart
         | from what's essentially 'printf debugging' via the
         | @compileError builtin).
        
           | menaerus wrote:
           | Not only the work of the C/C++ optimizer but the exact reason
           | why C++ constexpr was designed - to compute expressions
           | during the compile-time iff all its dependants can be
           | resolved during the compile time. If they don't it's still
           | cool, they behave as a normal runtime function.
        
           | mike_hearn wrote:
           | Graal's native-image tool also does a form of this. The
           | compiler actually executes parts of your program at compile
           | time, but it's real execution so you can theoretically do
           | anything. The heap data that's generated is then persisted
           | into the program. You can debug such code just like normal
           | code by just running it on HotSpot instead.
        
         | throwawaymaths wrote:
         | Do you mean in zig? It's possible to do arbitrary, bounded
         | computation (compiler quits if you expend "too many tokens") at
         | comptime in zig, unless there is strange statefulness.
         | 
         | This is sensible since you want changes in, e.g. your code tree
         | to taint compiled resources. If your code can go read from the
         | filesystem in an untracked way, you break the incremental
         | compilation model.
        
           | astrobe_ wrote:
           | This was sort of a general question, although Zig confused me
           | a bit by placing the annotation at the call site rather than
           | on the function declaration. I guess this choice was made to
           | keep compilation times under control.
        
             | throwawaymaths wrote:
             | A function should not have to be labelled as comptime to be
             | run at comptime. There are cases when you might want to run
             | the same function at comptime as at runtime (for example,
             | if you want to do a thing with algorithm X but cache the
             | result for a series of trivial cases as a lookup table).
             | 
             | Putting it at the callsite is more explicit actually and
             | for example generating precompiled arrays clearly ties
             | comptimeness to the actual artifact created instead of
             | making it inferred from the signature of the function
             | (which may be very far away in code)
        
       ___________________________________________________________________
       (page generated 2023-07-03 23:01 UTC)