3 ms·
This is the perfect example. For instance, I don't need Traits or Async or generics for embedded. I can pass a struct into functions as I've been doing my whole
by scoutt 5y ago
This is the perfect example. For instance, I don't need Traits or Async or generics for embedded. I can pass a struct into functions as I've been doing my whole life. Async is not supported (but I can generate my own interrupt-driven state machines).
In C I don't have an "abstract and async traits interface". I wouldn't even imagine why I need one "in the language". Do I need one? Does a bluetooth stack or kernel driver need one? I can make a struct with pointers to functions, and there you have an "interface", done with the basic elements the language provides.
I undertand that these features are a "nice-to-have plus", but what all these updates actually generate in a system/embedded developer like me is fatigue, and not only at cognitive level but also "I've changed compiler version and I have to revalidate all over again". Why? some purple-haired 25 years old across the ocean needed an "abstract and async traits interface". Now the bread on my family table depends on what they've changed.
I imagine that perhaps Rust tries to make everyone happy, but here are my two cents from my PoV.
- pornel 5y agoRust doesn't cater to small (short) programs, which some embedded programs may be. It's been originally envisioned for making a modern browser engine, which is almost an entire OS these days, so many of its features start making sense only when you need to work with programs big enough that you can't fit them all in your head at once, and have to maintain for a long time, cooperate with other developers, deal with 3rd party code, etc. Features that are layers of abstraction are hard to explain on their own, so I don't think I can justify them in just a HN comment. I remember when I have been reading about Encapsulation in OOP for the first time, and I completely didn't get it — why would I hide struct fields from myself!? I know they're there! Rust's traits are such an abstract feature that doesn't directly translate to things that CPU does, but it's a building block for a few higher-level language features: • Traits can be used like a Base Class or Interface in OOP, so for example you can make structs "Serializable" and have a data format writer know how to dump them to disk (assuming you need something smarter than fwrite(&struct) due to pointers or portability). Instead of having a one-off function for serializing your particular state, you can make each struct describe itself and "just work" when combined with a JSON/Msgpack/binary serializer. Rust makes good use of this for https://serde.rs https://serde.rs that is pretty nice to use and surprisingly efficient. • Traits can be used with static dispatch and combined with other types at compile time ("Generics" in Rust jargon). It makes them replace many things you'd do with horrible C macro hacks, and you get usually nicer syntax and type checking. For example, you can use containers (hash maps, btree maps) without void* cost and casts, and without include macro hacks. Linear search and linked lists can get you far, but when you need a better container, it's really nice to have best-in-class one available, and as fast as if you handcrafted it yourself. • If you prefer the struct-of-function-pointers approach, Traits can do dynamic dispatch that too ("dyn Trait" in Rust jargon). • Traits are nice to coordinate APIs across libraries. For example in C if you want to stream a file from a network, decompress it on the fly, and decode JSON, you need to write a bunch of glue code, because every library handles buffers slightly differently. In Rust there's io::Read trait, and people agreed to use it, so `json_decode(ungzip(request(addr)))` works, and it's streaming (functions here don't return malloced char*, they return sockets/handles that implement a trait that describes how to get more bytes from them). Sort of like a file descriptor, but using general-purpose feature, and it can also be integrated at compile time, so everything is as inlined and optimized as bespoke code.
- scoutt 5y agoEmbedded doesn't mean "small programs that fit in a head". Some projects I maintain are tens of thousands LoC. Modern microcontrollers are being built with 1 or 2 MB of flash (where a full exFAT FS plus an SDXC driver fits in around 30KB, to give you an idea). Look, I understand and really appreciate your answer, but I keep thinking these are niceties, and if I were to implement (I don't know) SSL from scratch on desktop/server or a browser as you said, I'll probably consider Rust, because of these niceties. For the rest, low-level system/kernel/embedded/BSP, I'd prefer what I called RISR in a parent comment. That is, Rust stripped from niceties (traits, async, generics), keeping the language to a minimum with safety as primary objective; and something that hardly updates. Perhaps it could born as an agreement between embedded/kernel Rust developers, to not got to certain places through certain paths. Because the more you add, the more complex it becomes, and it will always be difficult to keep everybody happy, test, validate, learn, support and interop with/within different architectures, avoid corner cases, etc. Just for the sake of doing "one-liners" like `json_decode(ungzip(request(addr)))`, which I will never do, because I don't have network access, nor persistent media, nor memory for gzipping, or memory to keep and navigate a JSON structure. It's like Rust tried to first "convince" javascript/go/haskell programmers, and system developers later, when it should have been the other way around. Because it's super cool to have automatic panics on unwrap() and failed allocations, but the moment Rust has been put to do one of the things it's supposed to be doing (kernel drivers?), the panic thing didn't worked anymore, in April 2021. I'm sure you know what I mean.
- pornel 5y agoI hate that Rust has fallen for the "Linux can't OOM" fallacy too. Fortunately the mistake is only in its stdlib. Rust-the-language doesn't allocate, and kernel/embedded projects turn stdlib off. Panic doesn't have to unwind. It can be a trap instruction or `while(true);`, but in case of an unrecoverable bug it has to stop execution somehow. Rust's initial goal was specifically Servo, a browser engine. It was based on a bunch 80s/90s programming languages, and Ocaml which is a bit Haskell-y, but JS/Go specifically and explicitly were a non-goal[1]. [1]: http://venge.net/graydon/talks/intro-talk-2.pdf http://venge.net/graydon/talks/intro-talk-2.pdf Direct C replacement for embedded programming was Rust's goal early on too. Async is for the JS/Go crowd, but it's a late addition to the language, and it's also designed to work without any runtime at all. It can be used in embedded and kernel code, and for non-network things too. At its core it's representing function's stack as a struct. Fancier things are just libraries built from it. If you want compile-time safety and minimal runtime cost, it's unfortunately impossible to do without a more-complex-than-C type system. There really isn't much you can remove from Rust without either creating loopholes in the safety, or losing features that make its safety practical to use. For example, you can't have a safe set that stores void*. You need HashSet<T>, so that the compiler can track of what type it stores (because T could be a struct with a pointer to the stack, etc.). But then you need to hash and compare the arbitrary T type, and that needs some abstraction over types, which traits provide. "Small Safer C" has been tried at least in Checked-C, Zig, and Cyclone, and from these it's clear the problem has inherent "choose two" trade-off of {runtime type checks, complex type system, safety loopholes}. Programming on a type level is definitely is a very different mindset from what C does, but dismissing it as merely unnecessarily complex is a shallow and inaccurate view IMHO. There is an inherent complexity in writing high-performance safe software. The complexity has to go somewhere. If the language doesn't have tools to with it, then you have to deal with it in the software yourself (like the level of vigilance and tooling that's required to have safe C). Rust is a bigger language, but its toolbox is for reduction of complexity in the software you write. The things you dismiss are not optional nice-to-haves, they are part of the solution, and the pay-off for not having to deal with the complexity in the software, because it's already dealt with in the language.