33 ms·
Why I rewrote my Rust keyboard firmware in Zig: consistency, mastery, and fun
- kristoff_it 6y agoIt seems that `inline for` is the corner of `comptime` that newcomers appreciate the most. See this other example: https://clips.twitch.tv/RockyLivelyChimpanzeeDoubleRainbow-Pov6fVYDbH1_r-A6 https://clips.twitch.tv/RockyLivelyChimpanzeeDoubleRainbow-P... Full video available here: https://www.youtube.com/watch?v=O4UYT-brgrc https://www.youtube.com/watch?v=O4UYT-brgrc
- flohofwoe 6y agoI 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" :)
- IshKebab 6y agoAbsolutely. I don't know Zig and was confused why inlining a loop would help. `comptime for` is 10 times clearer.
- kristoff_it 6y agoThe 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/ https://kristoff.it/blog/what-is-zig-comptime/
- IshKebab 6y agoRight 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.
- nyberg 6y ago`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 https://github.com/ziglang/zig/issues/7224
- AndrewDucker 6y agoI thought this was really interesting. Would love to know whether he had made mistakes or if these are necessary Rust pain points.
- tal8d 6y agoHe 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.
- zserge 6y agoZig 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 6y agoI 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?
- detaro 6y agoDo I interpret your comment correctly that you think Zig might follow a similar path?
- rjzzleep 6y agoNope, 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 6y ago> 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?
- shp0ngle 6y agoYes, 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.
- samuell 6y agoIt 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.
- cb321 6y agoAs 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 https://nim-lang.org )
- samuell 6y agoGood explanation, thanks!
- chris_st 6y agoNim has garbage collection, Zig you must manage memory on your own. Each side has pros and cons.
- samuell 6y agoAh, how did I miss that. Thanks!
- WJW 6y agoAre 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 6y agoYeah because it is no longer notable to be using Rust. It is notable to be using Zig.
- keyle 6y agoOut of interest, genuinely asking, Go is out of the question due to the GC?
- detaro 6y agoAnd 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 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 6y agoEven 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 6y agoTinyGo 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 https://tinygo.org
- dbaupp 6y agoI 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 6y agoIt 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 6y agoMaybe defining a "Port" trait and using the dynamic type instead of the static one is a solution.
- rcxdude 6y agoPerhaps, 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 6y agoenum_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/ https://docs.rs/enum_dispatch/0.3.5/enum_dispatch/ )
- ikskuh 6y agoMost 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"
- 6y ago
- pron 6y agoZig'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 6y agoWhat 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.
- skohan 6y agoThe approach to arbitrary compile-time execution seems like a particularly novel feature.
- lasagnaphil 6y agoD, 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 6y ago
- the_duke 6y agoAfter 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)
- diegocg 6y agoThis 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
- fluffything 6y agoI 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 6y ago> 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?
- pas 6y agoRust'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?)
- darthrupert 6y agoIt 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.
- spiritplumber 6y agoyou know what you doing!
- sammorrowdrums 6y agoI 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 6y agoAuthor 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 6y agoAh, 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 6y agoFirst, 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.
- jbandela1 6y agoI 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 https://gcc.godbolt.org/z/dcxnTo
- pharmakom 6y agoI think Rust macros are actually more powerful than templates
- bjz_ 6y agoThey 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 6y agoIt's not a panacea, but enum_dispatch seems to help a lot: https://docs.rs/enum_dispatch/0.3.5/enum_dispatch/ https://docs.rs/enum_dispatch/0.3.5/enum_dispatch/
- lulf 6y agoHaving 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/typestate-programming.html https://docs.rust-embedded.org/book/static-guarantees/typest... [1] https://github.com/drogue-iot/drogue-device/blob/main/rt/src/driver/led/matrix.rs#L12 https://github.com/drogue-iot/drogue-device/blob/main/rt/src...
- lynaghk 6y agoAuthor 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/ https://www.state-machine.com/.
- jll29 6y agoGreat 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 6y agoThe 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 https://en.wikipedia.org/wiki/Affordance
- ant6n 6y agoThe consulting world likes MECE - mutually exclusive collectively exhaustive.
- geogriffin 6y agoFWIW, 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.
- skohan 6y agoMy 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.
- pornel 6y agoAnother 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 {…}
- ncmncm 6y agoWhile 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 6y ago> 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 6y agoAs 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.
- deleted 6y ago[deleted]
- skohan 6y ago> 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.
- vasergen 6y agoHow 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
- wolf550e 6y agoAFAIK Go has a GC you can't avoid, so it's not very suitable for embedded or realtime.
- deleted 6y ago[deleted]
- coder543 6y ago"not very suitable" is a relative term. TinyGo is a cool project: https://tinygo.org https://tinygo.org There are various conference presentations about it that show it in action.
- IshKebab 6y agoCool 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 6y agoLots 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/ https://tinygo.org/usage/important-options/ (look at -gc=none) Another interesting link: https://tinygo.org/compiler-internals/heap-allocation/ https://tinygo.org/compiler-internals/heap-allocation/
- lenkite 6y agoThis is my first time reading Zig code and I actually understood most of it. The Rust version just vaguely flew over my head.
- dilap 6y agoHere's another example of the coolness of Zig's comptime code execution: https://github.com/ziglang/zig/commit/0808d98e10c5fea27cebf912c6296b760c2b837b 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_Arrays 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!
- thechao 6y agoI took one look at this and realized you could do the same thing in C++17 using the perfect-flat-reflection.
- p0nce 6y agoSame in 2016 with D https://maikklein.github.io/soa-d/ https://maikklein.github.io/soa-d/
- egeozcan 6y agoD 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.
- littlestymaar 6y agoAs 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.
- bla3 6y ago“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 6y agoRust 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.
- 59nadir 6y ago> 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.
- zelphirkalt 6y agoI 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.
- frabert 6y ago> That it not necessarily true It is necessarily true if you allow non-tail recursive calls.
- zelphirkalt 6y agoThe 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 6y agoCorrect 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 https://ziglang.org/documentation/0.7.1/#call [1] https://github.com/ziglang/zig/issues/1006 https://github.com/ziglang/zig/issues/1006
- simonhamp 6y agoThis 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 6y agoOkay. 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 6y ago> 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 6y agoYou 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/Dingle07Ownership.pdf https://researcher.watson.ibm.com/researcher/files/us-bacon/...
- joshgoldman 6y agoWhy not use micropython?
- michaelcampbell 6y agoI 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.
- flohofwoe 6y agoThe 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).
- afavour 6y ago> 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 6y agoI think libraries are a better place for this sort of code reuse than builtin language features though ;)
- loloquwowndueo 6y ago“ 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.
- flohofwoe 6y agoTBF 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 6y agoDocumentation 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!
- rudedogg 6y agoThis was discussed recently on Zig Showtime: https://youtu.be/pacsngNYXI0?t=2217 https://youtu.be/pacsngNYXI0?t=2217
- deleted 6y ago[deleted]
- jbluepolarbear 6y agoWhy 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 6y agoreally? 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.
- jbluepolarbear 6y agoI do prefer function over fn. I use a modern predictive text completions IDE with fuzzy typing, I don’t care if the keyword has 20 characters. I much prefer explicit keywords for readability. I think the struct, trait, impl, and fn relationships and differences are not only weird and intimidating, the shortened names add to the large barrier for entry.
- leshow 6y agoOddly, I would argue that the shorter words are better also "for readability".
- sly010 6y agoLooks 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.
- deleted 6y ago[deleted]
- tubularhells 6y agoI guess Rust isn't hipster enough anymore.
- higerordermap 6y agoThey are silent now. Expect a rust post within 48 hours.
- alcover 6y agoI 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 6y agoWould you also consider C++ a specialized, hard-to-use tool?
- nromiun 6y agoYes, 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).
- alcover 6y agoMy 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.
- axepeartree 6y agoWrote a version of the loop using an enum: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=1d4173925a9bcce26ac90d27a83adea8 https://play.rust-lang.org/?version=stable&mode=debug&editio...
- _pmf_ 6y agoRust is the systems language of choice for overengineering Silicon Valley procrastinators.
- higerordermap 6y agoPanicking on OOM is not probably overengineering.
- eximius 6y agoGreat 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.
- loup-vaillant 6y ago> 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.
- geodel 6y ago> 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 6y agoI'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 6y ago> `inline for` is pretty special Again: `inline for` has been in D for more than 10 years.
- nromiun 6y ago> 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.
- chubot 6y agoThis brings to mind Type Checking vs. Metaprogramming; ML vs. Lisp https://www.oilshell.org/blog/2016/12/05.html 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 6y agoA 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.
- tbojanin 6y ago> "Rather than any fancy safe resource management scheme, I used mutable globals. YOLO:" Made me chuckle
- cjohansson 6y agoThanks for the blog post, was an interesting read
- losvedir 6y agoGenerally 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 6y agoMany 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-unsafety-and-security/ https://alexgaynor.net/2020/may/27/science-on-memory-unsafet...
- roca 6y agoPartitioning memory-unsafe code into lots of little OS or WASM sandboxes each running with least-privilege would be interesting, but I think it would get really costly at runtime and at development time, because they all have to have disjoint address spaces one way or another and managing the privileges gets crazy. We don't see people actually doing this in practice (although some WASM people are thinking about it). You would have to be very aggressive about making the partitioning as fine as necessary, otherwise memory unsafety leads to privilege escalation. For example if memory unsafe code in a single sandbox can download arbitrary PDFs and render them, then a malicious PDF will be able to scan scan local networks and exfiltrate data.
- adsharma 6y agoIf 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.
- userbinator 6y agoAs 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%20and%208048%20ROM%20disassemblies.html http://halicery.com/Hardware/Intel%208042%20and%208048/8042%...
- mmoskal 6y agoTimes are changing. You can get 32-bit MCUs for under $0.30. This will continue to fall as fabs used for MCUs are 15 years behind the high end stuff, so they have at least that much more Moore's law in them. You can say that 8-bit ones are always going to be cheaper (you can get some for under $0.03 now) but at some point packaging not silicon costs will dominate. Also programmer productivity is very often worth more than a few cents of BOM cost, which is why we are seeing HLL languages used more often in embedded space even in production settings. As a side note, the NRF52 listed in article have a 32 bit config register per pin (granted the pin direction is separately mapped as 32 bit word per port).
- scoutt 6y ago> Times are changing. The same thing was said when C# came out, 20 years ago. "Now hardware is cheaper and software costlier"... "C# is the language of the future and will replace X language (and X++ too)"... "Just add some GB of RAM to the server and that's it, since it costs nothing". Still, we see everyday new articles on HN about new "low-level" or "system" languages, with focus on performance, speed, safety and whatever, on today's hardware. Even today, there is still a need to write low-level, fast software, trying to squeeze every cycle and bit of RAM. And C and C++ are still alive. > Also programmer productivity is very often worth more than a few cents of BOM cost. Guess what would happen if I say my boss that a $0.03 MCU can do the same thing as a $0.30 MCU? What would my boss choose? My mental health, or his pocket? $0.27 x 1000000 = $270000. That's another Ferrari.
- RantyDave 6y agoI'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 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/kscan.html https://docs.zephyrproject.org/latest/reference/peripherals/...). https://github.com/zephyrproject-rtos/zephyr/blob/master/samples/drivers/kscan/src/main.c https://github.com/zephyrproject-rtos/zephyr/blob/master/sam...
- leshow 6y agofn 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 {}
- bsder 6y agoI would like to point out something that may not be obvious: he is running both Rust embedded and Zig on embedded AND BOTH WORKED. And we're not discussing that as amazing. Those of you not in embedded may not get just how stunning an achievement that is from "C works sometimes and nothing else does ever". And a lot of that has been the Rust embedded guys banging on everything in the embedded space. Language, IDE, debugging, hardware probes, everything. Even if you're not in Rust, have a cheer for the Rust embedded guys because they are laying down pathways that folks like this can walk through with other languages like Zig.
- cletus 6y agoThis was a great post for one reason in particular: it led me down a huge rabbit hole about Zig, which honestly I knew little about before this. And further, into what is perhaps Rust's achilles heel: compile times. Zig does look interesting. I'm still wrapping my head around the consequences of comptime but fast compile times and easy interoperability with C (C to Zig and Zig to C) are all positives. And yes I know Rust can compile to a C ABI. It's also interesting to me that the Zig compiler only verifies if a particular instantiation of a "generic" is correct, not all versions. This makes it closer in some ways to C++'s templates (which, at the end of the day, are little more than string substitution). Like the functor pattern arose in C++ and was made possible by essentially just stringing symbols together and the compiler telling you if you could dereference a type (if you could ever figure that out from 8 pages of template errors in gcc; clang was better). Rust it seems also generates a ton of IR and leaves the LLVM backend to optimize it away. I found an article talking about the history of how this evolved from the early days of Servo and Rust. I know there's an ongoing effort to, say, add constexpr like behaviour to Rust. I worry if we'll get to a point where we are with C++, where we constantly bolt things onto it to the point that the language is incredibly complex, forcing people to use a sand and minimal subset.
- Ambroisie 6y agoDo you still have a link to that article about rust's IR generation?
- cletus 6y agoSorry, yes: https://pingcap.com/blog/rust-compilation-model-calamity https://pingcap.com/blog/rust-compilation-model-calamity HN post: https://news.ycombinator.com/item?id=22197082 https://news.ycombinator.com/item?id=22197082
- indepnd 6y agoAs a fellow Rust learner-and-user, I felt lots of points in the article are spot on. I recently wrote a custom HTTP framework in Rust and had spent 2 weeks to figure out how to store a collection of async closure so I can call any of them later. The idea / goal was very clear, but it was so hard for me to figure out how to implement it in the language. Maybe I'm not smart enough for Rust, but I've already used Rust for more than 1 year, and still struggled to use it. I don't know if I should switch to a different language, e.g. Golang.
- aboodman 6y agoIf you haven't worked with Go you should definitely do it, just to compare. It's hard to imagine two popular mainstream languages further apart in spirit than these two. Go is so focused on simplicity that you'll be up and running (and have your server working) in like a day, tops. Then you can ponder the tradeoffs from a place of (some) knowledge not hypotheticals.
- junon 6y agoEvery time Zig is mentioned I always have to post a most-likely-downvoted warning: The creator of Zig has expressed some disregard toward proper security in the current prototypes. The standard library is riddled with DOS vulnerabilities as of right now, and reports for them are closed as "The standard library will be rewritten." or the like. Take caution.
- perryizgr8 6y agoWhy wouldn't you just use C for this use case? There is no memory leak or safety problems in a keyboard controller. C is easy and designed for things exactly like this, generates smallest possible executables, runs as fast as possible, every single chip on the planet has a C toolchain. Zig looks like it may be almost as good as C in this case, but IMO it was madness to try Rust here.
- nudpiedo 6y agoFor me that's the key thing: > Despite all this, I still don’t feel comfortable with Rust. It feels fractally complex — seemingly every time I use Rust on a new project, I run into some issue that forces me to confront a new corner of the language/ecosystem. Developing my keyboard firmware was no exception: I ran into two problems, and each required learning a completely new language feature. There must be a balance between features, security & robustness, speed and easy of use. It all lays down to language design, and I guess we need a new language design paradigm, not a programming paradigm, but a new design paradigm on programming languages
- deleted 6y ago[deleted]
- jackjohn 6y agoLooks like Rust isn't a programming language but a set of DSLs, some of which are Turing complete.
- nahuel0x 6y agoThe Rust vs Zig discussions seems to echo the dichotomy between the declarative and imperative languages, but at the type level. In Rust (as is common in other static languages) you define the types declaratively, while Zig takes the poorly explored route of defining the types imperatively. The declarative way usually is more understandable, cohesive, composable and tool friendly, but not so flexible and understandable-in-the-small as the imperative one.
- travisgriggs 6y agoInteresting write up. I've written zero Rust and only a few toy examples in Zig. I do write quite a bit of straight C on microcontrollers. "...every time I use Rust on a new project, I run into some issue that forces me to confront a new corner of the language/ecosystem." This was the tour de force comment for me. This is how I feel about web programming, Swift, growingly so about Kotlin. I figure you can use Rust as a mutable variable for many of the "new languages" for developers to use. We didn't have time to write you a short language, so we wrote you a complex one instead. Reminds of laws in Congress these days.
- augustk 6y agoThis reminds me of a passage in The BlackBox Framework about Niklaus Wirth's approach to language design: "Wirth’s philosophy of programming languages is that a complex language is not required to solve a complex problem. On the contrary, he believes that languages containing complex features whose purposes are to solve complex problems actually hinder the problem solving effort. The idea is that it is difficult enough for a programmer to solve a complex problem without having to also cope with the complexity of the language. Having a simple programming language as a tool reduces the total complexity of the problem to be solved."
- deleted 6y ago[deleted]
- cztomsik 6y agoI've renamed my working branch to never-ending-story because that's how I feel about rust, it's not fun anymore. Often, I do a small PoC which works but then I put it inside bigger project I'm hitting one wall after another. Sure, simple examples work fine but advanced usage almost always break and I don't have any expectations anymore (I will avoid new features next time). Some examples: - it's still not possible to write your own smart pointer which would work with dyn traits, including coercions - macros have artificial limits when called from other macros (which is exactly why you can't do a PoC and put it into bigger project) - borrow-checker is very dumb and even simple things like x(&mut self.a, &mut self.b) don't work and require you to do destructures, inners and other mumbo-jumbos. - often, solution is to write macro, I feel like I'm writing macros all the time lately (and macros are entirely different language BTW)
- carols10cents 6y ago> My interest was not (and still isn’t) in operating systems, programming language design, or safety (with respect to memory, formal verfiability, modeling as types, etc.). Absolutely if you're not interested in the safety Rust brings, a lot of the design isn't going to feel worth it. The safety is my favorite part of Rust, though :) Zig is quite explicitly not a safe language.