[HN Gopher] Why I rewrote my Rust keyboard firmware in Zig: cons...
       ___________________________________________________________________
        
       Why I rewrote my Rust keyboard firmware in Zig: consistency,
       mastery, and fun
        
       Author : iansinnott
       Score  : 607 points
       Date   : 2021-03-07 08:42 UTC (14 hours ago)
        
 (HTM) web link (kevinlynagh.com)
 (TXT) w3m dump (kevinlynagh.com)
        
       | axepeartree wrote:
       | Wrote a version of the loop using an enum:
       | 
       | https://play.rust-lang.org/?version=stable&mode=debug&editio...
        
       | dilap wrote:
       | Here's another example of the coolness of Zig's comptime code
       | execution:
       | 
       | https://github.com/ziglang/zig/commit/0808d98e10c5fea27cebf9...
       | 
       | That's a generic container class (similar to vector in C++ or
       | List in C#). But! With a twist!
       | 
       | It stores structs in "column major" order in memory (e.g., if a
       | struct had two fields A and B, then in-memory layout would be
       | A...AB...B), and you can idiomatically and efficiently get a a
       | slice of the values of each column.
       | 
       | I.e., it's a datastructure that automatically applies the struct-
       | of-arrays optimization:
       | 
       | https://en.m.wikipedia.org/wiki/AoS_and_SoA#Structure_of_Arr...
       | 
       | And the code to do it is straightforward, normal Zig.
       | 
       | Pretty awesome stuff!
        
         | simias wrote:
         | I admit that I'm a Rust fanboy so it probably wraps my view a
         | bit, but from personal experience I don't consider unlimited
         | compile-time code execution to be a completely good thing.
         | 
         | Yes, it makes the language more approachable, yes it makes it
         | easier for people who aren't familiar with the language to
         | understand what's going on. That's nice, but that's not
         | critically important IMO. It's nice if you want to impress
         | people on HN, but if you use the language day-to-day you'll get
         | over that stuff pretty quickly.
         | 
         | On the other hand this type of extreme customization means that
         | even for somebody very familiar with the language you still
         | have to be on your toes because innocuous looking code could
         | behave surprisingly due to comptime shenanigans. On the other
         | hand languages with a more rigid structure may end up being
         | more verbose but that leads to code that a proficient coder can
         | unambiguously understand without having to mentally expand
         | comptime blocks or macros.
         | 
         | This is effectively the metaprogramming equivalent of the
         | statically vs dynamically typed debate. Yes, dynamic code is
         | easier to write but it can be harder to maintain and leads to
         | worse compiler diagnostics and generally requires more unit
         | tests to validate that it's doing the right thing. I think
         | macros/comptime behave similarly when compared to stricter,
         | more limited metaprograming like Rust's generics system.
         | 
         | Rust has macros too of course, but they're a pain to write in
         | my experience, and I'd almost argue that it's a feature. You
         | only use them if you really have to, and after careful
         | consideration, or at least that's how I use them.
        
           | pron wrote:
           | Zig doesn't have unlimited compile-time execution and what it
           | has is strictly weaker than Rust's macros [1]. Rather, it has
           | one carefully designed construct that is both very simple and
           | very expressive, and yet isn't as weird or as dangerous as
           | macros. It is not "extreme customisation" but just the right
           | amount to make the language both simple and expressive
           | _without_ extreme measures like macros. Zig accepts that
           | macros are problematic, and shows how far you can go without
           | them altogether. OTOH, while macros are common enough in Rust
           | that while you may not write them yourself all the time, you
           | do use them frequently.
           | 
           | I guess that there is some small truth to your allusion to
           | the static-vs-dynamic debate, but Zig does give you errors at
           | compile-time, and elaborate things it can check at runtime
           | are much easier to express than in Rust. But I would say that
           | Rust is a language in a well-known tradition, and is clearly
           | a "cleaned up C++", while Zig is something that we haven't
           | seen before. It is not dynamic in the same sense as dynamic
           | languages -- you get the checks done at compile-time -- but
           | it is not part of the familiar tradition of typed languages
           | or even any low-level language.
           | 
           | I'm not a devout minimalist, but when it comes to low-level
           | programming in particular, language simplicity is a very
           | important feature, and before Zig it wasn't clear it was
           | achievable at all in low-level languages without
           | significantly compromising on expressivity and safety.
        
             | simias wrote:
             | (I wanted to read your [1] reference but you seem to have
             | forgotten to add it.)
             | 
             | I agree that comptime is not the same thing as Rust macros,
             | I mostly mentioned Rust macros because I felt it was a
             | gotcha to my argument since my criticisms of Zig's comptime
             | could be levied at Rust's macros.
             | 
             | To be a little more specific in my criticism, the fact that
             | Zig implements generics with comptime is a bit of a red
             | flag for me. I worry, perhaps unreasonably, that it's going
             | to lead to fragmentation in the way generics are handled in
             | various libraries, leading to headaches and
             | incompatibilities. It is a smart solution, but I wonder if
             | it's a pragmatical solution.
             | 
             | It's definitely an interesting approach at any rate, it's
             | great to see all this creativity in systems language. I
             | don't want to sound too critical of Zig, it's a cool
             | language.
        
               | pron wrote:
               | Oops, sorry. I meant that macros are referentially
               | opaque, and therefore strictly more expressive than
               | comptime: https://news.ycombinator.com/item?id=26375027
               | 
               | > my criticisms of Zig's comptime could be levied at
               | Rust's macros.
               | 
               | Except that comptime is nothing at all like macros, even
               | though, as it turns out, it can replace enough of their
               | use to make them unnecessary in low-level languages.
               | 
               | > I worry, perhaps unreasonably, that it's going to lead
               | to fragmentation in the way generics are handled in
               | various libraries, leading to headaches and
               | incompatibilities.
               | 
               | But generics in Zig are just functions, and so the
               | problem should be no better but no worse than any API.
               | comptime is drastically less "crazy" or "weird" or hard
               | to make compatible than macros, which Zig doesn't have at
               | all.
        
             | smt1 wrote:
             | I'd say Rust is much more like Ocaml (with very different
             | memory management) than anything related to C++ (in fact,
             | if you unlearn C++, or know any ML-ish language, idiomatic
             | Rust becomes significantly easier). Ownership types are
             | probably Rust's main difference relative to any systems
             | language, I think the first attempt to bring it to a C-ish
             | language is probably Verona:
             | https://microsoft.github.io/verona/explore.html (though
             | it's very immature)
        
               | pron wrote:
               | Let's say it's a love-child of ML and C++.
        
               | otabdeveloper4 wrote:
               | C++ is closer to ML than to C.
        
           | chubot wrote:
           | But isn't Rust basically getting constexpr from C++? I saw
           | that in some recent release notes:
           | https://lobste.rs/s/lng3c0/announcing_rust_1_50_0
           | 
           | constexpr is basically making more of C++ available at
           | compile time -- e.g. lots of C++11, 14, 17, and 20 are just
           | allowing more of the language at compile time. Including STL,
           | allocators, etc.
           | 
           | Zig simplifies everything by designing in comptime up front,
           | rather than gradually opening it up with ad hoc rules over
           | 10+ years.
           | 
           | My understanding is that Rust is going down the same path as
           | C++. So you're going to have 2 kinds of macros AND comptime-
           | like/constexpr-like compile time execution.
        
             | steveklabnik wrote:
             | Yes, something very similar to constexpr.
             | 
             | > Zig simplifies everything by designing in comptime up
             | front, rather than gradually opening it up with ad hoc
             | rules over 10+ years.
             | 
             | I am not 100% sure that I agree with these
             | characterizations, personally, but they're both valid
             | strategies for sure.
        
               | girvo wrote:
               | Regardless of the particular trade-offs the various
               | languages under discussion are choosing to make, it's
               | exciting to me that more and more languages are adapting,
               | exposing and using "compile time" systems
        
           | lhorie wrote:
           | I don't think comparing zig's comptime with rust macros is
           | actually that apt of a comparison. I tend to think of rust
           | macros as syntactic sugar. Zig's comptime permeates the
           | language thoroughly and in fundamental (and IMHO very
           | pragmatic) ways. Top level const expressions are
           | automatically comptime, modules are comptime structs, static
           | polymorphism is used all over the stdlib (e.g. std.mem.eql),
           | heck std.debug.print is written without special compiler
           | tricks thanks to how cleanly you can use comptime in zig.
           | 
           | I think the idea that zig is a very sharp knife is true, but
           | the overlap of footguns and comptime is not as big as one
           | would think (@fieldParentPtr mistakes comes to mind, but
           | that's sort of about it)
        
             | ifreund wrote:
             | The implementation of the pinned keyword proposed here
             | should fix most if not all @fieldParentPtr() footguns
             | https://github.com/ziglang/zig/issues/7769
        
             | zozbot234 wrote:
             | > Zig's comptime permeates the language thoroughly and in
             | fundamental (and IMHO very pragmatic) ways.
             | 
             | TANSTAAFL. Compile-time evaluation cannot truly "permeate"
             | a language because most practical languages preserve a
             | phase distinction between compile time and runtime. (This
             | phase distinction is somewhat softened, e.g. in interpreted
             | languages as well as in advanced PL's which include such
             | features as dependent types). For a system programming
             | language like Rust which relies on this clear-cut phase
             | separation, macros and proc macros (as well as
             | `const`-marked expressions and functions) are ultimately
             | more elegant.
        
               | [deleted]
        
           | jiofih wrote:
           | Your points are in stark contrast to the reality of the
           | article. The Rust code produced is the one you have to be "on
           | your toes" with due to unnecessary complexity, while Zig,
           | despite the compile time features, is completely
           | straightforward to understand. Maybe a second reading is due.
        
           | twic wrote:
           | > On the other hand this type of extreme customization means
           | that even for somebody very familiar with the language you
           | still have to be on your toes because innocuous looking code
           | could behave surprisingly due to comptime shenanigans.
           | 
           | Could you give some concrete examples of this?
        
             | lhorie wrote:
             | Only thing I can think of is something blowing up when
             | cross-compiling to a different target because there's no
             | code in the static if branch to handle that architecture
             | (e.g. stdlib doesn't officially support WASI). But that's
             | not really comptime's fault per se IMHO; you can break
             | things in similar ways in golang, for example.
             | 
             | Unlike C, zig's comptime doesn't have grammar altering
             | abilities
        
         | p0nce wrote:
         | Same in 2016 with D https://maikklein.github.io/soa-d/
        
           | smt1 wrote:
           | The problem is that all of these languages are at least
           | partially derived from C or C++, where memory layout is
           | inverted relative to something like Fortran (which seemed to
           | get this right from the 1960s) when you consider most cache
           | lines on most processors. Therefore you must either go
           | through hoops yourself tinkering with layout in languages not
           | built for it or add much more complexity to a optimizing
           | compiler (like gcc or llvm). I feel Fortran got this right,
           | and Pascal and C was where languages flipped the norm. I kind
           | of get why they did this, because in the 80s and 90s there
           | were so many varying architectures, the memory wall was a
           | very real thing. Actually Simula60 (a monte carlo language),
           | probably had the right abstraction level (everything is just
           | a block), but this was before things like stacks, heaps,
           | trees, and other data structures were permanently etched into
           | people's brains.
           | 
           | I like how Julia implemented this (but they use Fortran-like
           | defaults, with clear inspiration from matlab, numpy, etc),
           | with a relatively compact set of functions to do any sort of
           | "index ordering":
           | https://julialang.org/blog/2016/02/iteration/
           | 
           | Rust is very much a child of Ocaml (with a lot of idioms form
           | haskell) with much more control of memory than pretty much
           | any other language (which probably makes it better for
           | implementing complex or safety critical things like
           | optimizing compilers or an operating system). Actually,
           | learning any ML language is probably easier than Rust, but
           | you'll get more fluent at a ML-like (expression based)
           | language like Rust. For me, I felt like I understood Rust way
           | more after looking at how rustc works and started unlearned
           | everything I knew about C/C++.
        
             | Bluestein wrote:
             | > things like optimizing compilers or an operating system).
             | 
             | I am on a countdown to a Rust OS ...                 (An OS
             | implemented in Rust).-
        
               | andrewflnr wrote:
               | I assume you mean a widely-used one. There's a handful in
               | development. Have you looked at Redox? :)
               | 
               | Ed: I really don't mean this to be snarky, even though
               | looking back it kind of sounds like it. Sometimes you
               | just have to stop fretting about phrasing, slap a smiley
               | on and ship the thing...
        
               | Bluestein wrote:
               | Not understood as snarky at all ...
               | 
               | Appreciate the comment, on the contrary ...
               | 
               | Redox FTW! (Besides, microkernels, which are nice ...)
               | 
               | (I take it someone from the Redox team saw this and took
               | to the downvotes tough :)
        
           | egeozcan wrote:
           | D is another gem (while established in its own circle, it's
           | not mainstream, therefore a "gem") I'd suggest people to give
           | a try. Also Nim for crazy powerful compile-time features.
        
             | p0nce wrote:
             | Absolutely, Nim and Zig have proper CTFE and you can parse
             | JSON at compile-time with a normal parser code in all 3 of
             | them IIRC.
        
               | dunefox wrote:
               | This all seems cool until you realise that Lisp had full
               | compile time access to the whole language for decades.
        
               | nepeckman wrote:
               | There's more to a language than just features and
               | functionality though. Why does Clojure have adoption
               | despite being the least powerful Lisp, created decades
               | after Common Lisp? Its stdlib, default data structures,
               | and even syntax make it compelling (yes, sometimes having
               | a deliminiter other than parentheses is helpful, and no
               | being able to define new syntax via macros is not the
               | same thing because defaults are important). Its not bad
               | to rehash language features that have existed for 50
               | years if the resulting language has other compelling
               | qualities.
        
               | [deleted]
        
               | p0nce wrote:
               | Yes. But you could see the new crop of native languages
               | as: as close to LISP as possible without codegen in the
               | runtime. There is this 2x gap between JIT and AOT. (EDIT:
               | Other than that I'm not sure if any of the new kids can
               | introspect on the content - lines of codes - of a
               | function, like LISP would).
        
               | pjmlp wrote:
               | Lisps support AOT compilation since several decades,
               | image tree shaking, and actually having a compiler in the
               | runtime allows for tooling that most languages lack.
        
               | p0nce wrote:
               | Can you avoid having a compiler in the runtime?
        
               | michaelcampbell wrote:
               | Why does a cool thing seem less cool when/if you realize
               | something else is also cool?
        
               | belval wrote:
               | Because if lisp has it then we are not moving forward so
               | much as people are creating new languages with the same
               | features.
               | 
               | Don't know if that actually applies to Zig/D, but that's
               | how I read it.
        
               | cle wrote:
               | It's a major step forward if it fixes one of the huge
               | problems with Lisps: adoption.
        
               | Hizonner wrote:
               | If the problem is that languages don't have enough
               | adoption, then spreading people out over even more
               | languages is _the opposite of useful_.
        
               | cle wrote:
               | Depends on what you think the cause of the lack of
               | adoption is. If you think it can be fixed by doubling
               | down on existing languages, then sure. But I don't know
               | if that's addressing the cause (because I don't know the
               | cause).
               | 
               | I use Lisps all the time and love them. But I recognize
               | that most people don't like them. At some point we have
               | to meet people where they are, if we want to have broad
               | impact. Ideas on their own aren't good enough, they need
               | to be packaged up in the right way, approachable to the
               | right people, marketed appropriately, etc. The tech
               | itself is just a small part of what it takes for an idea
               | to create impact.
        
               | unwind wrote:
               | I disagree, programming language ergonomics/"feel" is
               | real and Lisp's don't fit everyone.
        
               | lhorie wrote:
               | The revolutionary thing about lisp macros was that you
               | could freely manipulate _code_ as data.
               | 
               | What zig offers is completely different: it allows you
               | freely manipulate _types_ as data, while disallowing the
               | ability to manipulate syntax.
        
         | thechao wrote:
         | I took one look at this and realized you could do the same
         | thing in C++17 using the perfect-flat-reflection.
        
       | sly010 wrote:
       | Looks like the core problem is not rust, but the device
       | abstractions. There is no reason peripherals that are literally
       | the same in hardware should not be the same type in software.
        
       | _pmf_ wrote:
       | Rust is the systems language of choice for overengineering
       | Silicon Valley procrastinators.
        
         | higerordermap wrote:
         | Panicking on OOM is not probably overengineering.
        
       | spiritplumber wrote:
       | you know what you doing!
        
       | simonhamp wrote:
       | This sold me:
       | 
       |  _Even though I'm only a dozen hours in, I feel like I can
       | already be productive with Zig without an Internet connection._
        
         | himujjal wrote:
         | Okay. I was about to write a Svelte compiler in C. I am
         | choosing Zig. Thank you! I first wanted to choose Rust but I
         | never liked that language. I feel that it inhibits thought
         | process. I was making yet another project with Rust. I didn't
         | continue because I felt if in future 25 people were to work on
         | this, only 5 could actually write code without scratching their
         | heads. Rust surely does solve bugs, but does not let me think
         | with freedom. I am on the Zig train.
         | 
         | I loved Nim and considered it to be my second choice. But
         | something pulled me off. Maybe the GC. I still don't understand
         | though why Nim is not as popular as Go. I mean it has good
         | compile times, good performance and the best part, it compiles
         | to C.
         | 
         | Why these languages and not Go or <insert language here>?
         | Simple. Rust, Nim and Zig have `extern` with a C ABI. People
         | don't realize how big this feature is. Two way communication
         | with C.
        
           | 59nadir wrote:
           | > I loved Nim and considered it to be my second choice. But
           | something pulled me off. Maybe the GC.
           | 
           | I was under the impression that the Nim GC was basically
           | entirely optional. Is that not the case? They also added a
           | bunch of other ways to get basically the same thing, did they
           | not? I remember atomic reference counting and so on being
           | presented as basically a drop-in way, but maybe I
           | misunderstood.
        
             | cb321 wrote:
             | You are probably thinking of ARC which is _automatic_ (
             | _not_ "atomic") reference counting a la Bacon/Dingle [1]
             | via Nim's --gc:arc/orc.
             | 
             | Because some people do not consider any "reference
             | counting" to be "garbage collection, I think "automatic
             | memory management" a more clear term.
             | 
             | Regardless, you can turn off all automatic memory mgmt with
             | --gc:none, though the stdlib won't cooperate much.
             | --gc:arc/orc is fairly practical, though still not quite as
             | bug free as, say, --gc:markAndSweep. Nim gives you many
             | options for many things.
             | 
             | Ref counts have a reputation for being slower than
             | mark/sweep/generational/etc AMMs, but (with a lot of
             | compiler assistance/optimizations) they seem at least as
             | fast (and maybe faster) in practice, at least with Nim
             | which usually performs like C/Rust/etc.
             | 
             | [1] https://researcher.watson.ibm.com/researcher/files/us-
             | bacon/...
        
               | 59nadir wrote:
               | How does Nim deal with passing allocators to things? Does
               | the standard library have a story for this?
        
               | cb321 wrote:
               | Someone just asked _almost_ this question in the Nim
               | Forum [1] (coincidentally, unless that was you!). So, you
               | may want to follow that conversation. (EDIT: It may be
               | more global than you are used to in Zig (or than you need
               | /want), but might also be "good enough", depending.)
               | 
               | [1] https://forum.nim-lang.org/t/7588
        
           | higerordermap wrote:
           | > I still don't understand though why Nim is not as popular
           | as Go.
           | 
           | Young ecosystem, language still in flux, doesn't have name of
           | big company.
        
             | [deleted]
        
             | cb321 wrote:
             | I find that Nim compiles run as fast or faster than Go
             | compiles and Nim code compiled with optimization tends to
             | runs much faster than Go code.
        
               | srean wrote:
               | Gaining popularity is quite an exceptional event. Nim is
               | a fine language although some choices are quite
               | opinionated but such is the case for Go as well.
               | 
               | D compiles (DMD compiler) pretty damn fast too. Which is
               | exactly faster to compile D or Go depends on the nod in a
               | horse race.
        
               | higerordermap wrote:
               | None of that contradicts my points.
        
               | cb321 wrote:
               | It was meant only to supplement the discussion.
        
       | sammorrowdrums wrote:
       | I was wondering in the P0, P1 issue, why the auther didn't use an
       | Enum, Enums are the easiest way to handle that sort of mixed
       | type.
       | 
       | On my phone something like:                   enum Pin {
       | Pin0(P0, usize)            Pin1(P1, usize)         }
       | 
       | Assuming that would work in the embedded device which I don't
       | know.
       | 
       | I know the above doesn't affect the general point, but I have
       | found lots of people struggling with Rust haven't yet discovered
       | that you can easily combine types in Enums.
        
         | lynaghk wrote:
         | Author here. I considered the enum "solution" but found the
         | match on usize tuples to be clearer because it requires less
         | code. Introducing an enum doesn't help because it neither:
         | 
         | + helps better model the domain: P0 and P1 are already device
         | ports, wrapping doesn't clarify anything,
         | 
         | + nor does it buy you safety; arguably I'd say it makes it less
         | safe, since the real risk with this sort of code is that you
         | fat finger when copy/pasting between the electrical schematic
         | and the firmware, so by adding extra wrapping you further
         | obscure the pin assignments.
        
           | sammorrowdrums wrote:
           | Ah, sorry I added further comment to above before seeing
           | this. I can see why you would say that, but does the
           | arbitrary integer risk of the 0, 1, _ match not create even
           | greater risk? Compiler cannot help at all.
           | _ => {}
        
             | lynaghk wrote:
             | First, we should be clear that this is my goofy hobby
             | keyboard project --- if I was concerned about safety, I'd
             | have written it in MISRA C or something =D
             | 
             | There are many things about this project that a compiler
             | can't help me with. I had to read about all of the pinouts
             | from a PDF, draw them on a circuit board, and _then_ map
             | those pins in the firmware.
             | 
             | The code only deals with that last part, and in this
             | particular example I decided that it was safer on the whole
             | for the code to be obvious (easy to read + compare with
             | hardware schematic) than to go through contortions with
             | types to make some things more checkable by computer but
             | less-checkable by human inspection.
             | 
             | The main risk here is not passing the wrong value to this
             | match, it's fat fingering the transcription from the
             | schematic.
        
               | sammorrowdrums wrote:
               | Makes sense, and I guess given the above your conclusion
               | not to use Rust seems like the correct call for your
               | situation. Thanks for explaining further. It's true you
               | say much of this in the article.
        
               | josephg wrote:
               | Yeah I think people like to match the values of a
               | programming language to ourselves as people. (And by
               | values I mean, correctness, speed, expressiveness,
               | velocity, etc). Like, I'll pick the values which I like
               | the most and find languages which match the values I
               | aspire towards. Eg maybe I like the idea of my programs
               | being strongly typechecked without sacrificing speed - so
               | I program in rust. Then I write clean rust code even when
               | I'm doing a quick and dirty prototype.
               | 
               | The better & harder approach matches a language's virtues
               | to the problem at hand. Strict correctness doesn't matter
               | much for a hobby program like this - moving fast and
               | having fun are probably more important here. Expressing
               | _that_ with your tools might mean using Zig, or using
               | rust but being sloppy with allocations and .clone()
               | because it's fine. Or treating rust like C and using
               | unsafe everywhere. "Making invalid states inexpressible"
               | isn't that important in a fun side project - unless maybe
               | that's fun for _you_!
               | 
               | The right question isn't "What is my favourite tool?".
               | It's "If this project had a soul, how would it want to
               | express itself through code?"
        
               | autarch wrote:
               | > The right question isn't "What is my favourite tool?".
               | It's "If this project had a soul, how would it want to
               | express itself through code?"
               | 
               | I somewhat disagree. What you're saying is basically
               | "pick the right tool for the job", but more poetically
               | (not a criticism, BTW, I like your phrasing).
               | 
               | But what I think this is missing is that the "right" tool
               | is at least in part based on your favorite tools are.
               | 
               | Or to put it differently, using a tool that may be
               | suboptimal for the job, but which you know extremely
               | well, may be better than using a tool you don't know
               | which is optimal for the job.
               | 
               | Of course, this is much less of an issue for hobby
               | projects, and if one of your goals for a project is
               | "learn new stuff" (a goal I often have for my own hobby
               | projects), then picking the optimal language may be
               | exactly the right choice. You get to learn a new thing
               | while not fighting with the language.
        
               | ywei3410 wrote:
               | I like that last sentence; is it something you came up
               | with yourself or is it a quote?
        
               | josephg wrote:
               | Thanks - that's all me.
        
             | sammorrowdrums wrote:
             | Oh and also, if the whole separate types for each mapping
             | thing is so annoying and wrapping the tuples is unhelpful,
             | which is understandable, then perhaps safer to wrap just
             | the pins in emums and then always access them via the enum?
             | 
             | Then it would be (enum type, usize)
             | 
             | Then also at times where all Pins need handling,
             | the.exhaustive matching can reduce risks of not doing so?
        
         | sammorrowdrums wrote:
         | And the author's actual rust solution using the 0, 1 as a proxy
         | for the port with a match to resolve it, has effectively
         | implemented the enum solution but without leveraging the
         | language to do it, which means incorrect programs could use any
         | integer value and the compiler wouldn't know, and the author
         | has to handle that case, which they hack with empty block for
         | that case. (relying on knowing they haven't in their own code,
         | basically undoing the whole point of Rust compiler strictness)
         | _ => {}
        
       | [deleted]
        
       | jbandela1 wrote:
       | I am a long-time C++ developer and have been playing around with
       | Rust recently. I really love the language, but one thing I miss
       | about Rust from C++ is the ability to manipulate and play around
       | with types. The features that really enable this are variadic
       | templates and generic lambdas. I wish Rust would get something
       | like them in the future.
       | 
       | In C++17, the author's issues with trying to do port and pin with
       | different pin types, has a pretty elegant solution in C++.
       | 
       | Here is a toy solution.                   #include <iostream>
       | #include <tuple>                  /*         for (port, pin) in
       | &[(P0, 10), (P1, 7), ...] {
       | port.pin_cnf[pin].write(|w| {
       | w.input().disconnect();                 w.dir().output();
       | w             });         }         */                  template
       | <typename PinTuple, typename F>         void
       | for_each_port_and_pin(PinTuple& tuple, F f) {         std::apply(
       | [&](auto&&... p) {                 auto apply_pin = [&](auto& t)
       | { std::apply(f, t); };                 (apply_pin(p), ...);
       | },             tuple);         }                  struct P0 {
       | void write(int pin) {             std::cout << "Writing on Port
       | P0, pin " << pin << "\n";         }         };         struct P1
       | {         void write(int pin) {             std::cout << "Writing
       | on Port P1, pin " << pin << "\n";         }         };
       | int main() {         auto ports_and_pins =
       | std::tuple{std::tuple{P0{}, 10}, std::tuple{P1{}, 7}};
       | for_each_port_and_pin(ports_and_pins,
       | [](auto& port, int pin) { port.write(pin); });         }
       | 
       | Runnable godbolt link
       | 
       | https://gcc.godbolt.org/z/dcxnTo
        
         | pharmakom wrote:
         | I think Rust macros are actually more powerful than templates
        
           | bjz_ wrote:
           | They are differently powerful. Rust's macros can let you
           | extend the syntax and do context-free code generation, where
           | as C++ can let you to type-directed code generation. You can
           | do the latter in Rust using trait dispatch, but it's more
           | awkward and less expressive than what C++ has.
        
             | pas wrote:
             | It's not a panacea, but enum_dispatch seems to help a lot:
             | https://docs.rs/enum_dispatch/0.3.5/enum_dispatch/
        
         | surajrmal wrote:
         | I mostly skimmed the original article but in both cases, why
         | not use a enum in rust and a std::variant + std::visit in c++?
        
         | jll29 wrote:
         | Impressive that you were able to pull it off in C++17 like this
         | (and extra kudos for the live link), but the resulting code
         | (both template and invocation) looks very cryptic - except for
         | the part commented out; that one is much more pleasant to the
         | eye.
        
           | iamthemalto wrote:
           | Not sure if I'm missing a joke, but the part commented out is
           | in Rust (not C++) from the original post?
        
       | michaelcampbell wrote:
       | I think Zig looks very interesting; I just don't do that sort of
       | programming much so I have a hard time finding an interesting (to
       | me) hobby project to try these languages on. But I see this sort
       | of sentiment with `go` a lot:
       | 
       | > However, I ended up finding these absences liberating -- this
       | is where the "fun" comes in.
       | 
       | This just feels like a weird "it's not a bug, it's a feature!"
       | Stockholm syndrome. "I actually LIKE that it doesn't do what I
       | want it to do." Baffles me.
        
         | 59nadir wrote:
         | I've spent a fair amount of time learning C++ throughout the
         | years (beginning in 2001) and Haskell. I mention these as
         | examples of languages that seem to have no end to them.
         | 
         | Using and learning Zig after about 1.5-2 years is many times
         | more productive because I've already found the edges of the
         | language and I'm able to focus more on my actual task instead
         | of debugging my language knowledge. I say this not only in the
         | context of a work task but for things of every scope.
         | 
         | I personally have never felt like I "knew" Haskell, despite
         | working with it and being able to create solutions in it. I
         | know parts of it and there are big parts of it looming
         | somewhere in the distance. There are language extension names
         | that actually give me anxiety when I see them at the top of
         | source files. I think the same feeling is common in C++ users,
         | though admittedly I don't have my finger on that pulse anymore.
         | 
         | Is it possible to create finished solutions that are shorter
         | and where your solution seems to fit the problem more directly
         | in these languages? Yeah, I guess, but at the same time I'm not
         | sure it's ever finished and the anxiety this kind of thing can
         | cause when using these languages I think is underestimated.
         | 
         | There's a tension that this creates where you have to
         | intentionally limit yourself (and suggest others to limit
         | themselves, see "Simple Haskell") because you feel the weight
         | of all of this complexity on you constantly.
         | 
         | "Maybe if we read this 'Thinking in Types' book we can cut down
         | on the amount of lines in our code base, guys?". Meanwhile
         | people using intentionally small languages are provably super
         | productive and you see a lot of coping mechanisms in the
         | communities with these ever-growing colossi languages.
         | 
         | (With all of this said I'm not entirely sure I'd be sold on Zig
         | if it didn't at least have tagged unions and matching/unpacking
         | of them via `switch`. There are language features that I've
         | grown so accustomed to that I feel like I can barely program
         | without them.)
        
         | flohofwoe wrote:
         | The absence of specific features for solving specific problems
         | makes the language surface very small. Which is liberating in
         | the sense that you don't subconsciously look for the "right
         | language feature" for the current coding problem you're trying
         | to solve.
         | 
         | This sentence from the blog post is key:
         | 
         | "For example, it's now quite clear to me that Rust is a
         | language which has a dedicated feature for everything."
         | 
         | You always have that nagging doubt in the back of the head that
         | you don't know the language good enough yet. And I never got
         | rid of that feeling in over 20 years of coding C++. Turns out,
         | it's not you, it's the language. Switching to a simpler
         | language like C lets you focus on the problem solving again,
         | not solving language puzzles. Zig is truly a "better C" in that
         | sense, because in a way it is an even simpler language than C
         | (while being much more correct), yet it enables fundamental
         | features that C lacks (like comptime and generics).
        
           | michaelcampbell wrote:
           | > Switching to a simpler language like C lets you focus on
           | the problem solving again, not solving language puzzles
           | 
           | Or, read another way, "with simple language X I have to
           | reimplement the things that language Y comes with"
           | 
           | I guess it's all a matter of what level of abstraction one
           | likes to work with. For me, re-implementing a "Set"
           | implementation (eg: go) yet again doesn't count as
           | "productive" just because I'm typing typing typing more more
           | more.
        
           | afavour wrote:
           | > Switching to a simpler language like C lets you focus on
           | the problem solving again, not solving language puzzles.
           | 
           | But you're solving a puzzle in either case. Either with a
           | solution you created yourself (much more rewarding!) or a
           | premade one you pick off the shelf (higher chance of first
           | time success, often less time involved)
           | 
           | Everything is a trade off.
        
             | flohofwoe wrote:
             | I think libraries are a better place for this sort of code
             | reuse than builtin language features though ;)
        
       | joshgoldman wrote:
       | Why not use micropython?
        
       | littlestymaar wrote:
       | As Rust is getting more and more adoption, it's slowly starting
       | to move away from "hype language" to "boring language used by
       | normies", like every other successful languages once did. That's
       | a pretty good sign actually.
        
         | 59nadir wrote:
         | Is it really boring that you have to learn 20 different
         | features to do 20 different things? There is a certain economy
         | at play with feature sets that the author brings up that has
         | nothing to do with "boring" or not; having to learn a ton of
         | different things and always feeling like there's probably some
         | tailor-made thing you should be using for exactly your problem
         | is not boring, it's just less productive when working.
        
           | littlestymaar wrote:
           | You're missing the point here: I meant "boring" as "it's been
           | six years already, we want new toys". The more it ages and
           | gains adoption (which really skyrocketed in the past two
           | years), the less "cool" Rust will be.
           | 
           | You cannot be "cool" and "mainstream" at the same time.
        
             | 59nadir wrote:
             | I don't disagree about any of what you said here in
             | isolation, but I'm not sure that the complaints that the
             | author has stem from Rust being inherently more popular.
             | 
             | Sure, adding features is generally a sign that you're
             | trying to capture/retain users (I recently learned that
             | weak equality operators were added to JavaScript for this
             | exact reason and Brendan Eich regrets it *a lot*), but at
             | the same time it feels like things could be more cohesive
             | than they are.
             | 
             | But yeah, I agree that hype trains lose momentum when
             | things become more common. With that said, I don't really
             | feel like Rust is popular/common enough to lose hype.
             | Basically no one uses it in production in comparison to the
             | obvious alternatives and it'd be a bit odd for it to lose
             | hype so soon.
        
               | littlestymaar wrote:
               | > but I'm not sure that the complaints that the author
               | has stem from Rust being inherently more popular.
               | 
               | These complaints made the top of HN today, not 5 years
               | ago (when Rust for embedded was way less mature and
               | convenient), for a good reason. And notice that this
               | person isn't advocating that Rust is too complex and you
               | should keep using proven tech like C or Python for doing
               | real stuff, they are advocating using a brand new
               | experimental language, with a radical new design, whose
               | compiler keeps crashing and which still makes breaking
               | changes every once in a while[1]. This specific article
               | is a really good illustration of the hype train moving
               | on.
               | 
               | > but at the same time it feels like things could be more
               | cohesive than they are.
               | 
               | The Rust team attempt to make the feature as cohesive as
               | possible (and compared to a language like C++, they are
               | doing a pretty good job at it) but nothing is perfect.
               | I'm curious if you have specific examples of features
               | that you don't think are cohesive though. (One example I
               | can think of is the old `macro_rule!` vs procedural
               | macros, but maybe there are others).
               | 
               | > With that said, I don't really feel like Rust is
               | popular/common enough to lose hype. Basically no one uses
               | it in production in comparison to the obvious
               | alternatives and it'd be a bit odd for it to lose hype so
               | soon.
               | 
               | Hype is a multi-level thing: the Rust hype is still
               | growing (it still makes the front page of HN almost every
               | day), but the most _avant-garde_ hackers are moving away
               | from it. That 's nothing unexpected.
               | 
               | [1]: I'm not bashing Zig in any way, nobody expect such a
               | recent language to be polished already!
               | 
               | [2]: https://medium.com/code-adventures/farewell-node-
               | js-4ba9e7f3...
        
         | bla3 wrote:
         | "There are only two kinds of languages: the ones people
         | complain about and the ones nobody uses."
         | 
         | But it does seem that the complaining about Rust being a
         | difficult language is happening fairly early in its adoption.
        
           | littlestymaar wrote:
           | Rust took a lot of inspiration from the functional
           | programming world (it has some heavy OCaml inspiration),
           | which albeit very powerful and expressive, also come with a
           | conceptual burden.
           | 
           | Rust is also in the unique position, being low level as well
           | as functional, which means it will always be alien no matter
           | what programming background you have.
           | 
           | In order to guarantee memory and thread safety, it also adds
           | a few restrictions. Some are fundamental (mutable XOR shared,
           | move semantic) others are/were temporary implementation
           | limitations (like lexical lifetimes, or arrays being second
           | class citizens, or the `impl` keyword being usable only in a
           | few cases). In that regard, Rust has become much simpler
           | since it was released 6 years ago, but it still has some
           | margin for progress.
        
             | dnautics wrote:
             | On the balance i find functional languages to be easier
             | than oo languages, so I highly doubt that functional is the
             | reason for complexity. In fact as an FP programmer, I find
             | it's very possible to write zig in a functional style
             | (imports feel like modules), in spite of the oo sugar that
             | zig structs have been endowed with.
        
             | 59nadir wrote:
             | > Rust took a lot of inspiration from the functional
             | programming world (it has some heavy OCaml inspiration),
             | which albeit very powerful and expressive, also come with a
             | conceptual burden.
             | 
             | > Rust is also in the unique position, being low level as
             | well as functional, which means it will always be alien no
             | matter what programming background you have.
             | 
             | Having worked with Haskell and generally being very
             | comfortable with functional programming (whether or not I'm
             | happy about the astronomical complexity of Haskell as a
             | language with extensions is another story) and also having
             | a lower-level background I can tell you that the offerings
             | of Rust as a "functional" language (this is generally so
             | ill-defined that you can call basically anything
             | functional) are extremely slim and that I'd never choose it
             | for any of that.
             | 
             | Tagged unions and `match` expressions might make for a
             | functional language to a lot of people but these things are
             | orthogonal to functional programming. They're nice to have,
             | but to harken back to the original topic; Zig has tagged
             | unions and can `switch` on them to unpack payloads just
             | fine.
             | 
             | Like I said, I think FP is ill-defined but even with that
             | in mind it's a stretch to say that Rust is functional.
             | 
             | Most people, I would hope, would agree that function
             | composition is more an inherent feature of FP and honestly
             | I've never seen a particularly compelling argument for Rust
             | having a reasonably useful solution to that. Stitching
             | together methods is one step, but it only gets you so far
             | and isn't as generally useful as what most ML-descendants
             | would give you.
        
       | eximius wrote:
       | Great article! I'm impressed by the constant praises of Zigs
       | thoughtful ascetic feature design.
       | 
       | That said,
       | 
       | 1. Just seems like the same lack-of-feature/"simple" stockholm
       | syndrome from Go. To each their own. 2. It would seem that the
       | author could take the structure of their Zig application and
       | almost directly port it back to Rust (e.g., their conditional
       | compilation problems go away if you do it at a high enough level)
       | 
       | `inline for` is pretty special, though. I bet.... _now_ someone
       | has written a published macro for it.
        
         | geodel wrote:
         | > It would seem that the author could take the structure of
         | their Zig application and almost directly port it back to Rust
         | 
         | Yup, and this phenomenon has a popular name called 'Rewrite it
         | in Rust' Not sure though, that it has a positive connotation.
         | 
         | > Just seems like the same lack-of-feature/"simple" stockholm
         | syndrome from Go. To each their own.
         | 
         | Huh, I'd say another Rust fanboy obsessed with feature list
         | than working software. You would call it a fair take, right?
        
           | eximius wrote:
           | I'll respond in good faith, but your tone sounds like you're
           | just low-key dissing me.
           | 
           | He originally wrote it in Rust and solved some of his
           | problems when he rewrote it in Zig in a different
           | architecture. The same architecture in Rust would have solved
           | the same problems.
           | 
           | And no, I would not call your second paragraph a fair take.
        
         | p0nce wrote:
         | > `inline for` is pretty special
         | 
         | Again: `inline for` has been in D for more than 10 years.
        
         | loup-vaillant wrote:
         | > _1. Just seems like the same lack-of-feature /"simple"
         | stockholm syndrome from Go._
         | 
         | I don't think I'd say "lol no generics" to Zig... Though I'd
         | have to try to really see whether I'm missing anything (and I
         | think I'd notice pretty quick, with my love for OCaml). There's
         | a chance I'd miss closures.
        
       | tubularhells wrote:
       | I guess Rust isn't hipster enough anymore.
        
         | higerordermap wrote:
         | They are silent now. Expect a rust post within 48 hours.
        
       | RantyDave wrote:
       | I'm going to sound old and very uncool but ...
       | 
       | Use C! I'm in the process of doing my first "serious" project in
       | 'straight' C (I'm a C++ guy from long ago) and it's taken me a
       | while to get into it properly, but I'm starting to get its
       | philosophy and it's becoming easy.
       | 
       | But also, in the embedded space, it clearly has all the support
       | you could ever want. I'm also gradually falling for Zephyr
       | (https://www.zephyrproject.org) that has all the support for (eg)
       | callbacks, all sorts of low level stuff.
       | 
       | And one more ... the way you'd solve this in Zephyr is to use the
       | scary simple API (https://docs.zephyrproject.org/latest/reference
       | /peripherals/...).
       | 
       | https://github.com/zephyrproject-rtos/zephyr/blob/master/sam...
        
       | tbojanin wrote:
       | > "Rather than any fancy safe resource management scheme, I used
       | mutable globals. YOLO:" Made me chuckle
        
       | loloquwowndueo wrote:
       | " Zig is a young project and unfortunately we don't have yet the
       | capacity to produce extensive documentation and learning
       | materials for everything" - documentation as an afterthought.
       | Thanks, I'll look elsewhere.
        
         | rudedogg wrote:
         | This was discussed recently on Zig Showtime:
         | https://youtu.be/pacsngNYXI0?t=2217
        
         | flohofwoe wrote:
         | TBF it doesn't make much sense to put much effort into detailed
         | documentation as long as the language isn't stable yet. And
         | what's already there of documentation is more then enough to be
         | productive, at least if you don't mind nosing around the
         | standard library's source code a bit (which is very educational
         | too, so win-win).
        
         | dnautics wrote:
         | Documentation is not an afterthought. There is specific
         | structure for do strings. IIRC Completion of documentation is
         | on the roadmap, it's just in the icebox until a certain set of
         | other features is complete.
         | 
         | Tbh, i think it's a great strategy. As wonderful as it is, zig
         | has enough bugs in it still that you don't want total newbies
         | all in yet, and this acts as a bit of a gatekeeping device
         | (which will be lifted in due time) and also keeps negatively
         | predisposed people "looking for excuses to not use zig" out --
         | as I'd say works really well in your case!
        
       | leshow wrote:
       | fn read_keys(port: #[cfg(feature = "splitapple")]
       | nrf52840_hal::pac::P1                        #[cfg(feature =
       | "keytron")]                        nrf52833_hal::pac::P0) ->
       | Packet {}
       | 
       | I'm not sure why you'd choose to write it this way? Should this
       | not be a trait that provides the read_keys method and you have a
       | type that exists for each of your target architectures?
       | 
       | Nevermind that you could actually do this, although you probably
       | shouldn't                   fn read_keys(#[cfg(feature =
       | "splitapple")]                  port: nrf52840_hal::pac::P1
       | #[cfg(feature = "keytron")]                  port:
       | nrf52833_hal::pac::P0) -> Packet {}
        
       | kristoff_it wrote:
       | It seems that `inline for` is the corner of `comptime` that
       | newcomers appreciate the most. See this other example:
       | 
       | https://clips.twitch.tv/RockyLivelyChimpanzeeDoubleRainbow-P...
       | 
       | Full video available here:
       | 
       | https://www.youtube.com/watch?v=O4UYT-brgrc
        
         | flohofwoe wrote:
         | I was wondering recently if "comptime for()" wouldn't be a
         | better name, because "inline for()" sounds like its main
         | feature is "performance through loop unrolling", but the actual
         | main feature seems to be "comptime-duck-typing through loop
         | unrolling" :)
        
           | nyberg wrote:
           | `comptime for (someslice) |capture| {};` is already a valid
           | expression so it would conflict along with `inline while`
           | being the other form.
           | 
           | The majority of uses of it in unrolling duck-typing cases
           | will most likely be replaced by [1] which allows the same but
           | with `switch` instead. Thus `inline for` will be left for
           | loop unrolling and possibly in use with `inline switch` where
           | it matters.
           | 
           | [1]: https://github.com/ziglang/zig/issues/7224
        
           | IshKebab wrote:
           | Absolutely. I don't know Zig and was confused why inlining a
           | loop would help. `comptime for` is 10 times clearer.
        
             | kristoff_it wrote:
             | The problem is that comptime implies a full compile-time
             | evaluation, while when doing an inline for you only want to
             | unroll over a list of potentially heterogeneous elements
             | but the body of the for loop will remain available for
             | evaluation at runtime, if not all vales are comptime known.
             | In a comptime block that would cause a compile error.
             | 
             | I have an example of that in this blog post:
             | https://kristoff.it/blog/what-is-zig-comptime/
        
               | IshKebab wrote:
               | Right but it's just the `for` bit that's `comptime`
               | right? The loop is unrolled and the loop condition must
               | be `comptime`.
               | 
               | It makes sense to me that `comptime for` means that the
               | `for` is `comptime` but the body of the loop isn't.
        
       | cjohansson wrote:
       | Thanks for the blog post, was an interesting read
        
       | AndrewDucker wrote:
       | I thought this was really interesting. Would love to know whether
       | he had made mistakes or if these are necessary Rust pain points.
        
         | tal8d wrote:
         | He was pretty upfront with the the possibility that his
         | problems with rust were of his own making. The places he tried
         | to leverage conditional compilation could be charitably
         | described as "creative", and would raise eyebrows even in a
         | language like C - where the preprocessor is relatively
         | unconstrained. I'm not familiar with his project beyond the
         | snippets he shared, so he may have had good reason to
         | effectively ifdef inside a function call instead of any of the
         | more traditional locations.
        
       | lulf wrote:
       | Having distinct types for P0 and P1 is deliberate and is what is
       | called "type state programming" in the embedded rust book [0].
       | The advantage is that you can prevent misconfiguration at compile
       | time (ensuring that a given pin cannot be used in multiple
       | places). In the Zig example, it seems to me (and I have zero
       | knowledge of Zig, so sorry if this is inaccurate) that you can
       | potentially introduce bugs where the same pin is used twice.
       | 
       | For a generic led driver, it should not use these types, but
       | instead the trait types from the embedded_hal crate, such as
       | "OutputPin" that is implemented by the different chip-specific
       | HALs. There is an example of a generic led driver that uses these
       | traits at [1].
       | 
       | In general I recommend everyone who wants to try out Rust on
       | embedded to read the embedded rust book, because it clarifies a
       | lot of the reasons and advantages of its approach.
       | 
       | [0] https://docs.rust-embedded.org/book/static-
       | guarantees/typest...
       | 
       | [1] https://github.com/drogue-iot/drogue-
       | device/blob/main/rt/src...
        
         | pron wrote:
         | What is checked at compile-time in Zig is up to the Zig code.
         | It's a little hard to explain because this doesn't work like
         | Lisp (or Rust) macros, but, since Zig is so easy to learn --
         | despite this revolutionary design -- should mean it's not a
         | problem. As a first approximation (somewhat inaccurate), you
         | could think of Zig as an optionally typed dynamic language that
         | can run introspect (and create) types freely, perform elaborate
         | checks on them etc. (e.g. examine fields and their types, and
         | compare them to other types' fields), and then the programmer
         | gets to say: run these checks at compile-time and make errors
         | compilation errors.
        
           | The_rationalist wrote:
           | Note that c++ and rust have const fn. But yeah the dinamicity
           | and introspectabibility you describe reminds me of
           | typescript.
        
             | pron wrote:
             | It's not about what Zig has but what it _doesn 't_ have.
             | Because low-level programming is already complex, language
             | simplicity is a super-important feature that few low-level
             | languages have, and I would say none that are expressive
             | and emphasise safety -- except Zig.
             | 
             | You could do those things in C++ with template games and in
             | Rust with macros. But Zig lets you have immense
             | expressivity with a simple, small and easy-to-learn
             | language.
        
               | craftinator wrote:
               | > It's not about what Zig has but what it doesn't have.
               | Because low-level programming is already complex,
               | language simplicity is a super-important feature
               | 
               | This is what made me love Lua for embedded programming.
               | The more inherent complexity (or "exposed complexity"
               | might be a better phrase) in the system, the less
               | inherent complexity you want in the language.
        
               | leshow wrote:
               | > You could do those things in C++ with template games
               | and in Rust with macros. But Zig lets you have immense
               | expressivity with a simple, small and easy-to-learn
               | language.
               | 
               | const fn is (or seems to me to be) exactly what comptime
               | is though. The difference is that rust's const syntax is
               | still slowly allowing more things to be executed at
               | compile time. Like for now, it still can't do any heap
               | allocation.
        
               | pron wrote:
               | Zig's unique power and killer feature isn't having
               | comptime; it's having little else. That's a feature Rust
               | or D or Nim simply can never, ever have, and it's an
               | extremely important feature, especially in low-level
               | programming. You can do anything you can do in Zig in
               | C++; what you _can 't_ do in C++ (or in Rust) is do those
               | things in a simple language you can quickly learn and
               | fully grasp.
        
         | kristoff_it wrote:
         | > In the Zig example, it seems to me (and I have zero knowledge
         | of Zig, so sorry if this is inaccurate) that you can
         | potentially introduce bugs where the same pin is used twice.
         | 
         | Given the code in the blog post, yes. Here's a possible
         | solution:                 pub fn initKeyboardGPIO() void {
         | comptime checkPinsAreUnique(10, rows);           comptime
         | checkPinsAreUnique(100, cols);           ...       }
         | fn checkPinsAreUnique(max_pin: usize, elems: anytype) void {
         | var seen = [1]bool{false} ** (max_pin + 1);           inline
         | for (elems) |x| {               if (x.pin > max_pin) {
         | @compileError("Found pin value higher than max pin");
         | }                      if (seen[x.pin]) {
         | @compileError("Found duplicate pin!");               }
         | seen[x.pin] = true;           }       }
         | 
         | If pins happen to be very sparse, one could switch from a basic
         | array to a comptime hashmap:
         | https://github.com/ziglang/zig/blob/master/lib/std/comptime_...
         | 
         | There's also other ways of approaching the implementation
         | depending on the required level of dynamicism, I just hacked
         | together the quickest solution I could think of.
        
           | sitkack wrote:
           | Would it be correct to describe this as using comptime to
           | enforce system level constraints? To my naive understanding
           | it looks like comptime combined with type state programming
           | gives one user definable type systems.
        
         | lynaghk wrote:
         | Author here. I agree that the Rust embedded books are a nice
         | read, and the idea of type state programming --- taking
         | advantage of Rust's ownership and generics system to enforce at
         | compile time transitions between logical states via "zero-sized
         | types" --- is interesting and could be useful in some contexts.
         | 
         | However, that is not what is happening here. P0 and P1 are
         | distinct types because they are distinct hardware registers. I
         | think it's great that they're modeled as distinct types; the
         | problem is simply that Rust makes it difficult to conceptually
         | iterate over distinct types (regardless if such iteration
         | occurs at runtime via a loop or at compile-time via an unrolled
         | loop, as per Zig's `inline for`).
         | 
         | An aside about "type state programming": Microcontrollers have
         | a _lot_ of functionality packed into the pins (see the STM32
         | "Alternate Function" datasheet tables). Trying to model all of
         | that using ownership of zero-sized generic types would strike
         | me as a "when all you have is a hammer"-type situation.
         | 
         | If a single pin switches between, for example, high-impedance,
         | gpio low, and PWM output depending on what mode your firmware
         | is in, I suspect it'd be a nightmare to pass owned types around
         | Rust functions --- one would have a much easier time (and more
         | likely to be correct) if they checked their design using
         | something like TLA+ / Alloy or implemented the firmware using
         | an explicit statecharts runtime like Quantum Leap's QP
         | framework https://www.state-machine.com/.
        
           | foldr wrote:
           | >An aside about "type state programming": Microcontrollers
           | have a lot of functionality packed into the pins (see the
           | STM32 "Alternate Function" datasheet tables). Trying to model
           | all of that using ownership of zero-sized generic types would
           | strike me as a "when all you have is a hammer"-type
           | situation.
           | 
           | I second this. The idea of checking that a pin is "only used
           | in one place" doesn't really jive with how I think about
           | microcontroller programming. It's very common for one pin to
           | be used for multiple distinct purposes at different times.
           | 
           | There's also a lot of different ways of conceptually slicing
           | pin states. For example, if you are charlieplexing LEDs than
           | you'll switch pins between 'input' (high impedance) and
           | 'output' modes, but at a higher level the pin is serving a
           | single function.
        
             | bsder wrote:
             | > The idea of checking that a pin is "only used in one
             | place" doesn't really jive with how I think about
             | microcontroller programming.
             | 
             | I'm with you, but ...
             | 
             | I find that most SDKs invariably grow a C macro system for
             | "Configure this pin/register/whatever and yell if somebody
             | tries to reconfigure it".
             | 
             | The fact that Rust is baking this in up front is not
             | unwarranted.
        
             | varajelle wrote:
             | > The idea of checking that a pin is "only used in one
             | place" doesn't really jive with how I think about
             | microcontroller programming.
             | 
             | The borrow checker is not checking that the pin is used in
             | "only one place", it is checking that you don't use the
             | same pin for two different purposes at the same time.
             | 
             | It make sure that you configure your pin as output pin
             | before using it as an output pin, and that you reconfigure
             | it to input pin when using it as such.
             | 
             | (And there are some escape hatch to use when the type
             | system is not sufficient to express that different code
             | paths are disjoint, like RefCell, with runtime check, or
             | unsafe)
        
               | [deleted]
        
               | foldr wrote:
               | Ah, I was going on what the OP said ("ensuring that a
               | given pin cannot be used in multiple places").
               | 
               | That seems sensible, but also not particularly valuable.
               | A lot of the time it makes sense both to 'read' and
               | 'write' from a pin (e.g. if it's open-drain with a
               | pullup).
        
               | lulf wrote:
               | This was an inaccuracy on my part, sorry for that. It
               | should probably have been "... used in multiple places
               | _at the same time_".
        
               | sitkack wrote:
               | My rough understanding.
               | 
               | Borrow checker tracks who is using what over time. The
               | can prevent concurrency and uncoordinated mutation, use
               | after free type problems.
               | 
               | Type system checks how it is being used.
               | 
               | Both are tools and can used to help ensure a correct
               | program. It really comes down to how these _tools_ are
               | used to help the programmer and the team deconstruct and
               | manage a system to solve a problem.
               | 
               | I think petgraph [1] is an excellent example of relaxing
               | some of the constraints imposed by the tools (borrow
               | checker, type system) to make a system that was easier to
               | use and extend. These things are much more continuous
               | than we realize, it isn't all or nothing.
               | 
               | In a lot of ways, I wish Rust's complexity was a little
               | more gradual, or that we knew how to use it in a gradual
               | way. Use of Rust features encourages C++ levels of
               | complexity. Use of Zig features encourages C-ish levels
               | of complexity.
               | 
               | Zig is to C
               | 
               | as
               | 
               | Rust is to C++
               | 
               | I also think the author had a much better model of the
               | system and the hardware and what they wanted to
               | accomplish during the rewrite and could better project
               | the problem domain and the language together.
               | 
               | Learning Rust and the problem domain at the same time is
               | extremely difficult and in a way leads to a perversion of
               | both.
               | 
               | What do you think about modeling the hardware as a
               | "Resource" register, port, memory, etc. Then modeling a
               | type over a collection of resources.
               | 
               | The question that I would ask myself when trying to use
               | Rust and the features it has out of the box is, "How much
               | fine grain rigor do I want Rust to model for me?" For the
               | keyboard scanning code, in asm or C, one might just have
               | a function `get_keyboard_state(*keyboard_buffer)` but
               | this exposes a sampling problem and would require the
               | programmer to determine state transitions. So maybe a
               | channel or an iterator would be better. Then we might nee
               | to run it in an ISR, the hardware it uses might be
               | multiplexed with other parts of the system, etc.
               | 
               | Every Rust feature needs to be weighed, or rather, given
               | a complexity budget, every Rust feature needs to be
               | purchased.
               | 
               | Zig is awesome BTW, but it doesn't make the same
               | correctness guarantees that Rust can.
               | 
               | [1] petgraph, https://docs.rs/crate/petgraph/0.5.1
        
             | bacon_waffle wrote:
             | > It's very common for one pin to be used for multiple
             | distinct purposes at different times.
             | 
             | Anecdotal, but as someone who works in this space I haven't
             | found this to be the case. In my experience, any particular
             | pin is wired up for a specific purpose, and so the firmware
             | usually just sets it to that mode as appropriate. Generally
             | if it's found that the needed peripherals couldn't be
             | multiplexed to pins without conflicts, it's time to move up
             | to a package with more pins brought out.
             | 
             | I'm currently working on a relatively involved firmware for
             | ATSAMD21 in Rust, and have mostly enjoyed the experience.
             | While some of the language concepts have taken me a while
             | to get comfortable with, and we're still figuring out parts
             | of the ecosystem, it's quite usable and the tooling is a
             | huge improvement over anything I've seen.
        
           | varajelle wrote:
           | I agree that iterating over types of a tuple is indeed not
           | easy, but in that case, it should be trivial to iterate over
           | an array of `&dyn OutputPin`. Why is that not working in this
           | case?
        
           | 1MachineElf wrote:
           | I really like your Atreus. Are my eyes correct that it's
           | using choc switches? Can you share where you got that one?
        
           | jbandela1 wrote:
           | I think you can do something like this with your Rust code
           | using macros.                 struct P0{}              impl
           | P0{         fn write(self:&Self,pin:usize){
           | std::println!("Writing port P0 on pin {}",pin);         }
           | }            struct P1{}            impl P1{         fn
           | write(self:&Self,pin:usize){
           | std::println!("Writing port P1 on pin {}",pin);         }
           | }            macro_rules! for_each_port_pin{
           | ($port:ident,$pin:ident,$b:block,
           | $(($e1:expr,$e2:expr)),*) =>{             $(             let
           | $port = $e1;             let $pin = $e2;             $b
           | );*         }       }            fn main(){         let p0 =
           | P0{};         let p1 = P1{};
           | for_each_port_pin!(port,pin,         {port.write(pin);},
           | (&p0,10usize),(&p1,7usize)         );       }
           | 
           | Rust playground link: https://play.rust-
           | lang.org/?version=stable&mode=debug&editio...
        
             | afranchuk wrote:
             | Or a trait...
        
           | jokethrowaway wrote:
           | Even if you didn't have Output Pin, couldn't you just declare
           | a sum type?
           | 
           | enum MyPin { P0, P1 }
           | 
           | Edit: feel free to ignore, read your answer somewhere else
           | about this
           | 
           | You would then have to pattern match when you read the value
           | but I don't see a reason to reach for macros or anything more
           | complicated.
           | 
           | That said, really enjoyed the read (and I'll definitely try
           | zig at some point, if only for the speed / compile
           | experience), even if my experience with Rust didn't match
           | yours; my background is a bit different though, I worked with
           | C++ and Haskell in the past, which definitely made rust feel
           | almost natural. Overall I'd say that the compiler helps me
           | not to keep a lot of rust syntax in my mind and just try
           | things until it works
        
           | elcritch wrote:
           | Interesting write-up! I've barely used Rust but had/have a
           | similar feeling. It's really more akin to C++ and really
           | powerful but also pretty complex. For smaller MCU projects it
           | just feels like overkill.
           | 
           | > Microcontrollers have a lot of functionality packed into
           | the pins (see the STM32 "Alternate Function" datasheet
           | tables). Trying to model all of that using ownership of zero-
           | sized generic types would strike me as a "when all you have
           | is a hammer"-type situation.
           | 
           | The whole idea of utilizing TLA+ for a system level check
           | really does seem like something that would be awesome, even
           | if it's unclear how much effort it'd require to instrument an
           | entire project with TLA+.
           | 
           | > the problem is simply that Rust makes it difficult to
           | conceptually iterate over distinct types (regardless if such
           | iteration occurs at runtime via a loop or at compile-time via
           | an unrolled loop, as per Zig's `inline for`).
           | 
           | Rust just brings a lot of incidental complexity along and
           | still makes some things really difficult. Perhaps it's better
           | in the long run but it's just harder to work with.
           | 
           | Similarly, I wanted a simpler language than Rust and started
           | using Nim last summer for embedded projects. Primarily since
           | it compiles to C which let's me target ESP32's and existing
           | RTOS'es without rewriting everything or trying to get LLVM
           | based tools to work with GCC ones. However, it also embraces
           | `lazy` compilation approach to code and it's standard
           | library.
           | 
           | I wanted to try your example in Nim. Here's roughly how your
           | example would look in Nim (it appears to duck type fine as
           | well):                   var           # normally just import
           | these from the C headers as-is           # but this lets us
           | run it           p0* : RefPort0 = addr port0           p1* :
           | RefPort1 = addr port1              var rows = (
           | ( port: p1, pin: 0 ),              ( port: p1, pin: 1 ),
           | ( port: p1, pin: 2 ),              ( port: p1, pin: 4 ),
           | )              var cols = (             (port: p0, pin: 13 ),
           | (port: p1, pin: 15 ),             ...             (port: p0,
           | pin: 2 )           )              proc initKeyboardGPIO() =
           | rows[0].port.pin[rows[0].pin].dir = output                for
           | item in rows.fields:              item.port.pin[item.pin].dir
           | = output
           | 
           | Full example: https://gist.github.com/elcritch/1c8279418fc62f
           | 5e941b41a5df4...
           | 
           | I've toyed with the thought of adding TLA+ hooks into Nim
           | similar to Dr Nim (https://nim-lang.github.io/Nim/drnim.html)
           | using the effect system. Not sure if Zig has an effect system
           | for a similar setup.
        
       | zserge wrote:
       | Zig code looks way more readable to my eyes, damaged by the years
       | of staring at C/C++. Also the learning curve for Zig so far seems
       | to be relatively shallow. The documentation is rather "ok-ish",
       | comparing to Go for example. But it's much better than a few of
       | the other programming languages I've used. I really hope Zig will
       | find the place it deserves in the programming world!
        
         | rjzzleep wrote:
         | I don't know. When rust was first iterating it was basically a
         | different language from what it is now.
         | 
         | I cannot find the appeal of the current iteration. It's very
         | counterintuitive, which makes it really unsuitable for
         | mainstream programming IMHO. Sure, you could argue that it's
         | not intended for mainstream programming and that we want people
         | to know exactly what they're doing, but then you're basically
         | making the same argument Torvalds did for C.
         | 
         | And then you kinda have to ask yourself ... why?
        
           | EugeneOZ wrote:
           | Despite all my love to Rust, I completely agree.
        
           | the_duke wrote:
           | Can you mention some concrete criticism?
           | 
           | Rust has evolved a lot, even since 1.0, but all changes were
           | well designed and for the better, in my view.
        
           | cute_boi wrote:
           | whats wrong with current iteration? I think rust has become
           | better with things like lifetime elision? And many new thing
           | like GAT, out of band lifetime etc will make it more better?
           | 
           | Before claiming why its counterintuitive I think you should
           | have provided some example to backup such claim.
        
           | simias wrote:
           | The "why" is memory safety without GC, which as far as I know
           | no other non-toy language provides.
           | 
           | It's also why I feel that the comparison with Zig is a bit
           | unfair: Zig is not memory safe. If Rust was willing to
           | compromise with this constraint it would remove could remove
           | some of the intellectual overhead for the developer and
           | result in simpler looking, if unsafe, code.
           | 
           | But then it would also destroy the one killer feature of the
           | language.
        
             | loup-vaillant wrote:
             | I believe Rust isn't exactly memory safe either:
             | https://stackoverflow.com/questions/24898579/why-does-the-
             | ru... (and I _think_ bounds checking can be turned off).
             | 
             | The borrow checker is a Big Deal(tm), but even outside
             | unsafe blocks, Rust did not go all the way to perfect
             | safety. Safety remains a spectrum, not a binary choice. The
             | extreme end of that spectrum isn't Rust. It's using a proof
             | assistant to mechanically check the correctness of your
             | entire program.
        
               | OvermindDL1 wrote:
               | What are you referencing on that page that is memory and
               | safe, the program panicking when attempting to access
               | invalid memory is one of the safety features, it means
               | you have a programming error that you need to correct and
               | it is bailing right now to prevent anything bad from
               | happening.
        
               | loup-vaillant wrote:
               | Oops, after a cursory search, it would seem there's no
               | easy way to disable runtime bounds checking. While
               | runtime crashes aren't ideal, I do stand corrected,
               | sorry.
               | 
               | Still, I think my point about safety being a cursor
               | instead of a switch remains.
        
               | bogeholm wrote:
               | That looks like memory safety to me - deliberate panic
               | instead of returning what is next to `vec[2]` in memory.
        
           | detaro wrote:
           | Do I interpret your comment correctly that you think Zig
           | might follow a similar path?
        
             | rjzzleep wrote:
             | Nope, I have no idea which path Zig will take, but I
             | somewhat doubt it will do the same Rust did. Rusts later
             | stage development reminds me a bit of the design by
             | committee approach and it doesn't seem like Zig has that
             | problem, but it's also hard to make it to a mainstream
             | language without a hugely popular project that's associated
             | to it.
             | 
             | I was generally rooting for mainstream usage of Rust, but I
             | don't see it happening with the path it has taken. I also
             | don't really hope it will for the same reasons.
        
               | ttt0 wrote:
               | > it's also hard to make it to a mainstream language
               | without a hugely popular project that's associated to it.
               | 
               | As long as it's a good language, why do we care if it's
               | mainstream or not?
        
               | skohan wrote:
               | Mostly ecosystem and community support. There are a lot
               | of interesting languages out there, but it's hard to do
               | interesting things with them if they're missing support.
               | 
               | Zig might be in a good position here as it has very nice
               | C interop, which lets you leverage the past 30 years of
               | programming history, but it's still got a ways to go
               | before it will be "ready for primetime" from the look of
               | it.
        
               | 5mixer wrote:
               | I use a non-mainstream language, Haxe.
               | 
               | - There are extremely few jobs that recognise it. I'm
               | attempting to learn C++ because of this.
               | 
               | - Documentation can be lacking as there isn't as much
               | demand for it, or people with time to write it. That
               | said, personal support in small communities can be great.
               | 
               | - Smaller library ecosystem.
               | 
               | - Survival of the language into the future is less
               | certain without the financial support mainstream
               | languages have.
               | 
               | I've used Haxe for years despite these points, it's a
               | great language. A language is more than it's engineering
               | though.
        
               | loup-vaillant wrote:
               | Zig already has the main advantages of being mainstream:
               | first, it is small and easy to learn, which means you can
               | hire any C/C++/D/Rust programmer, and they'll be
               | productive in no time. Second, it binds to C more easily
               | than any other language (save _maybe_ C++), which means
               | you have access to a wealth of libraries already.
               | 
               | Ironically, neutralising network effects like that is
               | perhaps the best way to make sure Zig _becomes_
               | mainstream, eventually.
        
               | dnautics wrote:
               | > Ironically,...
               | 
               | Not to lionize andy or anything, but I'm pretty sure that
               | strategies to neutralize these concerns is a deliberate
               | choice in his stewardship of the language.
        
               | loup-vaillant wrote:
               | You're correct, "ironically" was uncalled for.
        
             | Hoppetosse wrote:
             | In many ways, it already has. Zig has been under
             | development for several years now and its syntax and
             | semantics have change a lot since its first iterations.
             | There are still some breaking changes coming that you can
             | find at https://github.com/ziglang/zig/issues.
             | 
             | One of the cooler aspects of Zig's breaking changes is that
             | its formatting tool can automatically update your code to
             | the new syntax.
        
               | bb010g wrote:
               | I'm really glad Rust has that too with cargo-fix.
               | https://doc.rust-lang.org/cargo/commands/cargo-fix.html
        
         | littlestymaar wrote:
         | Fewer symbols[1] "look" more readable at first glance (like
         | dynamic languages), and more beginner friendly, but it also
         | means there's less intent communicated and the reader needs to
         | look for the information elsewhere: it's an eternal trade-off.
         | 
         | Also, Zig comptime is extremely powerful, which means you can
         | do many many things with them, but it makes it pretty hard to
         | understand what's happening: you always need to wonder "what
         | will this code become when compiled". A bit like with super-
         | macro-heavy C code, or even lisp (even though comptime don't
         | even work the same way macro do so you also need to wrap your
         | head around it). In the end, IMHO it makes it "really fun to
         | write, and hard to read". This, combined with the lack of
         | memory safety[2], probably make Zig the perfect hacker/hobbyist
         | language, but not desirable for production (being the perfect
         | mirror of Rust).
         | 
         | [1] even though Zig isn't a particularly good example for this,
         | when reading real-world Zig code, there's `@` and unusual
         | keywords (`align` `inline` `try` `comptime`, etc.), and (kind
         | of) static typing. Of course it has a lighter syntax than Rust,
         | but it's not like a dynamically-typed language either.
         | 
         | [2] yes, I know, there are some plans to have some kind of op-
         | in memory-safety thanks to runtime checks, which is better than
         | C's "everything is UB and sanitizer are an afterthought", but
         | still far away from the "proven safe" situation you get when
         | using Rust. It's pretty sad that Zig didn't want to build upon
         | the ownership framework developed by Rust.
        
       | jll29 wrote:
       | Great article, and thanks for calling out "guessability" as you
       | call it. It relates to two concepts in computer science, one from
       | programming languages and one from human computer interaction:
       | 
       | 1. Orthogonality is the property of a language that it constitute
       | only a small number of concepts, but exactly the ones you need
       | (e.g. C or Scheme are orthogonal, C++17 is not).
       | 
       | 2. A good user experience (e.g. of a Web GUI, but also of a
       | programming language) minimizes the violation of expectations of
       | the user (Ben Shneiderman). "Discoverability" has also been used
       | to describe this.
       | 
       | I agree with you that it's desirable for a language that you may
       | have an intuition "it should be written as something like
       | this..." and it just words. Thanks for calling out
       | "guessability"!
        
         | kristoff_it wrote:
         | The design world likes to use the word affordance: "the quality
         | or property of an object that defines its possible uses or
         | makes clear how it can or should be used"
         | 
         | https://en.wikipedia.org/wiki/Affordance
        
         | ant6n wrote:
         | The consulting world likes MECE - mutually exclusive
         | collectively exhaustive.
        
       | zelphirkalt wrote:
       | I navigated from the post to Zig's documentation, which the
       | author links to. Seems a bit more than one page, but OK. I
       | checked for recursion, as I value being able to use recursion
       | without stack overflow or maximum recursion depth errors. I read:
       | 
       | > Recursion is a fundamental tool in modeling software. However
       | it has an often-overlooked problem: unbounded memory allocation.
       | 
       | That it not necessarily true. It depends on the implementation in
       | the given language. Perhaps in Zig it has that problem, but it is
       | not an inherent property of every recursion one can think of.
       | 
       | If Zig manages to get optimized tail calls like I can enjoy in
       | Scheme, it has a good chance of being the next language I learn.
       | As noted in the next paragraph of the docs, it is still an area
       | of experimentation.
        
         | gnuvince wrote:
         | ~Zig doesn't support TCE.~
         | 
         | Edit: Tried with different optimization levels, it seems it
         | might. I wonder if it's an optimization from LLVM.
         | Nevertheless, I don't think that Zig is the kind of language
         | where you want to use recursion as your primary mean of
         | looping; it's not a functional language, it's a procedural
         | language.
        
         | losvedir wrote:
         | Correct me if I'm wrong, but I think it's not possible to
         | express all recursion as tail-call recursion. So I think the
         | statement is correct, in the general case.
         | 
         | But, yes, it's true that you can express some recursion with
         | fixed memory size. And, in fact, the @call[0] built-in has an
         | `always_tail` which asserts that the call should always be
         | generated with tail call recursion optimization, and is an
         | error at compile time, if not.
         | 
         | You might also be interested in following this (open) GitHub
         | issue[1] that explores recursion in more detail.
         | 
         | But, yes, this area of Zig seems to be still a little
         | "experimental", as you say. (But shows great promise, I think!)
         | 
         | [0] https://ziglang.org/documentation/0.7.1/#call [1]
         | https://github.com/ziglang/zig/issues/1006
        
           | zelphirkalt wrote:
           | I think it is possible to express all recursion as tail-call
           | recursion, but only by passing continuations as arguments,
           | which might lose the advantage of a tail-call recursion. Any
           | computation you have left in the current frame you could put
           | into a continuation. However, that continuation then grows,
           | so you need more memory for it, instead of multiple stack
           | frames, so the advantage ist lost.
        
         | frabert wrote:
         | > That it not necessarily true
         | 
         | It is necessarily true if you allow non-tail recursive calls.
        
           | zelphirkalt wrote:
           | The phrase makes a statement about recursion in general, no
           | further narrowing it down to one kind. One cannot generalize
           | from one case (non-tail recursive, which might have unbounded
           | memory allocation) to the whole class of calls, which is
           | recursive calls or recursion in general.
           | 
           | So it is not necessarily a true statement about recursion. It
           | is only a true statement about non-tail recursive ones. It is
           | an over-generalization. The docs would to well to distinguish
           | those.
        
       | losvedir wrote:
       | Generally higher level programmer here (Elixir), who has been
       | dabbling with Zig lately (goal is to write an Elixir type
       | checker!). It's quite refreshing, interesting, and dare I say it,
       | fun!
       | 
       | I realize it doesn't have the same memory safety guarantees as
       | rust, but what I don't quite understand, is what sort of danger
       | that means I'm inviting these days. If my app doesn't deal with
       | anything particular sensitive, do I need to worry about it too
       | much? Am I inviting trouble for my users?
       | 
       | I read Smashing the Stack for Fun and Profit back in the day, and
       | I know that at one point, poorly behaving low level programs
       | could escalate privileges, reach into _other_ programs ' memory
       | and so forth.
       | 
       | But is that still relevant these days? I'd think OSes have gotten
       | better about sandboxing programs and such. I know I've seen
       | misbehaving programs segfault before, which I thought was the OS
       | protecting against wayward memory access.
       | 
       | And "memory safety" really doesn't apply if targeting wasm,
       | right? In that case Zig could really shine, since its great for
       | cross compilation and really has memory allocations handled well,
       | and doesn't need a GC, without the "memory safety" downsides.
       | 
       | I guess my question is how much rust's "memory safety" is kind of
       | an XY problem? In what cases is that actually what I should be
       | asking for, compared to the more general question of program
       | correctness? I can see it in rust's original use case of a web
       | browser, since, e.g., tabs shouldn't access each other, but does
       | it apply to all "system" or low level programming use cases?
        
         | gnuvince wrote:
         | Many organizations have independently converged on memory
         | safety being responsible for roughly 70% of vulnerabilities. So
         | yes, still relevant and still a problem for users.
         | 
         | https://alexgaynor.net/2020/may/27/science-on-memory-unsafet...
        
       | alcover wrote:
       | I have a humble feeling Zig has a super bright future.
       | Simplicity, seemingly perfect C interop,...
       | 
       | As a mere apprentice of 'low-level' programming, I have no
       | intention of learning Rust. It's certainly brilliant though and
       | understanding its underlying concepts is something I aspire to.
       | 
       | I think maybe a good deal of people at the same stage I am will
       | also leave Rust aside as a specialized hard-to-use tool.
        
         | bb010g wrote:
         | Would you also consider C++ a specialized, hard-to-use tool?
        
           | alcover wrote:
           | My worthless opinion is only based on what knowledgable devs
           | say : it's hard for sure, and feature/paradigms-creeped.
           | 
           | But not specialized, even less than C since it contains C.
        
           | nromiun wrote:
           | Yes, in fact the only reason most people (not all) use it
           | because there is no real alternative. Sometimes it is the
           | only tool on the table (the environment).
        
       | vasergen wrote:
       | How golang compares to zig? Not using any of them, but from my
       | understanding both of them trying to be "simple". Just curious
       | what is the difference on the highter level
        
         | dilap wrote:
         | It's interesting to consider generics.
         | 
         | Go gained some simplicity by not having generics, instead
         | simply special-casing the two most common generic
         | datastructures: lists and maps. But in the end, the desire for
         | generics was too strong, and now they're adding them to the
         | language, and losing that simplicity win.
         | 
         | Zig gained simplicity by not having generics, but instead
         | giving you comp-time evaluation, which can do everything
         | generics can do and more (e.g., replaces need for preprocessor,
         | need for minilanguage for build variants).
         | 
         | I do think both languages have similar philosophies and feel,
         | though Zig is much lower level, and seems to have more of a
         | focus on "get things exactly right" vs. maybe more of a "eh,
         | good enough / hacky" approach from Go.
         | 
         | I wonder what a language that tried to combine this focus on
         | smallness/orthogonality with a borrow checker would look like.
         | (Would it even be possible?)
        
         | wolf550e wrote:
         | AFAIK Go has a GC you can't avoid, so it's not very suitable
         | for embedded or realtime.
        
           | [deleted]
        
           | coder543 wrote:
           | "not very suitable" is a relative term. TinyGo is a cool
           | project: https://tinygo.org
           | 
           | There are various conference presentations about it that show
           | it in action.
        
             | IshKebab wrote:
             | Cool project but I think he's still right - you normally
             | don't want GC on your microcontroller and often you don't
             | want heap allocations at all. Very difficult to write Go
             | with those constraints.
        
               | coder543 wrote:
               | Lots of "serious" microcontroller projects are written in
               | MicroPython, Lua (such as eLua), and Espruino
               | (JavaScript).
               | 
               | I think it's simply an outdated oversimplification in
               | 2021 to say that microcontroller projects "normally"
               | don't want heap allocations at all.
               | 
               | TinyGo also makes it relatively easy to avoid heap
               | allocations because you can change a compiler flag to
               | make heap allocations a compiler error^1, if that's
               | required for a particular project.
               | 
               | ^1: https://tinygo.org/usage/important-options/ (look at
               | -gc=none)
               | 
               | Another interesting link: https://tinygo.org/compiler-
               | internals/heap-allocation/
        
               | Cyph0n wrote:
               | It ultimately depends on your definition of "serious" and
               | the microcontrollers we're talking about.
               | 
               | For performance-sensitive areas where deterministic
               | latency is critical, a GC simply won't cut it, even if
               | you can control when to run the GC step.
               | 
               | For lower end microcontrollers, I highly doubt any of
               | those solutions would ever work: there is simply too much
               | "magic" (read: overhead) involved.
               | 
               | This is primarily why C is still king of the
               | microcontroller world.
        
       | nromiun wrote:
       | > I was able to compile it to WASM for a layout engine, build and
       | sell a fast desktop search app (Rust shoved into Electron), and
       | compile Rust to an stm32g4 microcontroller to drive a track saw
       | robot (I even found a typo in the register definitions; the full
       | "hard-mode" embedded debugging experience!).
       | 
       | > Despite all this, I still don't feel comfortable with Rust.
       | 
       | This was pretty much my entire experience with Rust. No matter
       | how much I used it I didn't feel comfortable with it. I tried it
       | out when it was still pretty young and it felt like a child of
       | C++ and ML. It is just as feature rich as C++ and some people
       | obviously like that. But I just couldn't keep up with it.
        
       | shp0ngle wrote:
       | Yes, this mirrors my experiments with both languages. There is
       | just too much stuff going on in Rust.
       | 
       | I, however, still like the borrow checker and the dances with
       | ownership Rust has. It eliminates many kinds of bugs.
       | 
       | Just I wish the language was more easy to learn.
        
       | chubot wrote:
       | This brings to mind _Type Checking vs. Metaprogramming; ML vs.
       | Lisp_
       | 
       | https://www.oilshell.org/blog/2016/12/05.html
       | 
       | Zig is way better at metaprogramming, but doesn't give you great
       | safety guarantees. Rust is better at type checking, but has at
       | least 3 different kinds of metaprogramming to patch over
       | usability/composability holes created by that rigidity (2 kinds
       | of macros and const contexts)
        
         | littlestymaar wrote:
         | A interesting quote from your link:
         | 
         | > OCaml has had more than one macro system, and it appears that
         | they are not done evolving in incompatible ways.
         | 
         | Looks like Rust is following the path of its ancestor here,
         | unfortunately.
        
       | userbinator wrote:
       | As a long-time embedded programmer --- who has written keyboard
       | code before --- I feel like "keyboard firmware" and a HLL really
       | don't belong in the same sentence, and there's far too much
       | abstraction in the examples the author provides.
       | 
       |  _Say I need to initialize all the columns as output pins._
       | 
       |  _But my column pins are spread across two ports_
       | 
       | That would be one, or two, instructions to set the appropriate
       | port direction registers. Never a loop. Perhaps the author should
       | give Asm a try if he thinks the language is getting in the way.
       | 
       | You may find this interesting:
       | http://halicery.com/Hardware/Intel%208042%20and%208048/8042%...
        
       | samuell wrote:
       | It seems Zig and Nim are somehow targeting the same niche of "C
       | with better syntax". Would be interested to hear about any
       | differences in their focus.
        
         | chris_st wrote:
         | Nim has garbage collection, Zig you must manage memory on your
         | own. Each side has pros and cons.
        
         | cb321 wrote:
         | As abstract focus for large projects not easily summarized, I
         | would say Nim tries to be "richly expressive" out of the box
         | with tools to make it even moreso while Zig is more
         | "intentionally spartan" with tools to make it expressive.
         | 
         | Maybe more helpfully, Zig is much closer to your "C with better
         | syntax" than Nim. Nim is more like Ada & Lisp had a baby with
         | better/more Python-ish syntax ("better" being subjective, as
         | with most things).
         | 
         | ( EDIT: But really all should make their own determinations:
         | https://nim-lang.org )
        
       | WJW wrote:
       | Are these the early signs of the next great hype cycle? I feel I
       | haven't seen nearly as many "We rewrote XYZ in Rust" articles of
       | late.
       | 
       | That said, Zig does look very cool and I think its design choices
       | make a lot of sense. Rewriting from C to Rust is a way bigger
       | step than from C to Zig and you still get a lot of safety
       | improvements (though not quite as many as the borrow checker can
       | provide). It'll be interesting to see how it evolves.
        
         | IshKebab wrote:
         | Yeah because it is no longer notable to be using Rust. It is
         | notable to be using Zig.
        
       | geogriffin wrote:
       | FWIW, you can write the conditional compilation example like so:
       | fn read_keys(#[cfg(feature = "splitapple")]
       | port: nrf52840_hal::pac::P1,                    #[cfg(feature =
       | "keytron")]                    port: nrf52833_hal::pac::P0) ->
       | Packet {}
       | 
       | I suppose the compiler could try to suggest that as a fix.
        
         | pornel wrote:
         | Another option would be to define a type alias:
         | #[cfg(feature = "splitapple")]         type Port =
         | nrf52840_hal::pac::P1;              #[cfg(feature = "keytron")]
         | type Port = nrf52833_hal::pac::P0;              fn
         | read_keys(port: Port) -> Packet {}
         | 
         | but I think the whole problem here is that author tried to do
         | what would have been #ifdef in C, but that doesn't get you far
         | in Rust when macros are AST-based and types are more specific
         | than `int` and `char*`.
         | 
         | A more "rustic" solution would be to use a trait, e.g.
         | trait ReadKeys {            type Port;            fn
         | read_keys(Self::Port) -> Packet {}         }              impl
         | ReadKeys for Splitapple {...}         impl ReadKeys for Keytron
         | {...}
        
         | skohan wrote:
         | My experience with Rust has been that it's _possible_ to do
         | almost anything, but that doesn 't mean it's necessarily
         | obvious or ergonomic to do so, or that anyone else will
         | understand my code when its finished.
        
       | ncmncm wrote:
       | While this might not be a popular observation around here, about
       | all the design choices that have led us from C to C++20 (and soon
       | enough C++23) have been specifically to address problems of this
       | nature. (Rust may someday be as versatile, but that is not on the
       | immediate agenda.)
       | 
       | There is really no reason ever to even _consider_ restricting
       | yourself to C when coding for a microcontroller. The ability to
       | do computation at compile time is essential in this area, and C
       | is just fundamentally lacking. While _in principle_ you could do
       | your compile-time work in the makefile, instead, complicating
       | your build process rarely turns out well.
       | 
       | Some people insist C++ is about "O-O" notions of inheritance,
       | virtual functions, and heap allocation; others, that it is about
       | STL and std::vector. All such people are _dead, dead_ wrong. You
       | don 't (generally!) use _any_ of that in microcontroller coding,
       | yet you routinely draw upon all the most powerful features of the
       | language and library to make code that is maximally short, fast,
       | small, and correct. These features put the type system to work
       | for you, not just checking but actively making code correct.
       | 
       | Debugging on microcontrollers is hard enough with a language that
       | makes bad code easier to write than good code. In modern C++ (as
       | now also in Rust), when the program builds and links, it is more
       | often than not right. The more you help yourself to correctness
       | with powerful language features, the more often this happens.
       | 
       | While Zig has features to do computation at compile time, it
       | lacks features to help make that computation correct. Such
       | features would depend on a more powerful type system than Zig
       | provides. Debugging bad compile-time computation at runtime, in a
       | microcontroller, is no fun.
        
         | dnautics wrote:
         | > While Zig has features to do computation at compile time, it
         | lacks features to help make that computation correct.
         | 
         | Huh? Zig will reject comptime overflows/underflows, and you can
         | write suites of inline tests too if you'd like, so whatever you
         | miss at comptime you can constrain using runtime CI-validation
         | at the location of interest.
        
           | ncmncm wrote:
           | As written immediately above, "Such features would depend on
           | a more powerful type system than Zig provides." So Zig offers
           | as much help as it can, without.
        
       | keyle wrote:
       | Out of interest, genuinely asking, Go is out of the question due
       | to the GC?
        
         | detaro wrote:
         | And generally not being designed with that kind of low
         | level/microcontrollers in mind. There is a variant for
         | microcontrollers (https://github.com/tinygo-org/tinygo), so
         | it's not impossible, but the fact that it is a variant and not
         | just a different compile target already tells us something
         | about the different focus.
        
         | 1f60c wrote:
         | Even a "hello, world!" application is like 1.6 MB when using
         | Go, when (from the article)
         | 
         | > microcontrollers have limited program space, roughly 10-100kB
         | in my case
        
           | coder543 wrote:
           | TinyGo is a Go toolchain for microcontrollers that uses LLVM,
           | and it produces binaries that are extremely small. A few kB
           | is not uncommon from what I've heard, although I don't have
           | much personal experience with it. For WASM, the smallest
           | binary TinyGo can produce is on the order of 500 bytes, and I
           | would expect bare metal MCU targets to be similarly sized for
           | the smallest hello world binaries.
           | 
           | The standard Go toolchain cannot compile for
           | microcontrollers, so the size of binaries that it produces is
           | irrelevant.
           | 
           | Just like there are many compilers for C, there are multiple
           | compilers for Go with different priorities.
           | 
           | https://tinygo.org
        
       | [deleted]
        
       | dbaupp wrote:
       | I think the trick of using a separate file per target works just
       | as well in Rust, where each one imports the appropriate HAL(s)
       | and they all expose the same interface as each other. "Circular"
       | imports across the files should work too. This will help
       | reduce/resolve the scattering of #[cfg]s throughout the code (but
       | won't help with the heterogeneous iteration).
       | 
       | Another approach for that sort of genericity would be a trait
       | that is implemented for each target. This ends up being a more
       | formal/structured version of the above, since it defines the
       | interface explicitly, but is potentially over engineering.
        
       | leoedin wrote:
       | It seems like a lot of the problems stem from the definition of
       | each microcontroller port being a different type. So you can't
       | simply pass P0 _or_ P1, you have to choose at compile time which
       | one it is.
       | 
       | I wonder if there's a better way to do this. Perhaps making GPIO
       | peripherals all the same type, with some sort of feature flag for
       | each pin. It would simplify a lot of things.
       | 
       | From the point of view of an embedded developer tired of C/C++,
       | there's a lot of attractive features in Rust for embedded. But I
       | haven't tried to write more than a trivial project in it. I
       | wonder if this is a fatal flaw or just a problem with how the
       | embedded HAL is defined.
        
         | rhn_mk1 wrote:
         | Maybe defining a "Port" trait and using the dynamic type
         | instead of the static one is a solution.
        
           | rcxdude wrote:
           | Perhaps, but it sucks from a performance point of view. In
           | embedded for a lot of operations you want the HAL to compile
           | down to one or two instructions (GPIO being a classic example
           | of this). Dynamic indirection is proportionally very
           | expensive here (though in embedded a lot of the other costs
           | normally associated with pointers are a lot less, since the
           | memory hierarchy is very shallow).
        
             | pas wrote:
             | enum_dispatch to the rescue! (or at least that is what it
             | says on the tin:
             | https://docs.rs/enum_dispatch/0.3.5/enum_dispatch/ )
        
         | ikskuh wrote:
         | Most of them are actually the same and on several
         | microcontrollers you can even just access them as an array of
         | structs, selecting each port at runtime. HALs try to hide these
         | facts which you can only find out about in the hardware
         | documentation
         | 
         | Most GPIO defs are just "this is a struct at 0xAABBCCDD"
        
         | lynaghk wrote:
         | Author here. Both my Zig code and the Rust peripheral access
         | crate model the pins as distinct types, which I think is
         | correct --- the pins have different memory addresses and
         | sometimes (depending on the microcontroller) distinct sets of
         | controlling registers.
         | 
         | The tricky part in Rust is how to make things generic across
         | distinct types. Zig's comptime lets you sort of "duck type" it
         | (but with compile-time checking that all of the methods exist,
         | etc.), whereas Rust requires that you explicitly introduce a
         | trait and implement trait methods across the types. The
         | embedded HAL crates do this with extensive use of macros, for
         | example: https://github.com/nrf-rs/nrf-
         | hal/blob/aae17943efc24baffe30b...
         | 
         | This solution makes sense given the constraints of Rust, but
         | there's quite a cost in terms of compiler time and cognitive
         | overhead to understand what is going on.
         | 
         | (Aside: I didn't use the HAL in my Rust firmware, that's a
         | higher layer of abstraction; I only used the PAC crates.)
        
           | leoedin wrote:
           | Instances of types also have different memory addresses -
           | that doesn't mean they're different types.
           | 
           | I think it's a hard problem because every microcontroller is
           | different. A pretty common pattern in C land is to define a
           | peripheral struct (eg I2C, GPIO) which has a 32 bit uint for
           | each register in the peripheral, and then create a pointer to
           | the physical memory address for each peripheral. That means
           | you can write functions which take _an I2C peripheral_
           | without knowing which one - and so if you decide later to
           | move over to I2C2 it 's just a case of changing one variable.
           | 
           | That works because broadly there's very little difference
           | between I2C1 and I2C2, or GPIO0 and GPIO1, in the
           | microcontroller. If they start being very different then
           | you'd have problems with that approach.
        
         | rcxdude wrote:
         | Yeah, that's a better way to do it. I've been following rust
         | for embedded with great interest but I'm not so sure the HAL
         | work is really going in a good direction. There seems to be a
         | lot of awkward design decisions going into the interfaces (and
         | to be clear, designing even a simple GPIO interface which
         | satisfies even most users is Hard, let along anything which
         | works for something like a serial port). I've a feeling if I
         | started using it in anger I would fairly quickly just write my
         | own.
        
       | skohan wrote:
       | > much of the complexity I'd unconsciously attributed to the
       | domain -- "this is what systems programming is like" -- was in
       | fact a consequence of deliberate Rust design decisions.
       | 
       | This is something I've thought a lot about as I've started to
       | reach the "hundreds of hours" of working with Rust mark. Besides
       | attributing the complexity of Rust to the domain of systems
       | programming, I think a lot of Rust's complexity often gets
       | attributed as a trade-of you're making for the safety guarantees
       | afforded by the borrow checker. But I think a lot of the
       | complexity in Rust is not related to the borrow checker at all,
       | and is just a result of certain design decisions in the language.
       | 
       | For instance, taking the module system as an example, in general,
       | I can declare a dependency in mu `Cargo.toml` file like this:
       | `"crate_name"`, and then import it into a given source file using
       | the use declaration: `use crate_name`. However there's a special
       | case, where if the crate name uses dashes, like `"crate-name"`,
       | then the compiler will implicitly resolve to that from a use
       | declaration using underscores: `use crate_name`.
       | 
       | Similarly, if wade into an unfamiliar code-base, and I see a use
       | declaration like this: `use path::to::foo", if I want to look for
       | the code for this, it could be in one of three places:
       | 
       | 1. The file `src/path/to/foo.rs`
       | 
       | 2. The file `src/path/to/foo/mod.rs`
       | 
       | 3. Some other file, based on re-export via a `pub use`
       | declaration.
       | 
       | So in order to use Rust effectively, I have to just sort of know
       | about all these implicit behaviors of the compiler, and in my
       | experience it took months to learn enough of these tricks and
       | corners to really just be able to sit down with an idea and start
       | coding in Rust without consulting documentation and examples
       | regularly. And even after that the compiler still surprises me
       | sometimes. To give another example of somewhat vexing implicit
       | behavior which does relate to the ownership system, just today I
       | had a block of code which looked like this:                   let
       | x = some_value;         match x { ... }         x =
       | some_other_value;         match x { ... }
       | 
       | Which compiled fine. And then by commenting out the second
       | assignment of `x`:                   let x = some_value;
       | match x { ... }         // x = some_other_value;         match x
       | { ... } // <-- use after move
       | 
       | suddenly I had an ownership error, because `x` was moved by the
       | first match statement. So here what was really going on is that
       | the compiler was "helping me" using implicit rules to establish
       | that `x` was referring to a different memory location after the
       | assignment. It's an example of how Rust has all these implicit
       | behaviors and overlaid systems which are intended to make working
       | in a borrow-checked context easier, but in practice what this
       | often means is that when you change something in such a way that
       | one of these implicit systems breaks down, it can cause a failure
       | in what seems like a totally unrelated place, which can be very
       | surprising.
       | 
       | I wonder if part of this has to do with the fact that Rust seems
       | to appeal to a certain type of programmer who is attracted to
       | esoteric topics and arcane knowledge, so the fact that Rust is
       | essentially an unlimited well of complexity is more a feature
       | than a bug to them. But I have been thinking a lot about what a
       | programming language would look like which has an ownership
       | system like Rust, algebraic types, and a trait system, but puts a
       | ruthless emphasis on productivity and eschewing complexity.
        
         | paavohtl wrote:
         | > suddenly I had an ownership error, because `x` was moved by
         | the first match statement
         | 
         | How do you propose it should work instead? Disallow using match
         | with owned values, so that match never moves? Disallow
         | assigning to a variable where the value has been moved,
         | requiring a new or shadowed variable instead? You could do
         | either of those things, but neither would remove complexity,
         | just move it elsewhere and perhaps cause some new issues. It's
         | easy to complain about complexity, but almost all of it exists
         | for a reason, and hasn't been added just because Rust
         | programmers are "attracted to esoteric topics and arcane
         | knowledge". The implicit behavior you are talking about in this
         | case is the ownership system.
        
           | skohan wrote:
           | To me it would be conceptually simpler to consider one named
           | variable as analogous to a memory location. So I would not be
           | able to write to assign to a variable after a move, because
           | essentially I could read that as "assign some_other_value to
           | the memory location x", which in this case is already owned
           | by the match statement. It seems here that after the
           | assignment, `x` is essentially being implicitly re-declared
           | as a shadowed variable. I have to think in terms of "can the
           | compiler find a way to make this safe" rather than having a
           | more-or-less one-to-one mapping between the code I write and
           | the machine behavior.
        
             | sdht0 wrote:
             | > To me it would be conceptually simpler to consider one
             | named variable as analogous to a memory location.
             | 
             | I don't think this will work well in practice. For example,
             | would you want to declare a new variable every time an
             | array is reallocated at a new memory location?
             | 
             | Reusing variables has both ergonomic (don't have to think
             | about and manage new names) and conceptual (x may represent
             | the same "thing", e.g., a reallocated array) values.
             | 
             | And in this particular case, the compiler will very
             | helpfully tell you what went wrong, so it's not like a
             | programmer will have to hunt down the bug for hours trying
             | to understand what happened. The programmer has to learn
             | this once. Can we then call this a complexity issue then?
        
               | [deleted]
        
             | paavohtl wrote:
             | > To me it would be conceptually simpler to consider one
             | named variable as analogous to a memory location.
             | 
             | But that's how it already is! The variable x is
             | conceptually a single memory location within the stack
             | frame. The match statement doesn't own the variable x, it
             | owns its previous contents. It has effectively removed
             | whatever was in x and whatever remains in x's memory
             | location is no longer accessible, but the variable is still
             | there.
        
               | [deleted]
        
         | hitekker wrote:
         | > Rust seems to appeal to a certain type of programmer who is
         | attracted to esoteric topics and arcane knowledge, so the fact
         | that Rust is essentially an unlimited well of complexity is
         | more a feature than a bug to them.
         | 
         | Unfortunately true in my experience. The two engineers I know
         | have advocated strongly for Rust at my company cared more for
         | the technical elegance of their code than the long-term costs
         | of using said code.
        
         | sdht0 wrote:
         | The examples described here surprising. The first two "issues"
         | are handled quite well in an IDE environment, where jump to
         | definition immediately shows me what I'm looking for. And as I
         | said in another comment, the third "issue" is immediately
         | highlighted by the compiler with a clear error message. I have
         | to wonder if these are really complex design wart in the Rust
         | language that I somehow found quite intuitive or comes from an
         | insufficient effort or misunderstanding of how or why these
         | features work. Rust has other complexity issues no doubt, but I
         | don't feel these belong to that discussion.
         | 
         | > I wonder if part of this has to do with the fact that Rust
         | seems to appeal to a certain type of programmer who is
         | attracted to esoteric topics and arcane knowledge
         | 
         | I find this an unfair take, especially having been a witness to
         | the design process in Rust, where a lot of emphasis and effort
         | is put into coming up with designs that have the right
         | complexity-usefulness balance.
         | 
         | A better way to put it is that Rust matches the values that I
         | care about in a programming language [0][1]. What I love about
         | Rust is that I can rely on it to point out a wider class of
         | mistakes that I'd often make in other languages. Forgetting to
         | deallocate a pointer (C/C++) or using multiple objects when
         | trying to run a synchronized code (Java) often need non-trivial
         | time to debug that ultimately don't teach me anything other
         | than to be more careful. In Rust, I can offload that cognitive
         | load to the compiler. And I find that delightful and
         | satisfying. If it compiled, it is highly likely to be correct,
         | moreso after a refactoring session spanning the entire project.
         | And all this makes the effort in learning whatever complexity
         | Rust has worth it. And I say this with the knowledge that the
         | Rust team is doing their best to address the complexity
         | concerns seriously.
         | 
         | > what a programming language would look like which has an
         | ownership system like Rust, algebraic types, and a trait
         | system, but puts a ruthless emphasis on productivity and
         | eschewing complexity.
         | 
         | Do give this a try. It is likely that you'll have to make
         | different tradeoffs. Or you'll discover novel ideas. Either
         | ways, it'll be a learning experience.
         | 
         | [0] https://www.slideshare.net/bcantrill/platform-values-rust-
         | an... [1] https://www.infoq.com/presentations/rust-tradeoffs/
        
       | [deleted]
        
       | adsharma wrote:
       | If you're wondering how to get some adoption without having a
       | killer app written in your language - in this case Zig, there is
       | a second way:
       | 
       | Take programs in an already popular language and transpile the
       | code to your language.
       | 
       | One of my projects py2many is exactly that. It supports 6
       | languages now for a small set of features. Would love to review
       | patches if someone submitted a Zig backend.
        
       | jbluepolarbear wrote:
       | Why do all these recent languages keep using shorthand keywords?
       | Was there a meeting where all language creators decided full
       | keywords aren't cool? It just makes the code unreadable to me.
        
         | leshow wrote:
         | really? for a word you're going to write potentially hundreds
         | of thousands of times you prefer to write out "function"
         | because "fn" is not clear enough?
         | 
         | Short keywords improve readability in a big way, IMO. There's
         | obviously a balance to strike but I think things like "impl"
         | "const" or "mut" or "ref" are pretty obvious.
        
       | pron wrote:
       | Zig's design is so radical, that it completely rethinks how low-
       | level programming can and should be done, rather than improve on
       | one of the existing low-level programming philosophies (C or
       | C++'s). That the result is such a simple and easy-to-learn
       | language that, despite being so radical, doesn't feel foreign is
       | truly an accomplishment.
       | 
       | > In particular, that much of the complexity I'd unconsciously
       | attributed to the domain -- "this is what systems programming is
       | like" -- was in fact a consequence of deliberate Rust design
       | decisions.
       | 
       | I also thought that, and, to be fair to Rust, it is following the
       | tradition of C++ and Ada, two low-level languages that would also
       | easily make the top five most complex languages in history
       | alongside Rust. Until Zig showed up, I, and I think many others,
       | didn't believe that an expressive low-level language could be
       | made so simple, certainly not one that values safety.
        
         | the_duke wrote:
         | What do you find radical about Zig?
         | 
         | I like Zig, but can't see anything particularly revolutionary
         | about it's design.
         | 
         | The features mostly an evolution of concepts found in languages
         | like D or Nim.
         | 
         | Between the two, Rust is much more revolutionary. Although
         | Rust, of course, was also heavily inspired by research
         | languages that came before.
         | 
         | There's very little happening in CS that hasn't already been
         | conceptualized in the 50-70ies.
        
           | yellowapple wrote:
           | > What do you find radical about Zig?
           | 
           | Personally, I find the "bring your own allocator" philosophy
           | to be pretty radical. Yeah, other systems programming
           | languages _can_ facilitate additional allocators beyond
           | "the" allocator for the language's runtime, but Zig seems to
           | optimize for that case, which makes it a lot more intuitive
           | from a learning perspective (no more guessing about where the
           | memory lives). Even Rust (last I checked) defaults to
           | assuming some global one-size-fits-all allocator.
           | 
           | There's also Zig's flavor of compile-time code /
           | metaprogramming. It's probably less powerful than Rust's
           | macros, but I feel like it's a lot cleaner and intuitive, and
           | I'd argue that being able to run ordinary Zig code at compile
           | time is powerful enough of a feature for Zig's use cases.
           | Ultimately, it's a nice happy medium between full-blown
           | metaprogramming (like in Lisp and - from what I understand -
           | Rust) v. preprocessing (like in C/C++).
           | 
           | And yeah, I'm sure there's plenty of prior art for everything
           | that Zig does, but I don't know of any other languages that
           | combine these things in such a simple and intuitive and
           | principle-of-least-astonishment-friendly way Zig does.
        
             | leshow wrote:
             | > Even Rust (last I checked) defaults to assuming some
             | global one-size-fits-all allocator.
             | 
             | You can substitute whatever allocator you want:
             | https://doc.rust-lang.org/std/alloc/trait.GlobalAlloc.html
             | 
             | Unless you're talking about some other restriction I'm not
             | aware of.
        
               | slimsag wrote:
               | In Zig, you can provide a different allocator for a
               | single data structure (or even distinct instances of the
               | same data structure). There is no "global" allocator
               | (what Rust lets you swap out.)
               | 
               | This is vastly more powerful, I have used this to tailor
               | an allocator to specific data structures for better
               | performance.
        
               | steveklabnik wrote:
               | That is what they're referring to, it is a single global
               | allocator, rather than a per-data structure or per-
               | instance one. You can do this in Rust, there's just no
               | abstraction for it. One is coming.
        
               | glandium wrote:
               | I don't know Zig, but it sounds like Zig allows to use
               | arbitrary allocators for anything. The abstraction Rust
               | is getting will only work for things that do account for
               | using arbitrary allocators. Anything that doesn't will
               | end up using the global allocator. That's a significant
               | difference.
        
           | pron wrote:
           | Zig's main feature is what it _doesn 't_ have, and the
           | languages you mentioned don't have that feature.
           | 
           | Other languages also have more-or-less general partial
           | evaluation constructs, but they're not revolutionary because
           | they didn't realise they can express traditional constructs
           | in terms of partial evaluation. Zig is revolutionary in that
           | its simple partial evaluation construct _replaces_ generics
           | /templates, typeclasses/concepts/traits, macros and
           | conditional compilation. The result is something that is
           | consistent, extremely powerful, and yet exceptionally simple.
        
             | philosopher1234 wrote:
             | Hacker news has a really hard time valuing simplicity. I
             | think it's an egotistical thing: I'm smart so I don't need
             | a simple language.
             | 
             | What people miss is that a simple a language allows you to
             | apply your smarts to solving the actual problems in front
             | of you instead of puzzling over language features. You can
             | only handle so much cognitive load at once, and ideally the
             | vast majority of that should be devoted to whatever problem
             | you're solving, not to the language itself.
             | 
             | Ironically, this fact is the same fact that makes complex
             | languages more fun for hobbyists. There's simply more to
             | explore, and to try, and to solve, when your object isn't
             | building a product but instead playing with a language.
             | It's a different purpose, but people very rarely
             | acknowledge this fact, likely because they'd rather pretend
             | their purposes are clearly mechanical and business
             | oriented. It's ok to just want to have fun sometimes.
        
               | cjohansson wrote:
               | In all programming languages you must understand how the
               | parser will understand and translate what you write, it
               | sounds like Zig will always know your intentions and
               | maximize your code, it sounds too good to be true
        
               | the_duke wrote:
               | That's an interesting observation.
               | 
               | It definitely has merit. I've run into this exact same
               | thing in type-system heavy languages like
               | Haskell/Scala/Rust, where I spend more time juggling
               | abstractions than implementing features.
               | 
               | But there is an additional dimension: abstractions often
               | make the first implementation much more cumbersome. But
               | most code is maintained and read much more than it is
               | written, and abstractions can make extending and
               | maintaining a code base much easier.
               | 
               | It's also good to remember that the existence of certain
               | language abstractions doesn't mean you have to use them.
               | 
               | You have to find the right tradeoff.
        
               | philosopher1234 wrote:
               | Another point I disagree with the consensus about!
               | Abstractions have their place, but a bad abstraction ends
               | up spending more of its life getting torn down than it
               | does productively simplifying the code. In my opinion
               | each abstraction increases system cognitive load, and so
               | they should only be added "lazily", ie when empirical
               | experience with the code proves that a particular
               | abstraction would have broad and deep utility.
        
             | the_duke wrote:
             | As mentioned in my other (wall of text) comment, that's not
             | strictly beneficial.
             | 
             | It forces you to implement a lot of logic in "userspace"
             | that other languages do for you automatically.
             | 
             | Complexity for certain abstractions moves from the language
             | to user code, at the expense of consistency, cohesion,
             | totality and (auto-generated) documentation.
             | 
             | It will be interesting to see how things play out for Zig
             | once the ecosystem grows a little and libraries appear, but
             | there are very significant downsides to this approach.
        
               | [deleted]
        
               | pron wrote:
               | Which of those is more beneficial indeed remains to be
               | seen and might end up being purely a matter of personal
               | taste; the very thing you call a downside I see as an
               | upside. I think that talking about the positives
               | "consistency and cohesion" where composing primitives
               | works as a positive is merely a matter of habit. Zig
               | treats some aspects that other languages sees as
               | primitives as if they were any other part of the
               | language, where code and libraries rule rather than a
               | growing collection of primitives. I do agree that in
               | principle a language could be _too_ unstructured for some
               | domains (Lisp?) but interestingly, Zig didn 't go as far
               | as syntax macros, whereas Rust did. Anyway, Zig finds a
               | surprising middle-ground that is, as yet, hard to
               | definitively judge, whereas Rust, for better or worse, is
               | more of the same.
        
               | anp wrote:
               | I was thinking of The Lisp Curse[1] while reading your
               | comment and then you mentioned the language! I'm quite
               | excited by Zig (even if safety is lower priority for it
               | than for Rust, it is really pushing the tooling envelope)
               | but I do wonder whether the "anti-composition hypothesis"
               | here holds up for relevant projects today. Many C
               | programs include shims for compatibility between multiple
               | different "library object models" (hell even strings
               | count here) and they seem to be a common source of
               | security and performance issues. Maybe in the domains
               | where Zig is most competitive that dynamic won't play
               | out? Or maybe comptime provides tools that will still
               | enable composition or at least allow for lower overhead
               | "object model shims"? I suspect that it will be hard to
               | know more about how it plays out until there is more
               | language stability and code sharing, maybe even a
               | repository like npm or crates.io.
               | 
               | [1]
               | http://www.winestockwebdesign.com/Essays/Lisp_Curse.html
        
             | varajelle wrote:
             | As the language evolve and people will want to do actual
             | things with it, features will be added.
        
               | loup-vaillant wrote:
               | Assumption 1: people don't actually use Zig.
               | 
               | Assumption 2: the more we do with a language, the bigger
               | the language has to be.
               | 
               | I am sceptical about (1), and the only way (2) can
               | possibly be true is if the standard library is part of
               | the language (which it really is not: it's user space
               | stuff, curated approved by whoever's in charge). Don't be
               | excessively pessimistic. It's just as irrational as
               | misguided optimism.
        
               | n30phyte wrote:
               | In regards to assumption 1: zig's documentation isn't the
               | greatest, and it's still pre 1.0 which may have turned
               | many potential users away for the moment.
               | 
               | I think the person you were replying to implies that
               | after a certain point (1.0 release?), Zig's userbase will
               | increase to such levels that it can be considered "used"
               | by (many) people
        
               | loup-vaillant wrote:
               | My guess is, a few hundred users are enough to identify
               | and correct most of what's missing in the language. Going
               | from there to a million users is unlikely to make a big
               | difference. Especially if the language's features are
               | orthogonal (apparently they are), and the scope of the
               | language is clear (the intended use case at least seems
               | to be).
               | 
               | We'll see how it goes. I won't bet my hat on it, but Zig
               | does seem to be on a good path to stay simple even as it
               | matures.
        
             | zozbot234 wrote:
             | > Zig is revolutionary in that its simple partial
             | evaluation construct replaces generics/templates,
             | typeclasses/concepts/traits, macros and conditional
             | compilation.
             | 
             | This is what C++98 did, except they called their one true
             | comptime evaluation construct "templates", and they did it
             | by accident. There's a reason why Rust introduced generics
             | and typeclasses separately: C++98 templates as bespoke
             | comptime evaluation was a disaster, and this was clear
             | already in the C++ community.
        
               | pron wrote:
               | > This is what C++98 did, except they called their one
               | true comptime evaluation construct "templates", and they
               | did it by accident.
               | 
               | Right, except not at all, because templates' syntactic
               | elements are distinct from the "object" part of the
               | language, so it is not a partial evaluation construct for
               | C++, but rather a separate (and rather complex) meta-
               | language for C++. In Zig there is just Zig (with its
               | superb error reporting mechanism), and comptime partially
               | evaluates it. Zig distances itself from C++'s problematic
               | design much more than Rust, which, when all is said and
               | done, is pretty darn similar to C++.
               | 
               | But that's the problem with revolutionary design. Your
               | ability to compare it to what came before it is limited
               | because it isn't really similar to anything. Luckily, Zig
               | can be fully learned in a day or two, so there's no need
               | to rely on comparisons for long. You can quickly learn it
               | and decide if it's your cup of tea or not; even if it
               | isn't, you'd have learned something quite refreshing and
               | inspirational, and without spending too much time.
               | 
               | I do agree that there is something more mysterious about
               | Zig. Nobody knows how "good" Rust is yet, either, but
               | it's probably no worse than C++ when we factor all
               | elements that matter to C++/Rust developers, and we're
               | willing to accept that it's also probably not drastically
               | better, except maybe when it comes to undefined
               | behaviour. Zig is more of an unknown because it is so
               | different. It has the potential to be worse than C++, but
               | it can also be _much_ better. At the very least, it is
               | very interesting in that it offers a completely new
               | vision for how low-level programming could be done.
        
           | skohan wrote:
           | The approach to arbitrary compile-time execution seems like a
           | particularly novel feature.
        
             | lasagnaphil wrote:
             | D, Nim, and Haxe had it for quite some time (Along with
             | Jai, which is yet unreleased to the public), although you
             | can argue that Zig's implementation is conceptually the
             | simplest (it has merged compile time semantics with generic
             | types in a unified way).
        
               | Rochus wrote:
               | And Lisp since even a much longer time.
        
               | pron wrote:
               | I see some abstract aesthetic similarities between Zig
               | and Lisp, or some Lisp's at least -- especially their
               | minimalism -- but Zig's partial evaluation (comptime)
               | works nothing at all like Lisp's syntactic macros (there
               | is no quoting construct, and you don't manipulate
               | syntactic terms at all), and, in fact, has much simpler
               | semantics. The result is intentionally weaker than macros
               | -- macros are "referentially opaque" while comptime is
               | transparent[1] -- but Zig's realisation is that you don't
               | need macros -- with their complexities and pitfalls -- to
               | deliver everything a low-level language would need.
               | 
               | [1]: I.e. if x and y have the same meaning ("reference"),
               | in Lisp -- and in any other language with macros -- you
               | could write a parameterised expression e, such that e(x)
               | does not have the same meaning as e(y); you can't do that
               | in Zig.
        
               | Rochus wrote:
               | Thanks. I'm not familiar with Zig. I responded to the
               | "arbitrary compile-time execution" by adding Lisp to the
               | proposed list of D, Nim, and Haxe. Also the latter might
               | raise some concerns when looking at details as you did
               | with Lisp.
        
               | kristoff_it wrote:
               | One subtle but extremely important feature of Zig's
               | comptime is that is emulates the target architecture.
               | Fundamental for implementing correct cross compilation.
        
               | elcritch wrote:
               | That's pretty impressive. It's always annoying to get bit
               | struct alignment issues. :/
        
               | pron wrote:
               | Zig's revolution is not in _adding_ a partial evaluation
               | feature, but in _removing_ many other separate features
               | that can be expressed as mere applications. As Antoine de
               | Saint-Exupery said,  "Perfection is achieved not when
               | there is nothing more to add, but when there is nothing
               | left to take away."
        
         | atombender wrote:
         | I like Zig a lot, but I'm concerned that it's yet another
         | language that leaves memory management up to the developer.
         | 
         | For example, Zig does not appear to have any concept of
         | lifetimes, and does not enforce single mutable ownership. As I
         | understand it, Zig does not have RAII, either, so cleanup (with
         | "defer", etc.) is also left as an exercise for the programmer.
         | Zig has arenas, allowing quick cleanup, but seems pretty bare-
         | bones otherwise.
         | 
         | (I _was_ relieved to see that Zig does not allow unchecked null
         | pointers.)
        
       | the_duke wrote:
       | After writing a lot of Rust, I recently did a small project with
       | Zig to learn the language.
       | 
       | I'm especially impressed with the C interop. You can just import
       | C headers, and Zig will use clang to analyze the header and make
       | all symbols available as regular Zig functions etc. No need for
       | manually writing bindings, which is always an awkward and error-
       | prone chore, or use external tools like bindgen, which still
       | takes quite a bit of effort. Things just work. Zig can also just
       | compile C code.
       | 
       | Rust indeed can feel very heavy, bloated and complicated. The
       | language has a lot of complex features and a steep learning
       | curve.
       | 
       | On the other hand, Rust has an extremely powerful type system
       | that allows building very clean abstractions that enforce
       | correctness. I've never worked with a language that makes it so
       | easy to write correct, maintainable and performant code. With
       | Rust I can jump into almost any code base and contribute, with a
       | high confidence that the compiler will catch most of the obvious
       | issues.
       | 
       | The defining feature of Rust is also the borrow checker and
       | thread safety (Send/Sync), which contribute a lot to the
       | mentioned correctness. Zigs doesn't help you much here. The
       | language is not much of an improvement over C/C++ in this regard.
       | The long-term plan for Zig seems to be static analysis, but if
       | the many attempts for C/C++ in this domain show anything is that
       | this is not possible without severe restrictions and gaps.
       | 
       | Choosing to forego generics and do everything with a comptime
       | abstraction makes Zig a lot easier to understand, compared to
       | Rust generics and traits. The downside is that documentation and
       | predictability suffers. Comptime abstractions can fail to compile
       | with unexpected inputs and require quite a bit of effort. They
       | are also problematic for composability, and require manual
       | documentation, instead of getting nicely autogenerated
       | information about traits and bounds.
       | 
       | Many design decisions in Rust are not inherently tied to the
       | borrow checker. Rust could be a considerably simpler, more
       | concise language. But I also think Rust has gotten many aspects
       | right.
       | 
       | It will be very interesting to see how Zig evolves, but for me,
       | the borrow checker, thread safety and ability to tightly scope
       | `unsafe` would make me chose Rust over Zig for almost all
       | projects.
       | 
       | The complexity of Rust is a pill you have to swallow to get those
       | guarantees, unless you use something like Ada/Spark or verifiable
       | subsets of C - which are both more powerful than Rust in this
       | regard, but also a lot more effort.
       | 
       | Some smaller paper cuts, which are partially just due to the
       | relative youth of Zig:
       | 
       | * no (official) package manager yet, though this is aparently
       | being worked on
       | 
       | * documentation is often incomplete and lacking
       | 
       | * error handling with inferred error sets and `try` is very nice!
       | But for now errors can't hold any data, they are just
       | identifiers, which is often insufficient for good error reporting
       | or handling.
       | 
       | * No closures! (big gotcha)
        
         | kristoff_it wrote:
         | Some context on the issues you pointed (all true, to be clear):
         | 
         | > * no (official) package manager yet, though this is aparently
         | being worked on
         | 
         | It's the next item on the roadmap as soon as the self-hosted
         | compiler is good enough.
         | 
         | > * documentation is often incomplete and lacking
         | 
         | The language reference is good, but for the stdlib it's best to
         | just read the source code. For other miscellaneous learning
         | materials:
         | 
         | https://ziglearn.org/
         | 
         | https://github.com/ratfactor/ziglings
         | 
         | https://www.youtube.com/c/ZigSHOWTIME
         | 
         | > * error handling with inferred error sets and `try` is very
         | nice! But for now errors can't hold any data, they are just
         | identifiers, which is often insufficient for good error
         | reporting or handling.
         | 
         | It's still under debate and I'm personally in the camp that
         | errors should not have a payload, so I would avoid assuming
         | that it's definitely the preferable choice. We already have a
         | couple of existing patterns for when diagnostics are needed.
         | That said proposals about adding support for error payloads are
         | still open, so who knows.
         | 
         | https://github.com/ziglang/zig/issues/2647
         | 
         | https://github.com/ziglang/zig/issues/7812
         | 
         | Existing pattern:
         | https://github.com/ziglang/zig/issues/2647#issuecomment-5898...
         | 
         | > * No closures! (big gotcha)
         | 
         | It's possible to create closures already (by declaring a struct
         | type with a method (the closure) and instantiating it
         | immediately after), but it's a clunky solution. We also have an
         | open proposal in this space:
         | 
         | https://github.com/ziglang/zig/issues/6965
        
           | the_duke wrote:
           | The new compiler is very innovative and a distinguishing
           | feature, but I would recommend giving a higher priority to
           | package management.
           | 
           | A package manager is more or less expected by developers now,
           | and I bet you will see a lot more adoption once it's easy to
           | publish and consume libraries with an official packager
           | manager and online repository.
        
             | gwenzek wrote:
             | IIUC the line of thoughts is that the current compiler is
             | too slow, and that it doesn't show on a small project. But
             | if you imagine a big project with 100 dependencies, you'll
             | be really slowed done. The new compiler is faster (I think
             | there are benchmarks in the repo) and can compile debug
             | builds without going through LLVM.
        
           | coder543 wrote:
           | > It's still under debate and I'm personally in the camp that
           | errors should not have a payload, so I would avoid assuming
           | that it's definitely the preferable choice.
           | 
           | How can you argue that _not_ having access to string index
           | where the JSON was unparseable is better than having access
           | to it? I read through issues /2647, and I read through the
           | "existing pattern", and it just seems obvious to me that
           | containing the error information _in the error_ is better
           | than trying to hack it through side channels.
           | 
           | If A -> B, and B returns an error through a side channel,
           | then A can use it and "what's so bad about that?"
           | 
           | But this doesn't seem to scale very well.
           | 
           | If A is refactored to use an intermediate function X, then it
           | just doesn't work. A -> X -> C would mean that...
           | 
           | X cannot use Zig's normal error handling syntax to
           | automatically propagate this error that A is better equipped
           | to handle, unless we're going to further stipulate that A
           | _must_ pass this Diagnostics struct _into_ X which then must
           | pass it into B.
           | 
           | If we now assume that X calls into two fallible functions, B
           | and C, and each of them provide their own diagnostics
           | structs, then X will have to take in two "out" arguments that
           | provide the diagnostics, and every single caller of X will
           | have to provide those two values.
           | 
           | You see how this goes. It just doesn't seem scalable.
           | 
           | Why not take the current design to its logical conclusion of
           | simply having every function return a Boolean indicating
           | whether it succeeded or not, and then require the caller to
           | look into some "out" argument to determine what the error
           | was? Obviously, that would be extremely annoying.
           | 
           | A tagged error union containing the diagnostic error values
           | is just a minor evolution of the current design that brings
           | huge wins for language ergonomics.
           | 
           | For errors that don't need to carry a value, there is no
           | additional cost: they compile and work exactly as they do
           | today.
           | 
           | For errors that carry a value, the size of the error struct
           | is only the size of the largest error value (plus the tag, of
           | course), not the product of all possible error values, so the
           | global error set will never grow to be enormous unless you
           | have some very weird error type. In which case, you can solve
           | this problem by fixing that error type.
           | 
           | So, I'm not deeply versed in Zig, but I have personally
           | argued in favor of Zig's async design, which seems
           | exceptionally interesting. I had not realized until now how
           | limiting the error system implementation was, but I had
           | superficially appreciated how much less verbose it was than
           | Rust's where you tend to hand write these error enums, or use
           | a bunch of code gen. However, errors benefit tremendously
           | from having the ability to supply payloads.
           | 
           | No one _is required_ to act on the payloads within the
           | errors, but no one _is able_ to if they don 't exist.
        
             | dnautics wrote:
             | For those specialized cases, just have your function emit
             | an error/ok type union and early exit with the error
             | information.
        
               | coder543 wrote:
               | If the standard library doesn't do follow that
               | convention, then it doesn't matter what I do in my code.
               | I won't get the information I need from the standard
               | library, and third party libraries are unlikely to follow
               | this ad hoc convention. I'm certainly not willing to
               | rewrite the entire world to follow this ad hoc
               | convention.
        
               | dnautics wrote:
               | You make a good point that the standard library could
               | make better use of this pattern to show it off, but the
               | language was designed with this sort of thing as an
               | affordance and there even is a (rough) example in the
               | docs: https://ziglang.org/documentation/master/#Tagged-
               | union
               | 
               | You wouldn't have to rewrite the world, but the language
               | is young and maybe more effort could go into making using
               | this pattern more idiomatic and encourage library writers
               | to do it more often.
        
             | kristoff_it wrote:
             | The problem is for all the situations where the error
             | payload isn't needed. You now need to carry around the
             | extra syntactic complexity and/or the extra wasted memory
             | (unless you have a strategy to elide all of this stuff when
             | error payloads are not needed).
             | 
             | I think it's not trivial to find a good alternative to
             | status quo.
        
               | coder543 wrote:
               | "wasted memory"... this stuff exists on the stack, right?
               | And we're talking on the order of like 64 bytes or less
               | in common, practical scenarios?
               | 
               | I believe the solution has been clearly presented to
               | those who are listening, and the downsides are so much
               | smaller than the status quo.
               | 
               | If people on the language team are unwilling to provide
               | developers with the tools to make more robust software
               | because of something like 64 bytes of stack memory... I
               | find that pretty shocking. And yes, I mean that it is
               | extremely difficult to diagnose and fix issues in
               | production software when you only get back a static error
               | value that lacks the dynamic error context that a payload
               | would provide. I've been there, done that. Do not want
               | that again.
               | 
               | Such a strong viewpoint (avoiding such error payloads)
               | could at least focus more on the technical aspects of how
               | best to implement the elision of unused error payloads
               | instead of just broadly opposing the concept, since it
               | should be obvious how beneficial those payloads are, and
               | the only question is how to remove them for developers
               | who literally cannot spare dozens of bytes of stack
               | memory. (Which I find hard to believe outside of AVR or
               | PIC microcontrollers, which is probably not the best use
               | case to be optimizing for in a new language.)
               | 
               | Elision is an optimization that can be added later. It's
               | not obvious that it can't be done, and there's no clear
               | reason that it has to be solved first... I just don't
               | think anyone would actually care enough to implement it
               | once they see how nonexistent the negative impacts are,
               | but it would be a cool optimization.
               | 
               | But, maybe everyone who upvoted the apparently most
               | upvoted GitHub issue on the Zig repo (including myself)
               | is just completely wrong and this obvious solution is
               | actually secretly terrible. It's entirely possible.
               | 
               | Your comment has not really done anything to help me
               | believe that I'm wrong, though, unfortunately.
               | 
               | But, as I'm basically "no one" in this context, my
               | opinion probably doesn't really matter.
        
               | dnautics wrote:
               | It's not that the "obvious solution" is so terrible, it's
               | that the workaround (not needed in 99% of cases) is
               | really easy and arguably good architectural practice. You
               | can emit an error/ok union and have the error return the
               | structured information.
               | 
               | Note that this isn't like go's "if err = nil"
               | monstrosity, either.
        
               | coder543 wrote:
               | > You can emit an error/ok union and have the error
               | return the structured information.
               | 
               | I already addressed how you're apparently giving up all
               | of the benefits of Zig's error handling system to do
               | this. That level of convention breaking isn't a good sign
               | for anything. The current convention really is that bad,
               | from my point of view.
               | 
               | The obvious solution's extremely tiny overhead could
               | "easily" be avoided in the almost non-existent cases
               | where it matters.
               | 
               | I want robust software by default, not software where I
               | have to use side channel hacks to get the information I
               | need by default... but maybe that's just me. (And
               | everyone else who upvoted that GitHub issue)
               | 
               | Of course Zig programmers would be unlikely to see the
               | issue here -- naturally the people left are the ones who
               | don't see the problem. People like myself who know from
               | past experience how problematic not having error context
               | is are likely to just avoid Zig until it meets our
               | minimum requirements.
               | 
               | That's not a problem for existing Zig users -- it works
               | for them -- but it is annoying to people like me who
               | think Zig otherwise has a number of interesting aspects.
        
               | dnautics wrote:
               | You're missing the point. Let me give a direct analogy:
               | In erlang there is a fantastic error system, but
               | sometimes you want to emit an ok/error tuple instead.
               | There is a generally sane heuristic of when you do and
               | don't want to use the error pathways, it's a part of
               | making good engineering choices. Generally speaking it's
               | the case where when you want structured errors you use
               | the tuple.
               | 
               | It's possible that erlang developers are merely
               | internalizing sone pain, but it's also a robust system
               | that people have been developing highly reliable and
               | broadly used (e.g. rabbitMQ) systems architectures in, so
               | the choice can't be all that bad.
        
               | ericbarrett wrote:
               | Languages should be careful eliding error payloads--it's
               | a good way to end up with every library rolling their
               | equivalent to C's errno.
        
               | coder543 wrote:
               | Eliding would be done by the compiler where the values
               | aren't used. That's whole meaning of the word "elide" in
               | a language context.
               | 
               | From the developer's point of view, the values would
               | always be there, just unused. The compiler would just
               | make them disappear at compile time if unused.
        
               | ericbarrett wrote:
               | > You now need to carry around the extra syntactic
               | complexity and/or the extra wasted memory
               | 
               | This quote is from your GP post, and I read this as
               | arguing against having the mechanism for getting this
               | payload in the language (and hence the compiler itself),
               | so my language was intended. Am I misunderstanding?
        
               | coder543 wrote:
               | Ah. I think I see what you were saying now. The word
               | "elide" has different connotations to me than the word
               | "omit". Zig's omission of error context is something I
               | agree is problematic.
               | 
               | (This could just be a quirk of my own vocabulary)
        
               | dilap wrote:
               | People are lazy, though. I think if errors can't take
               | payloads, it's inevitable you'll end up with many
               | libraries that don't return error payload information
               | when you wish they did. This will trickle out into
               | software using the libraries, where the end-user will get
               | errors that don't include as much useful info as you'd
               | like. So to my mind, not supporting error payloads in a
               | first class way is contrary to Zig's goal of enabling
               | perfect software. :-)
        
               | the_duke wrote:
               | There are so many situations where an error payload is
               | either mandatory for properly handling the error, or
               | required to get somewhat decent error output...
               | 
               | Side channels are not really a feasible implementation
               | option.
               | 
               | So you have to resort to maintaining the logical
               | information on intermediate levels, returning a result
               | sum type, or stick to good old C paradigms and use a
               | output param. Which negates all value that error sets
               | offer.
               | 
               | I understand that it's not trivial to implement, but for
               | me it's a sort of must to avoid ending up with messy
               | APIs.
        
         | lrem wrote:
         | Why keep two competing languages in your toolbox? I personally
         | do C++ and Python and will pretty much never voluntarily choose
         | another language from their respective niches... Except I'm
         | looking forward to replacing C++ with Rust (any year now), at
         | which point I'll not choose C++ again.
        
           | flohofwoe wrote:
           | Because no single language can (nor should) be a good match
           | for solving all types problems. For this reason it's better
           | to be fluent in 5 (or so) small languages than one big
           | language IMHO.
        
             | lrem wrote:
             | Are we still talking general purpose languages? What is
             | your list of five languages?
        
               | flohofwoe wrote:
               | Most used first: C (C99 is different enough from the
               | common C/C++ subset that it counts as its own language
               | IMHO), Python, C++ (up to around C++11), Javascript, and
               | recently more and more Zig. Less then I would like: Go
               | (since currently I don't do much server backend stuff).
               | 
               | PS: forgot Objective-C, for coding against macOS APIs.
        
               | FpUser wrote:
               | >"Less then I would like: Go (since currently I don't do
               | much server backend stuff)."
               | 
               | I do loads of server backend in C++. Never felt that I
               | need anything else for this kind of stuff. Sometimes due
               | to client's insistence I did it in other languages but it
               | was their choice.
        
         | The_rationalist wrote:
         | Regarding the automatic C interop JVM languages now have it
         | either through jextract or graalVM.
        
         | tastyminerals wrote:
         | You should also try D to see that seamless C interop has always
         | been there for many years.
        
           | the_duke wrote:
           | Correct me if I'm wrong, but I don't think D has the ability
           | to just import C headers and seamlessly use them without
           | having generated or manually written `extern` declarations?
        
             | lenkite wrote:
             | An extern declaration is needed.
             | https://dlang.org/spec/interfaceToC.html
             | https://dlang.org/spec/cpp_interface.html
             | 
             | C (or even C++) functions can be called directly from D.
             | There is no need for wrapper functions, argument swizzling,
             | and the C functions do not need to be put into a separate
             | DLL.
             | 
             | I think Walter Bright achieved this via implementing a
             | full-blown C++ parser in the dlang compiler. A [God-Tier]
             | achievement.
        
             | chromatin wrote:
             | Not quite as seamless as Zig, but dstep is an external
             | program that leverages libclang to do the same thing (and
             | generates a D module for you), as well as e.g., smartly
             | convert #define macros to inlineable templates functions :)
             | 
             | https://github.com/jacob-carlborg/dstep
        
         | kelnos wrote:
         | > _On the other hand, Rust has an extremely powerful type
         | system that allows building very clean abstractions that
         | enforce correctness._
         | 
         | I love Scala because of this as well, and put up with the slow
         | compile times and large runtime needed because I very much
         | value that correctness. I get a lot of the same with Rust (and
         | more, like data races being a compiler error), with the added
         | bonus that so many of its abstractions are zero-cost.
         | 
         | The more I build software, the more I want strong type systems
         | that I can lean on to help ensure correctness. Obviously that
         | won't eliminate all bugs, but building reliable software on a
         | deadline turns out to be really hard, and if a compiler can
         | tell me I'm doing things wrong before it becomes an expensive
         | mistake in production, that's worth the added effort it takes
         | to write in a language that can help me in this way.
         | 
         | It seems like Zig is approaching it from the other side: a
         | "better C". I don't really want a better C; I want a Scala that
         | runs with the CPU and memory footprint of a C program. Rust is
         | probably as close as I'll get to that.
         | 
         | Unfortunately I've found that a lot of developers -- especially
         | senior ones -- don't want to learn anything new, and want to
         | keep churning out the same overengineered, overabstracted,
         | exception-oriented, mutable-spaghetti Java code, year after
         | year. Reminds me of the saying that some senior developers have
         | one year of experience, repeated ten times.
        
       | diegocg wrote:
       | This is something I have been reading more and more lately -
       | "Rust is complex". In the past, people usually brushed it off
       | saying that it's much simpler than C++. But that always felt like
       | saying that a mountain is not very high because it's smaller than
       | the Everest
        
         | darthrupert wrote:
         | It has essentially become the very thing it sought to destroy.
         | Choosing Rust over C++ is now mostly about the vastly superior
         | package ecosystem, thanks to Cargo.
        
           | chubot wrote:
           | Rust's tools are better for common cases, but C++ has the
           | vastly superior set of libraries: GPU, embedded, robotics,
           | desktop, mobile, etc.
           | 
           | I recommend watching CppCon and being amazed at how much work
           | is going into the C++ ecosystem right now. Rust is popular in
           | some circles but the programming world is extremely big.
        
             | cbHXBY1D wrote:
             | > Rust is popular in some circles but the programming world
             | is extremely big.
             | 
             | And HN only represents a tiny viewpoint of the programming
             | world.
        
         | pas wrote:
         | Rust's philosophy is "frontload the problems", thus complex
         | problems are complex right away. It means it takes a lot of
         | thinking about design, fiddling with data structures, types,
         | looking for elegant design optimizations, but then it works as
         | "intended", compared to a lot of other tools/langs.
         | 
         | Here the author states that the hard (error prone) part is not
         | the coding, but the transliteration from the manufacturer's
         | data sheets. So Rust seems to be adding complexity for no gain
         | at all. (Which is completely fair for a hobbyist project for a
         | keyboard firmware.)
         | 
         | Does this mean Rust should only be used for big systems where
         | that mandatory explicitness about complexity pays off? Does
         | this mean Rust perhaps would benefit from a mode where certain
         | modules/functions are type checked in a different way? (Or that
         | would just make the language even more complex for no
         | significant gain during programming?)
        
         | fluffything wrote:
         | I mean, the author could have removed most of the complexity by
         | using Rust HALs.
         | 
         | They just decided to reimplement from scratch their own
         | different solution. That's completely fair, but doesn't allow
         | you to make the complexity argument.
         | 
         | It would be like deciding to start a C++ project without using
         | the C++ std library and arguing that C++ is hard because you
         | had to reimplement std::Tuple...
        
           | detaro wrote:
           | > _I mean, the author could have removed most of the
           | complexity by using Rust HALs._
           | 
           | What are the "nrfXXXXX-hal" crates they use that provide the
           | Port/Pin types then? What do the HALs provide in addition
           | that would have helped?
        
       | lenkite wrote:
       | This is my first time reading Zig code and I actually understood
       | most of it. The Rust version just vaguely flew over my head.
        
       ___________________________________________________________________
       (page generated 2021-03-07 23:00 UTC)