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