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