Comment by QuiEgo

4 days ago

Thank you for the clarification, that's exactly what I was trying to say :).

Perhaps another way to phrase this: in Rust, you spend more time telling the compiler how your code is expected to work (making the borrow checker happy, adding sync traits on objects you "know" are thread safe because of how you use them or assurances the underlying hardware provides, etc etc etc). In return, the compiler does a lot of work to make sure the code will actually work how you think it's going to work.

A good example is a simple producer-consumer problem. Make a ring buffer. Push the current sys clk tick count register to the ring buffer every time there's a rising edge interrupt on a GPIO (e.x. hook up a button or something to it). Poll the ring buffer in another thread and as soon as there is a timestamp in the buffer, pop it and log it to a UART.

Compare writing this in C vs Rust. In the text book implementation of a producer-consumer ring buffer, you don't need locks.

In C, this is a problem I'd expect a senior-level candidate to be able to knock out in a 60 minute interview.

(aside: I'd never actually ask a question like this in an interview, because it heavily favors people who just happen to be familiar with the algorithm, which doesn't give me enough signal about the candidate. This is borderline like asking someone to implement a sort algorithm in an interview - it just tells you they know how to google or memorize things. /aside).

(aside 2: If I did ask this, I'd be more interested how they design the thing - I'd be looking for questions like "how many events per second? How should I size the ring buffer? Wait, you want me to hook up an IRQ to a button? Is there a risk of interrupt storm from bounce or someone being malicious? Do you want me to add a cooldown timer between events? If we overflow the ring buffer, what should the behavior be? How fast can the UART go on this system - can it even keep up with the input?" - I'd be far more interested in that conversation than actually seeing them write code for this. /aside2).

In Rust, it's a bit more tricky. You'll need to give the compiler hints (in the form of Sync traits that are no-ops) to tell it you know what you're doing is thread safe. It's not rocket science, but the syntax is kind of weird and it will take some putzing around or aid from your favorite AI to get it right.

All of Rust ends up like this - you must be more verbose telling the compiler your intent. In exchange, it verifies the code matches your intent. So the up front cost is higher.

I suspect a lot of people will just pull a ring buffer off crates.io instead of figuring out the right incantations to make the compiler happy.

I don't spend significant time making the borrow checker happy, because I learned how to write C++ that works.

  • ¯\_(ツ)_/¯ I'd like to lean into that "YMMV" in my post, I'm coming from low level C that interacts with hardware, can't really speak to higher-level C++.

    Some things in Rust just don't translate to the way you'd do them in C - e.x. using different types to say if a GPIO pin is input or output adds a ton of boiler plate, but lets the compiler assure you don't mistakenly try and use a pin configured as an input for output.

    In general, the whole zero sized types paradigm in Rust leads to way more lines of code to accomplish the same thing (it all ends up compiled out in the end though).

    For embedded, I'll stand by what I said: it takes longer to write idiomatic Rust but you are more likely to get functionally correct code in the end.

    • ZSTs are a choice, if you're not benefiting from them in development time or correctness, why are you making that choice?

      Rust isn't just C++ with static analysis. The safety is only about 1/3 of what makes me enjoy working with it. Even if I just consider the effort taken before I present something to a compiler, the trait system means I don't spend anywhere near as much time writing copy/copy assignment/move/move assignment constructors.

      Some of that 2/3 will be me making choices like ZSTs, but I believe that's a result of it being necessary to grok the whole language and style and the idioms. If I just try to write C in Rust, it will be bad Rust and I'll have a bad time - but the same goes for just writing C++ as C with classes. I deliberately didn't say 'C/C++' in my ancestor post, because I think it's terminology that undermines claims of authority.

      I do happen to have embedded experience, but avoid drawing conclusions from it because it is so different to more 'standard' systems development. If you can forgive a tangent: I avoid the whole domain now, because it feels like a ghetto. Standards are poor, tools are poor, pay is seriously poor. If you want to write embedded software, you probably have to work for a hardware company with hardware company's engineering culture and revenue model. That's a pretty crap place to write software in the west. I can't comment on comparing what like to do embedded development in Rust. Frankly, I hope I never can.

      1 reply →

Remind me again, how the”sufficiently senior dev” approach is panning out for places that are swapping off C/C++?

Because from here, it seems like they’re having a great time, which is extremely at-odds with your comment.

  • My experience in low-level stuff has been a mirror of the Linux kernel trying to take up Rust. Some people love it and fight hard for it, some hate it and fight hard against it, but most just see it as another tool in the toolbox and don't have strong feelings.