[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)