_______               __                   _______
       |   |   |.---.-..----.|  |--..-----..----. |    |  |.-----..--.--.--..-----.
       |       ||  _  ||  __||    < |  -__||   _| |       ||  -__||  |  |  ||__ --|
       |___|___||___._||____||__|__||_____||__|   |__|____||_____||________||_____|
                                                             on Gopher (inofficial)
   URI Visit Hacker News on the Web
       
       
       COMMENT PAGE FOR:
   URI   Low-Level Optimization with Zig
       
       
        9d wrote 1 hour 28 min ago:
        > People will still mistakenly say "C is faster than Python", when the
        language isn't what they are benchmarking.
        
        Yeah but some language features are disproportionately more difficult
        to optimize. It can be done, but with the right language, the right
        concept is expressed very quickly and elegantly, both by the programmer
        and the compiler.
       
        csjh wrote 3 hours 33 min ago:
        > High level languages lack something that low level languages have in
        great adundance - intent.
        
        Is this line really true? I feel like expressing intent isn't really a
        factor in the high level / low level spectrum. If anything, more ways
        of expressing intent in more detail should contribute towards them
        being higher level.
       
          jeroenhd wrote 2 hours 35 min ago:
          I think this isn't referring to intent as in "calculate the tax rate
          for this purchase" but rather "shift this byte three positions to the
          left". Less about what you're trying to accomplish, and more about
          what you're trying to make the machine do.
          
          Something like purchase.calculate_tax().await.map_err(|e|
          TaxCalculationError { source: e })?; is full of intent, but you have
          no idea what kind of machine code you're going to end up with.
       
            csjh wrote 25 min ago:
            Maybe, but from the author's description, it seems like the
            interpretation of intent that they want is to generally give the
            most information possible to the compiler, so it can do its thing.
            I don't see why the right high level language couldn't give the
            compiler plenty of leeway to optimize.
       
          wk_end wrote 3 hours 18 min ago:
          I agree with you and would go further: the fundamental difference
          between high-level and low-level languages is that in high-level
          languages you express intent whereas in low-level languages you are
          stuck resorting to expressing underlying mechanisms.
       
        timewizard wrote 4 hours 15 min ago:
        That for loop syntax is horrendous.
        
        So I have two lists,  side by side,  and the position of items in one
        list matches positions of items in the other?  That just makes my eyes
        hurt.
        
        I think modern languages took a wrong turn by adding all this "magic"
        in the parser and all these little sigils dotted all around the code. 
        This is not something I would want to look at for hours at a time.
       
          int_19h wrote 1 hour 16 min ago:
          Such arrays are an extremely common pattern in low-level code
          regardless of language, and so is iterating them in parallel, so it's
          natural for Zig to provide a convenient syntax to do exactly that in
          a way that makes it clear what's going on (which IMO it does very
          well). Why does it make your eyes hurt?
       
        dustbunny wrote 6 hours 2 min ago:
        What interests me most by zig is the ease of the build system, cross
        compilation, and the goal of high iteration speed. I'm a gamedev, so I
        have performance requirements but I think most languages have
        sufficient performance for most of my requirements so it's not the #1
        consideration for language choice for me.
        
        I feel like I can write powerful code in any language, but the goal is
        to write code for a framework that is most future proof, so that you
        can maintain modular stuff for decades.
        
        C/C++ has been the default answer for its omnipresent support. It feels
        like zig will be able to match that.
       
          wg0 wrote 3 hours 6 min ago:
          Zig seems to be simpler Rust and better Go.
          
          Off topic - One tool built on top of Zig that I really really admire
          is bun.
          
          I cannot tell how much simpler my life is after using bun.
          
          Similar things can be said for uv which is built in Rust.
       
            FlyingSnake wrote 2 hours 16 min ago:
            Zig is nothing like Go. Go uses GC and a runtime while Zig has
            none. While Zig’s functions aren’t coloured, it lacked the CSP
            style primitives like goroutines and channels.
       
          haberman wrote 3 hours 21 min ago:
          > I feel like I can write powerful code in any language, but the goal
          is to write code for a framework that is most future proof, so that
          you can maintain modular stuff for decades.
          
          I like Zig a lot, but long-term maintainability and modularity is one
          of its weakest points IMHO.
          
          Zig is hostile to encapsulation.  You cannot make struct members
          private: [1] Key quote:
          
          > The idea of private fields and getter/setter methods was
          popularized by Java, but it is an anti-pattern. Fields are there;
          they exist. They are the data that underpins any abstraction. My
          recommendation is to name fields carefully and leave them as part of
          the public API, carefully documenting what they do.
          
          You cannot reasonably form API contracts (which are the foundation of
          software modularity) unless you can hide the internal representation.
           You need to be able to change the internal representation without
          breaking users.
          
          Zig's position is that there should be no such thing as internal
          representation; you should publicly expose, document, and guarantee
          the behavior of your representation to all users.
          
          I hope Zig reverses this decision someday and supports private
          fields.
          
   URI    [1]: https://github.com/ziglang/zig/issues/9909#issuecomment-9426...
       
            dgb23 wrote 3 min ago:
            Some years ago I started to just not care about setting things to
            "private" (in any language).  And I care _a lot_ about long term
            maintainability and breakage. I haven't regretted it since.
            
            > You cannot reasonably form API contracts (...) unless you can
            hide the internal representation.
            
            Yes you can, by communicating the intended use can be made with
            comments/docstrings, examples etc.
            
            One thing I learned from the Clojure world, is to have a separate
            namespace/package or just section of code, that represents an API
            that is well documented, nice to use and more importantly stable.
            That's really all that is needed.
            
            (Also, there are cases where you actually need to use a thing in a
            way that was not intended. That obviously comes with risk, but when
            you need it, you're _extremely_ glad that you can.)
       
            mwkaufma wrote 7 min ago:
            > You need to be able to change the internal representation without
            breaking users.
            
            Unless the user only links an opaque pointer, then just changing
            the sizeof() is breaking, even if the fields in question are
            hidden. A simple doc comment indicating that "fields starting with
            _ are not guaranteed to be minor-version-stable" or somesuch is a
            perfectly "reasonable" API.
       
            unclad5968 wrote 36 min ago:
            I disagree with plenty of Andrew's takes as well but I'm with him
            on private fields. I've never once in 10 years had an issue with a
            public field that should have been private, however I have had to
            hack/reimplement entire data structures because some library author
            thought that no user should touch some private field.
            
            > You cannot reasonably form API contracts (which are the
            foundation of software modularity) unless you can hide the internal
            representation. You need to be able to change the internal
            representation without breaking users.
            
            You never need to hide internal representations to form an "API
            contract". That doesn't even make sense. If you need to be able to
            change the internal representation without breaking user code,
            you're looking for opaque pointers, which have been the solution to
            this problem since at least C89, I assume earlier.
            
            If you change your data structures or the procedures that operate
            on them, you're almost certain to break someone's code somewhere,
            regardless of whether or not you hide the implementation.
       
            9d wrote 1 hour 20 min ago:
            Andrew has so many wrong takes. Unused variables is another.
            
            Such a smart guy though, so I'm hesitant to say he's wrong. And
            maybe in the embedded space he's not, and if that's all Zig is for
            then fine. But internal code is a necessity of abstraction. I'm not
            saying it has to be C++ levels of abstraction. But there is a line
            between interface and implementation that ought to be kept. C
            headers are nearly perfect for this, letting you hide and rename
            and recast stuff differently than your .c file has, allowing you to
            change how stuff works internally.
            
            Imagine if the Lua team wasn't free to make it significantly faster
            in recent 5.4 releases because they were tied to every internal
            field. We all benefited from their freedom to change how stuff
            works inside. Sorry Andrew but you're wrong here. Or at least you
            were 4 years ago. Hopefully you've changed your mind since.
       
              philwelch wrote 19 min ago:
              > I'm not saying it has to be C++ levels of abstraction. But
              there is a line between interface and implementation that ought
              to be kept. C headers are nearly perfect for this, letting you
              hide and rename and recast stuff differently than your .c file
              has, allowing you to change how stuff works internally.
              
              Can’t you do this in Zig with modules? I thought that’s what
              the ‘pub’ keyword was for.
              
              You can’t have private fields in a struct that’s publicly
              available but the same is sort of true in C too. OO style
              encapsulation isn’t the only way to skin a cat, or to send the
              cat a message to skin itself as the case may be.
       
                9d wrote 8 min ago:
                I don't know Zig so I dunno maybe
       
              haberman wrote 1 hour 3 min ago:
              I agree with almost all of this, including the point about c
              header files, except that code has to be in headers to be inlined
              (unless you use LTO), which in practice forces code into headers
              even if you’d prefer to keep it private.
       
            dustbunny wrote 1 hour 36 min ago:
            I don't care about public/private.
       
            eddd-ddde wrote 2 hours 1 min ago:
            Just prefix internal fields with underscore and be a big boy and
            don't access them from the outside.
            
            If you really need to you can always use opaque pointers for the
            REALLY critical public APIs.
       
              9d wrote 1 hour 7 min ago:
              Sure, then publish an API which gets used by huge apps or games,
              which then make thorough use of _foo and _bar, and now you're
              stuck with those fields for the next decade and can't change it
              no matter what, even though they're preventing a 100x performance
              boost that requires dropping those fields.
       
              haberman wrote 1 hour 26 min ago:
              I am not the only user of my API, and I cannot control what users
              do.
              
              My experience is that users who are trying to get work done will
              bypass every speed bump you put in the way and just access your
              internals directly.
              
              If you "just" rely on them not to do that, then your internals
              will effectively be frozen forever.
       
                nicoburns wrote 1 hour 11 min ago:
                > If you "just" rely on them not to do that, then your
                internals will effectively be frozen forever.
                
                Or they will be broken when you change them and they upgrade.
                The JavaScript ecosystem uses this convention and generally if
                a field is prefixed by an underscore and/or documented as being
                non-public then you can expect to break in future versions (and
                this happens frequently in practice).
                
                Not necessarily saying that's better, but it is another choice
                that's available.
       
          raincole wrote 4 hours 12 min ago:
          I wonder how zig works on consoles. Usually consoles hate anything
          that's not C/C++. But since zig can be transpiled to C, perhaps it's
          not completely ruled out?
       
            jeroenhd wrote 2 hours 40 min ago:
            Consoles will run anything you compile for them. There are stable
            compilers for most languages for just about any console I know of,
            because modern consoles are pretty much either amd64 or aarch64
            like phones and computers are.
            
            Language limitations are more on the SDK side of things. SDKs are
            available under NDAs and even publicly available APIs are often
            proprietary. "Real" test hardware (as in developer kits) is
            expensive and subject to NDAs too.
            
            If you don't pick the language the native SDK comes with (which is
            often C(++)), you'll have to write the language wrappers yourself,
            because practically no free, open, upstream project can maintain
            those bindings for you. Alternatively, you can pay a company that
            specializes in the process, like the developers behind Godot will
            tell you to do: [1] I think Zig's easy C interop will make
            integration for Zig into gamedev quite attractive, but as the
            compiler still has bugs and the language itself is ever changing, I
            don't think any big companies will start developing games in Zig
            until the language stabilizes. Maybe some indie devs will use it,
            but it's still a risk to take.
            
   URI      [1]: https://docs.godotengine.org/en/stable/tutorials/platform/...
       
          osigurdson wrote 5 hours 10 min ago:
          I've dabbled in Rust, liked it, heard it was bad so kind of paused.
          Now trying it again and still like it. I don't really get why people
          hate it so much. Ugly generics - same thing in C# and Typescript.
          Borrow checker - makes sense if you have done low level stuff before.
       
            sapiogram wrote 1 hour 2 min ago:
            Haters gonna hate. If you're working on a project that needs
            performance and correctness, nothing can get the job done like
            Rust.
       
            int_19h wrote 1 hour 25 min ago:
            If you don't happen to come across some task that implies a data
            model that Rust is actively hostile towards (e.g. trees with
            backlinks, or more generally any kind of graph with cycles in it),
            borrow checker is not much of a hassle. But the moment you hit
            something like that, it becomes a massive pain, and requires either
            "unsafe" (which is strictly more dangerous than even C, never mind
            Zig) or patterns like using indices instead of pointers which are
            counter to high performance and effectively only serve to work
            around the borrow checker to shut it up.
       
              dwattttt wrote 51 min ago:
              > the moment you hit something like that, it becomes a massive
              pain, and requires either "unsafe" (which is strictly more
              dangerous than even C, never mind Zig) or patterns like using
              indices instead of pointers
              
              If you need to make everything in-house this is the experience.
              For the majority though, the moment you require those things you
              reach for a crate that solves those problems.
       
              carlmr wrote 55 min ago:
              >requires either "unsafe" (which is strictly more dangerous than
              even C, never mind Zig)
              
              Um, what? Unsafe Rust code still has a lot more safety checks
              applied than C.
              
              >It’s important to understand that unsafe doesn’t turn off
              the borrow checker or disable any of Rust’s other safety
              checks: if you use a reference in unsafe code, it will still be
              checked. The unsafe keyword only gives you access to these five
              features that are then not checked by the compiler for memory
              safety. You’ll still get some degree of safety inside of an
              unsafe block.
              
   URI        [1]: https://doc.rust-lang.org/book/ch20-01-unsafe-rust.html
       
          FlyingSnake wrote 5 hours 35 min ago:
          I recently, for fun, tried running zig on an ancient kindle device
          running stripped down Linux 4.1.15.
          
          It was an interesting experience and I was pleasantly surprised by
          the maturity of Zig. Many things worked out of the box and I could
          even debug a strange bug using ancient GDB. Like you, I’m sold on
          Zig too.
          
          I wrote about it here:
          
   URI    [1]: https://news.ycombinator.com/item?id=44211041
       
        el_pollo_diablo wrote 11 hours 37 min ago:
        > In fact, even state-of-art compilers will break language
        specifications (Clang assumes that all loops without side effects will
        terminate).
        
        I don't doubt that compilers occasionally break language specs, but in
        that case Clang is correct, at least for C11 and later. From C11:
        
        > An iteration statement whose controlling expression is not a constant
        expression, that performs no input/output operations, does not access
        volatile objects, and performs no synchronization or atomic operations
        in its body, controlling expression, or (in the case of a for
        statement) its expression-3, may be assumed by the implementation to
        terminate.
       
          tialaramex wrote 10 hours 55 min ago:
          C++ says (until the future C++ 26 is published) all loops, but as you
          noted C itself does not do this, only those "whose controlling
          expression is not a constant expression".
          
          Thus in C the trivial infinite loop for (;;); is supposed to actually
          compile to an infinite loop, as it should with Rust's less opaque
          loop {}  -- however LLVM is built by people who don't always remember
          they're not writing a C++ compiler, so Rust ran into places where
          they're like "infinite loop please" and LLVM says "Aha, C++ says
          those never happen, optimising accordingly" but er... that's the
          wrong language.
       
            kibwen wrote 9 hours 4 min ago:
            > Rust ran into places where they're like "infinite loop please"
            and LLVM says "Aha, C++ says those never happen, optimising
            accordingly" but er... that's the wrong language
            
            Worth mentioning that LLVM 12 added first-class support for
            infinite loops without guaranteed forward progress, allowing this
            to be fixed:
            
   URI      [1]: https://github.com/rust-lang/rust/issues/28728
       
              loeg wrote 5 hours 14 min ago:
              For some context, 12 was released in April 2021.  LLVM is now on
              20 -- the versions have really accelerated in recent years.
       
            el_pollo_diablo wrote 9 hours 5 min ago:
            Sure, that sort of language-specific idiosyncrasy must be dealt
            with in the compiler's front-end. In TFA's C example, consider that
            their loop
            
              while (i <= x) {
                  // ...
              }
            
            just needs a slight transformation to
            
              while (1) {
                  if (i > x)
                  break;
                  // ...
              }
            
            and C11's special permission does not apply any more since the
            controlling expression has become constant.
            
            Analyzes and optimizations in compiler backends often normalize
            those two loops to a common representation (e.g. control-flow
            graph) at some point, so whatever treatment that sees them
            differently must happen early on.
       
              pjmlp wrote 8 hours 6 min ago:
              In theory, in practice it depends on the compiler.
              
              It is no accident that there is ongoing discussion that clang
              should get its own IR, just like it happens with the other
              frontends, instead of spewing LLVM IR directly into the next
              phase.
       
        justmarc wrote 11 hours 41 min ago:
        Optimization matters, in a huge way. Its effects are compounded by
        time.
       
          sgt wrote 7 hours 19 min ago:
          Only if the software ends up being used.
       
        uecker wrote 13 hours 13 min ago:
        You don't really need comptime to be able to inline and unroll a string
        comparison. This also works in C: [1] (edit: fixed typo)
        
   URI  [1]: https://godbolt.org/z/6edWbqnfT
       
          Retro_Dev wrote 13 hours 8 min ago:
          Yep, you are correct! The first example was a bit too simplistic. A
          better one would be [1] Do note that your linked godbolt code
          actually demonstrates one of the two sub-par examples though.
          
   URI    [1]: https://github.com/RetroDev256/comptime_suffix_automaton
       
            uecker wrote 11 hours 50 min ago:
            I haven't looked at the more complex example, but the second issue
            is not too difficult to fix: [1] For complicated things, I haven't
            really understood the advantage compared to simply running a
            program at build time.
            
   URI      [1]: https://godbolt.org/z/48T44PvzK
       
              Cloudef wrote 11 hours 36 min ago:
              To be honest your snippet isn't really C anymore by using a
              compiler builtin. I'm also annoyed by things like `foo(int N,
              const char x[N])` which compilation vary wildly between compilers
              (most ignore them, gcc will actually try to check if the
              invariants if they are compile time known)
              
              > I haven't really understood the advantage compared to simply
              running a program at build time.
              
              Since both comptime and runtime code can be mixed, this gives you
              a lot of safety and control. The comptime in zig emulates the
              target architecture, this makes things like cross-compilation
              simply work. For program that generates code, you have to run
              that generator on the system that's compiling and the generator
              program itself has to be aware the target it's generating code
              for.
       
                uecker wrote 11 hours 8 min ago:
                It also works with memcpy from the library: [1] I just didn't
                feel like burdening godbolt with an inlclude.
                
                I do not understand your criticism of [N].  This gives compiler
                more information and catches errors. This is a good thing! Who
                could be annoyed by this: [2] (of course, nowadays you could
                also define a descent span type in C)
                
                The cross-compilation argument has some merit, but not enough
                to warrant the additional complexity IMHO.  Compile-time
                computation will also have annoying limitations and makes
                programs more difficult to understand.    I feel sorry for
                everybody who needs to maintain complex compile time code
                generation. Zig certainly does it better than C++ but still..
                
   URI          [1]: https://godbolt.org/z/Mc6M9dK4M
   URI          [2]: https://godbolt.org/z/EeadKhrE8
       
                  quibono wrote 9 hours 41 min ago:
                  Possibly a stupid question... what's a descent span type?
       
                    uecker wrote 9 hours 20 min ago:
                    Something like this: [1] It encapsulates a pointer to an
                    array and a length.  It is not perfect because of some
                    language limitation (which I hope we can remove), but also
                    not to bad. One limitation is that you need to pass it a
                    typedef name instead of any type, i.e. you may need a
                    typedef first. But this is not terrible.
                    
   URI              [1]: https://godbolt.org/z/er9n6ToGP
       
                      quibono wrote 7 hours 47 min ago:
                      Thanks, this is great! I've been having a look at your
                      noplate repo, I really like what you're doing there
                      (though I need a minute trying to figure out the more
                      arcane macros!)
       
                        uecker wrote 7 hours 10 min ago:
                        In this case, the generic span type is just 
                          #define span(T) struct CONCAT(span_, T) { ssize_t N;
                        T* data; }
                        And the array to span macro would just create such an
                        object form an array by storing the length of the array
                        and the address of the first element.
                          #define array2span(T, x) ({ auto __y = &(x);
                        (span(T)){ array_lengthof(__y), &(__y)[0] }; })
       
                  Cloudef wrote 10 hours 46 min ago:
                  > I do not understand your criticism of [N]. This gives
                  compiler more information and catches errors. This is a good
                  thing!
                  
                  It only does sane thing in GCC, in other compilers it does
                  nothing and since it's very underspec'd it's rarely used in
                  any C projects. It's shame Dennis's fat pointers / slices
                  proposal was not accepted.
                  
                  > warrant the additional complexity IMHO
                  
                  In zig case the comptime reduces complexity, because it is
                  simply zig. It's used to implement generics, you can call zig
                  code compile time, create and return types.
                  
                  This old talk from andrew really hammers in how zig is
                  evolution of C:
                  
   URI            [1]: https://www.youtube.com/watch?v=Gv2I7qTux7g
       
                    uecker wrote 10 hours 40 min ago:
                    Then the right thing would be to complain about those other
                    compilers.  I agree that Dennis' fat pointer proposal was
                    good.
                    
                    Also in Zig it does not reduce complexity but adds to it by
                    creating an distinction between compile time and run-time.
                    It is only lower complexity by comparing to other
                    implementations of generic which are even worse.
       
                      pron wrote 6 hours 54 min ago:
                      C also creates a distinction between compile-time and
                      run-time, which is more arcane and complicated than that
                      of Zig's, and your code uses it, too: macros (and other
                      pre-processor programming). And there are other
                      distinctions that are more subtle, such as whether the
                      source of a target function is available to the caller's
                      compilation unit or not, static or not etc..
                      
                      C only seems cleaner and simpler if you already know it
                      well.
       
                        uecker wrote 6 hours 44 min ago:
                        My point is not about whether compile-time programming
                        is simpler in C or in Zig, but that is in most cases
                        the wrong solution. My example is also not about
                        compile time programming (and does not use macro: [1]
                        ), but about letting the optimizer do its job.     The
                        end result is then  leaner than attempting to write a
                        complicated compile time solution - I would argue.
                        
   URI                  [1]: https://godbolt.org/z/Mc6M9dK4M
       
                          pyrolistical wrote 6 hours 19 min ago:
                          Right tool for the job. There was no comptime problem
                          shown in the blog.
                          
                          But if there were zig would prob be simpler since it
                          uses one language that seamlessly weaves comptime and
                          runtime together
       
                            uecker wrote 5 hours 35 min ago:
                            I don't know, to me it seems the blog tries to make
                            the case that comptime is useful for low-level
                            optimization: "Is this not amazing? We just used
                            comptime to make a function which compares a string
                            against "Hello!\n", and the assembly will run much
                            faster than the naive comparison function. It's
                            unfortunately still not perfect."  But it turns out
                            that a C compiler will give you the "perfect" code
                            directly while the comptime Zig version is fairly
                            complicated. You can argue that this was just a bad
                            example and that there are other examples where
                            comptime makes more sense. The thing is, about two
                            decades ago I was similarly excited about
                            expression-template libraries for very similar
                            reasons. So I can fully understand how the idea of
                            "seamlessly weaves comptime and runtime together"
                            can appear cool. I just realized at some point that
                            it isn't actually all that useful.
       
                              pron wrote 1 hour 30 min ago:
                              >  But it turns out that a C compiler will give
                              you the "perfect" code directly while the
                              comptime Zig version is fairly complicated.
                              
                              In this case both would (or could) give the
                              "perfect" code without any explicit comptime
                              programming.
                              
                              >  I just realized at some point that it isn't
                              actually all that useful.
                              
                              Except, again, C code often uses macros, which is
                              a more cumbersome mechanism than comptime (and
                              possibly less powerful; see, e.g. how Zig
                              implements printf).
                              
                              I agree that comptime isn't necessarily very
                              useful for micro optimisation, but that's not
                              what it's for. Being able to shift computations
                              in time is usedful for more "algorithmic" macro
                              optimisations, e.g. parsing things at compile
                              time or generating de/serialization code.
       
                                uecker wrote 1 hour 0 min ago:
                                Of course, a compiler could possibly also
                                optimize the Zig code perfectly. The point is
                                that the blogger did not understand it and
                                instead created an overly complex solution
                                which is not actually needed.  Most C code I
                                write or review does not use a lot of macros,
                                and where they are used it seems perfectly fine
                                to me.
       
                      pjmlp wrote 8 hours 0 min ago:
                      Not only it was a good proposal, since 1990 that WG14 has
                      not done anything else into that sense, and doesn't look
                      like it ever will.
       
                        uecker wrote 5 hours 5 min ago:
                        Let's see. We have a relatively concrete plan to add
                        dependent structure types to C2Y: struct foo { size_t
                        n; char (buf)[.n]; };
                        
                        Once we have this, the wide pointer could just be
                        introduced as syntactic sugar
                        for this. char (buf)[:] = ..
                        
                        Personally, I would want the dependent structure type
                        first as it is more powerful and low-level with no need
                        to decide on a new ABI.
       
                          int_19h wrote 1 hour 22 min ago:
                          This feels like such a massive overkill
                          complexity-wise for something so basic.
       
                            uecker wrote 59 min ago:
                            Why do you think so? The wide pointers are
                            syntactic sugar on top of it, so from an
                            implementation point of view not really simpler.
       
                      Cloudef wrote 10 hours 37 min ago:
                      Sure there's tradeoffs for everything, but if I had to
                      choose between macros, templates, or zig's comptime, I'd
                      take the comptime any time.
       
                        uecker wrote 10 hours 14 min ago:
                        To each their own, I guess. I still find C to be so
                        much cleaner than all the languages that attempt to
                        replace it, I can not possibly see any of them as a
                        future language for me. And it turns out that it is
                        possible to fix issues in C if one is patient enough. 
                        Nowadays I would write this with a span type: [1] which
                        is safe and gives good code.
                        
                        update: clang is even a bit nicer [2] although both
                        compile it to a constant if the other argument is known
                        at compile time.  In light of this, the Zig solution
                        does not impress me much:
                        
   URI                  [1]: https://godbolt.org/z/nvqf6eoK7
   URI                  [2]: https://godbolt.org/z/b99s1rMzh
   URI                  [3]: https://godbolt.org/z/1dacacfzc
       
        saagarjha wrote 13 hours 32 min ago:
        > As an example, consider the following JavaScript code…The generated
        bytecode for this JavaScript (under V8) is pretty bloated.
        
        I don't think this is a good comparison. You're telling the compiler
        for Zig and Rust to pick something very modern to target, while I don't
        think V8 does the same. Optimizing JITs do actually know how to
        vectorize if the circumstances permit it.
        
        Also, fwiw, most modern languages will do the same optimization you do
        with strings. Here's C++ for example:
        
   URI  [1]: https://godbolt.org/z/TM5qdbTqh
       
          vanderZwan wrote 12 hours 15 min ago:
          In general it's a bit of an apples to fruit salad comparison, albeit
          one that is appropriate to highlight the different use-cases of JS
          and Zig. The Zig example uses an array with a known type of fixed
          size, the JS code is "generic" at run time (x and y can be any
          object). Which, fair enough, is something you'd have to pay the cost
          for in JS. Ironically though in this particular example one actually
          would be able to do much better when it comes to communicating type
          information to the JIT: ensure that you always call this function
          with Float64Arrays of equal size, and the JIT will know this and
          produce a faster loop (not vectorized, but still a lot better).
          
          Now, one rarely uses typed arrays in practice because they're pretty
          heavy to initialize so only worth it if one allocates a large typed
          array one once and reuses them a lot aster that, so again, fair
          enough! One other detail does annoy me a little bit: the article says
          the example JS code is pretty bloated, but I bet that a big part of
          that is that the JS JIT can't guarantee that 65536 equals the length
          of the two arrays so will likely insert a guard. But nobody would
          write a for loop that way anyway, they'd write it as i < x.length,
          for which the JIT does optimize at least one array check away. I
          admit that this is nitpicking though.
       
          Retro_Dev wrote 13 hours 10 min ago:
          You can change the `target` in those two linked godbolt examples for
          Rust and Zig to an older CPU. I'm sorry I didn't think about the
          limitations of the JS target for that example. As for your link, It's
          a good example of what clang can do for C++ - although I think that
          the generated assembly may be sub-par, even if you factor in zig
          compiling for a specific CPU here. I would be very interested to see
          a C++ port of [1] though. It is a use of comptime that can't be
          cleanly guessed by a C++ compiler.
          
   URI    [1]: https://github.com/RetroDev256/comptime_suffix_automaton
       
            saagarjha wrote 12 hours 41 min ago:
            I just skimmed your code but I think C++ can probably constexpr its
            way through. I understand that's a little unfair though because C++
            is one of the only other languages with a serious focus on
            compile-time evaluation.
       
        KingOfCoders wrote 14 hours 0 min ago:
        I do love the allocator model of Zig, I would wish I could use
        something like an request allocator in Go instead of GC.
       
          usrnm wrote 13 hours 52 min ago:
          Custom allocators and arenas are possible in go and even do exist,
          but they ara just very unergonomic and hard to use properly. The
          language itself lacks any way to express and enforce ownership rules,
          you just end up writing C with a slightly different syntax and hoping
          for the best. Even C++ is much safer than go without GC
       
            KingOfCoders wrote 12 hours 43 min ago:
            They are not integrated in all libraries, so for me they don't
            exist.
       
        flohofwoe wrote 14 hours 20 min ago:
        > I love Zig for it's verbosity.
        
        I love Zig too, but this just sounds wrong :)
        
        For instance, C is clearly too sloppy in many corners, but Zig might
        (currently) swing the pendulum a bit too far into the opposite
        direction and require too much 'annotation noise', especially when it
        comes to explicit integer casting in math expressions (I wrote about
        that a bit here: [1] ).
        
        When it comes to performance: IME when Zig code is faster than similar
        C code then it is usually because of Zig's more aggressive LLVM
        optimization settings (e.g. Zig compiles with -march=native and does
        whole-program-optimization by default, since all Zig code in a project
        is compiled as a single compilation unit). Pretty much all 'tricks'
        like using unreachable as optimization hints are also possible in C,
        although sometimes only via non-standard language extensions.
        
        C compilers (especially Clang) are also very aggressive about constant
        folding, and can reduce large swaths of constant-foldable code even
        with deep callstacks, so that in the end there often isn't much of a
        difference to Zig's comptime when it comes to codegen (the good thing
        about comptime is of course that it will not silently fall back to
        runtime code - and non-comptime code is still of course subject to the
        same constant-folding optimizations as in C - e.g. if a "pure"
        non-comptime function is called with constant args, the compiler will
        still replace the function call with its result).
        
        TL;DR: if your C code runs slower than your Zig code, check your C
        compiler settings. After all, the optimization heavylifting all happens
        down in LLVM :)
        
   URI  [1]: https://floooh.github.io/2024/08/24/zig-and-emulators.html
       
          int_19h wrote 1 hour 18 min ago:
          I rather suspect that the pendulum will swing rather strongly towards
          more verbose and explicit languages in general in the upcoming years
          solely because it makes things easier for AI.
          
          (Note that this is orthogonal to whether and to what extent use of AI
          for coding is a good idea. Even if you believe that it's not, the
          fact is that many devs believe otherwise, and so languages will
          strive to accommodate them.)
       
          titzer wrote 3 hours 46 min ago:
          Zig has some interesting ideas, and I thought the article was going
          to be more on the low-level optimizations, but it turned out to be
          "comptime and whole program compilation are great". And I agree.
          Virgil has had the full language available at compile time, plus
          whole program compilation since 2006. But Virgil doesn't target LLVM,
          so speed comparisons end up being a comparison between two compiler
          backends.
          
          Virgil leans heavily into the reachability and specialization
          optimizations that are made possible by the compilation model. For
          example it will aggressively devirtualize method calls, remove
          unreachable fields/objects, constant-promote through fields and heap
          objects, and completely monomorphize polymorphic code.
       
          Zambyte wrote 8 hours 44 min ago:
          Regarding the explicit integer casting, it seems like there is some
          cleanup that will be coming soon:
          
   URI    [1]: https://ziggit.dev/t/short-math-notation-casting-clarity-of-...
       
          knighthack wrote 13 hours 5 min ago:
          I'm not sure why allowances are made for Zig's verbosity, but not
          Go's.
          
          What's good for the goose should be good for the gander.
       
            Zambyte wrote 8 hours 34 min ago:
            FWIW Zig has error handling that is nearly semantically identical
            to Go (errors as return values, the big semantic difference being
            tagged unions instead of multiple return values for errors), but
            wraps the `if err != nil { return err}` pattern in a single `try`
            keyword. That's the verbosity that I see people usually complaining
            about in Go, and Zig addresses it.
       
              kbolino wrote 8 hours 3 min ago:
              The way Zig addresses it also discards all of the runtime
              variability too. In Go, an error can say something like
              
                  unmarshaling struct type Foo: in field Bar int: failed to
              parse value "abc" as integer
              
              Whereas in Zig, an error can only say something that's known at
              compile time, like IntParse, and you will have to use another
              mechanism (e.g. logging) to actually trace the error.
       
            ummonk wrote 11 hours 49 min ago:
            Zig's verbosity goes hand in hand with a strong type system and a
            closeness to the hardware. You don't get any such benefits from
            Go's verbosity.
       
            nurbl wrote 12 hours 57 min ago:
            I think a better word may be "explicitness". Zig is sometimes
            verbose because you have to spell things out. Can't say much about
            Go, but it seems it has more going on under the hood.
       
          messe wrote 14 hours 5 min ago:
          With regard to the casting example, you could always wrap the cast in
          a function:
          
              fn signExtendCast(comptime T: type, x: anytype) T {
              const ST = std.meta.Int(.signed, @bitSizeOf(T));
              const SX = std.meta.Int(.signed, @bitSizeOf(@TypeOf(x)));
              return @bitCast(@as(ST, @as(SX, @bitCast(x))));
              }
          
              export fn addi8(addr: u16, offset: u8) u16 {
              return addr +% signExtendCast(u16, offset);
              }
          
          This compiles to the same assembly, is reusable, and makes the intent
          clear.
       
            johnisgood wrote 13 hours 32 min ago:
            Yeah but what is up with all that "." and "@"? Yes, I know what
            they are used for, but it is noise for me (i.e. "annotation
            noise"). This is why I do not use Zig. Zig is more like a lighter
            C++, not a C replacement, IMO.
            
            I agree with everything flohofwoe said, especially this: "C is
            clearly too sloppy in many corners, but Zig might (currently) swing
            the pendulum a bit too far into the opposite direction and require
            too much 'annotation noise', especially when it comes to explicit
            integer casting in math expressions ".
            
            Seems like I will keep using Odin and give C3 a try (still have yet
            to!).
            
            Edit: I quite dislike that the downvote is used for "I disagree, I
            love Zig". sighs. Look at any Zig projects, it is full of
            annotation noise. I would not want to work with a language like
            that. You might, that is cool. Good for you.
       
              throwawaymaths wrote 7 hours 9 min ago:
              the line noise is really ~only there for dangerous stuff, where
              slowing down a little bit (both reading and writing) is probably
              a good idea.
              
              as for the dots, if you use zig quite a bit you'll see that dot
              usage is incredibly consistent, and not having the dots will feel
              wrong, not just in an "I'm used to it sense/stockholm syndrome"
              but you will feel for example that C is wrong for not having
              them.
              
              for example, the use of dot to signify "anonymous" for a struct
              literal.  why doesn't C have this?  the compiler must make a
              "contentious" choice if something is a block or a literal.  by
              contentious i mean the compiler knows what its doing but a quick
              edit might easily make you do something unexpected
       
              pjmlp wrote 7 hours 49 min ago:
              Despite all bashes that I do at C, I would be happy if during the
              last 40 years we had gotten at least fat pointers, official
              string and array vocabulary types (instead of everyone getting
              their own like SDS and glib), namespaces instead of
              mylib_something, proper enums (like enum class in C++, enums in
              C# and so forth), fixing the pointer decay from array to
              &array[0], less UB.
              
              While Zig fixes some of these issues, the amount of @ feels like
              being back in Objective-C land and yeah too many uses of dot and
              starts.
              
              Then again, I am one of those that actually enjoys using C++,
              despite all its warts and the ways of WG21 nowadays.
              
              I also dislike the approach with source code only libraries and
              how importing them feels like being back in JavaScript CommonJS
              land.
              
              Odin and C3 look interesting, the issue is always what is going
              to be the killer project, that makes reaching for those
              alternatives unavoidable.
              
              I might not be a language XYZ cheerleeder, but occasionally do
              have to just get my hands dirty and do the needfull for an happy
              customer, regardlees of my point of view on XYZ.
       
              codethief wrote 13 hours 5 min ago:
              > Yeah but what is up with all that "." and "@"
              
              "." = the "namespace" (in this case an enum) is implied, i.e. the
              compiler can derive it from the function signature / type.
              
              "@" = a language built-in.
       
                johnisgood wrote 12 hours 47 min ago:
                I know what these are, but they are noise to me.
       
                  kprotty wrote 4 hours 57 min ago:
                  C++'s `::` vs Zig's `.`
                  
                  C++'s `__builtin_` (or arguably `_`/`__`) vs Zig's `@`
       
                    johnisgood wrote 4 hours 18 min ago:
                    I hate C++, too.
       
                  Simran-B wrote 5 hours 52 min ago:
                  It's not annotation noise however, it's syntax noise.
       
                    johnisgood wrote 3 hours 52 min ago:
                    Thanks for the correction. Is it really not "annotation"?
                    What makes the difference?
       
                      Simran-B wrote 54 min ago:
                      You're not providing extra information to the compiler,
                      clarifying the intent, but merely follow the requirements
                      of the language when writing . to infer the type or @ to
                      use a built-in function.
       
                  pyrolistical wrote 6 hours 13 min ago:
                  It is waaaaaaay less noisy than c++
                  
                  C syntax may look simpler but reading zig is more comfy bc
                  there is less to think about than c due to explicit
                  allocator.
                  
                  There is no hidden magic with zig. Only ugly parts. With
                  c/c++ you can hide so much complexity in a dangerous way
       
                    johnisgood wrote 4 hours 17 min ago:
                    FWIW: I hate C++, too.
       
            flohofwoe wrote 14 hours 1 min ago:
            Yes, that's a good solution for this 'extreme' example. But in
            other cases I think the compiler should make better use of the
            available information to reduce 'redundant casting' when narrowing
            (like the fact that the result of `a & 15` is guaranteed to fit
            into an u4 etc...). But I know that the Zig team is aware of those
            issues, so I'm hopeful that this stuff will improve :)
       
              hansvm wrote 9 hours 34 min ago:
              This is something I used to agree with, but implicit narrowing is
              dangerous, enough so that I'd rather be more explicit most of the
              time nowadays.
              
              The core problem is that you're changing the semantics of that
              integer as you change types, and if that happens automatically
              then the compiler can't protect you from typos, vibe-coded
              defects, or any of the other ways kids are generating
              almost-correct code nowadays. You can mitigate that with other
              coding patterns (like requiring type parameters in any
              potentially unsafe arithmetic helper functions and banning
              builtins which aren't wrapped that way), but under the swiss
              cheese model of error handling it still massively increases your
              risky surface area.
              
              The issue is more obvious on the input side of that expression
              and with a different mask. E.g.:
              
                const a: u64 = 42314;
                const even_mask: u4 = 0b0101;
                a & even_mask;
              
              Should `a` be lowered to a u4 for the computation, or `even_mask`
              promoted, or however we handle the internals have the result
              lowered sometimes to a u4? Arguably not. The mask is designed to
              extract even bit indices, but we're definitely going to only
              extract the low bits. The only safe instance of implicit
              conversion in this pattern is when you intend to only extract the
              low bits for some purpose.
              
              What if `even_mask` is instead a comptime_int? You still have the
              same issue. That was a poor use of comptime ints since now that
              implicit conversion will always happen, and you lost your
              compiler errors when you misuse that constant.
              
              Back to your proposal of something that should always be safe:
              implicitly lowering `a & 15` to a u4. The danger is in using it
              outside its intended context, and given that we're working with
              primitive integers you'll likely have a lot of functions floating
              around capable of handling the result incorrectly, so you really
              want to at least use the _right_ integer type to have a little
              type safety for the problem.
              
              For a concrete example, code like that (able to be implicitly
              lowered because of information obvious to the compiler) is often
              used in fixed-point libraries. The fixed-point library though
              does those sorts of operations with the express purpose of having
              zeroed bits in a wide type to be able to execute operations
              without loss of precision (the choice of what to do for the final
              coalescing of those operations when precision is lost being a
              meaningful design choice, but it's irrelevant right this second).
              If you're about to do any nontrivial arithmetic on the result of
              that masking, you don't want to accidentally put it in a helper
              function with a u4 argument, but with implicit lowering that's
              something that has no guardrails. It requires the programmer to
              make zero mistakes.
              
              That example might seem a little contrived, and this isn't
              something you'll run into every day, but every nontrivial project
              I've worked on has had _something_ like that, where implicit
              narrowing is extremely dangerous and also extremely easy to
              accidentally do.
              
              What about the verbosity? IMO the point of verbosity is to draw
              your attention to code that you should be paying attention to. If
              you're in a module where implicit casting would be totally fine,
              then make a local helper function with a short name to do the
              thing you want. Having an unsafe thing be noisy by default feels
              about right though.
       
                throwawaymaths wrote 7 hours 10 min ago:
                you could give the wrapper function a funny name like
                @"sign-cast" to force the eye to be drawn to it.
       
          skywal_l wrote 14 hours 5 min ago:
          Maybe with the new x86 backend we might see some performance
          differences between C and Zig that could definitely be attributed
          solely to the Zig project.
       
            saagarjha wrote 13 hours 54 min ago:
            I would be (pleasantly) surprised if Zig could beat LLVM's codegen.
       
              Zambyte wrote 8 hours 38 min ago:
              So would the Zig team. AFAIK, they don't plan to (and have said
              this in interviews). The plan is for super fast compilation and
              incremental compilation. I think the homegrown backend is mainly
              for debug builds.
       
          Retro_Dev wrote 14 hours 9 min ago:
          Ahh perhaps I need to clarify:
          
          I don't love the noise of Zig, but I love the ability to clearly
          express my intent and the detail of my code in Zig. As for
          arithmetic, I agree that it is a bit too verbose at the moment.
          Hopefully some variant of [1] will fix this.
          
          I fully agree with your TL;DR there, but would emphasize that gaining
          the same optimizations is easier in Zig due to how builtins and
          unreachable are built into the language, rather than needing gcc and
          llvm intrinsics like __builtin_unreachable() - [2] It's my dream that
          LLVM will improve to the point that we don't need further annotation
          to enable positive optimization transformations. At that point
          though, is there really a purpose to using a low level language?
          
   URI    [1]: https://github.com/ziglang/zig/issues/3806
   URI    [2]: https://gcc.gnu.org/onlinedocs/gcc-4.5.0/gcc/Other-Builtins....
       
            matu3ba wrote 12 hours 49 min ago:
            > LLVM will improve to the point that we don't need further
            annotation to enable positive optimization transformations
            
            That is quite a long way to go, since the following formal
            specs/models are missing to make LLVM + user config possible:
            
            - hardware semantics, specifically around timing behavior and (if
            used) weak memory
            
            - memory synchronization semantics for weak memory systems with
            ideas from “Relaxed Memory Concurrency Re-executed” and
            suggested model looking promising
            
            - SIMD with specifically floating point NaN propagation
            
            - pointer semantics, specifically in object code (initialization),
            se- and deserialization, construction, optimizations on pointers
            with arithmetic, tagging
            
            - constant time code semantics, for example how to ensure data
            stays in L1, L2 cache and operations have constant time
            
            - ABI semantics, since specifications are not formal
            
            LLVM is also still struggling with full restrict support due to
            architecture decisions and C++ (now worked on since more than 5
            years).
            
            > At that point though, is there really a purpose to using a low
            level language?
            
            Languages simplify/encode formal semantics of the (software) system
            (and system interaction), so the question is if the standalone
            language with tooling is better than state of art and for what use
            cases.
            On the tooling part with incremental compilation I definitely would
            say yes, because it provides a lot of vertical integration to
            simplify development.
            
            The other long-term/research question is if and what code synthesis
            and formal method interaction for verification, debugging etc would
            look like for (what class of) hardware+software systems in the
            future.
       
              eptcyka wrote 9 hours 43 min ago:
              For constant time code, it doesn’t matter too much if data
              spills out of a cache, constant time issues arise from compilers
              introducing early exits which leaves crypto open to timing
              attacks.
       
                matu3ba wrote 3 hours 58 min ago:
                Thanks for the info. Do you have a good overview on what other
                hardware properties or issues are relevant?
       
            flohofwoe wrote 14 hours 4 min ago:
            Yeah indeed. Having access to all those 'low-level tweaks' without
            having to deal with non-standard language extensions which are
            different in each C compiler (if supported at all) is definitely a
            good reason to use Zig.
            
            One thing I was wondering, since most of Zig's builtins seem to map
            directly to LLVM features, if and how this will affect the future
            'LLVM divorce'.
       
              Retro_Dev wrote 13 hours 59 min ago:
              Good question! The TL;DR as I understand it is that it won't
              matter too much. For example, the self-hosted x86_64 backend
              (which is coincidentally becoming default for debugging on linux
              right now - [1] ) has full support for most (all?) builtins. I
              don't think that we need to worry about that.
              
              It's an interesting question about how Zig will handle additional
              builtins and data representations. The current way I understand
              it is that there's an additional opt-in translation layer that
              converts unsupported/complicated IR to IR which the backend can
              handle. This is referred to as the compiler's "Legalize" stage.
              It should help to reduce this issue, and perhaps even make
              backends like [2] possible :)
              
   URI        [1]: https://github.com/ziglang/zig/pull/24072
   URI        [2]: https://github.com/xoreaxeaxeax/movfuscator
       
       
   DIR <- back to front page