Hacker News new | ask | show | jobs
by merlincorey 2535 days ago
> No, software written in C is and will continue to be replaced by software written in modern memory safe languages; the trend is strong and growing, to the point where we tend to look askance at (1) new software written in C and (2) popular software written in C that doesn't need to be written in C.

When will we get an ANSI standard for one of these new fangled languages?

Go is captive to Google, and Rust is more akin to C++ than C -- neither have an actual standard with competing compilers.

Until that changes, I think plenty of new software will be written in C, especially freestanding and embedded software.

3 comments

Rust will displace C in embedded applications, and Mozilla will gradually POC out its displacement in browser software as well. Swift is and will continue to displace C/ObjC in client application software on Apple platforms. Java is displacing C on other mobile platforms. Go is already displacing C in serverside software. And, of course, languages like Python have overwhelmingly replaced most of what C used to be used for in conventional CRUD applications.

C is on the way out. It's not gone, but it's going. There will not be a revitalization of the language. It'll be good to know, because some things (OS kernels and device drivers) will continue to be written in C for the foreseeable future. But that's a very small niche relative to the industry as a whole.

Nobody cares, and nobody should care, about ANSI standards.

Why would you use Rust on embedded hardware? Seems like C is a better match for embedded considering it has no borrow checker to complicate things, and the borrow checker is less useful when you do not have a heap.
The borrow checker isn't nearly as big a deal as you make it out to be from a complexity perspective. You have to take some time to learn the paradigm but when you do it's easy. The challenge is people assume it writes like C because it looks like C, and are frustrated when it doesn't. After a couple of months, you'll realize the borrow checker is your friend, you just need to feed it stuff in a way it understands.

The borrow checker is great for all sorts of things, like ensuring that you only have a single mutable reference or an arbitrary number of immutable references to objects (to prevent corruption) and for the pseudo-threaded model interrupts entail. Memory safety matters in embedded systems, too. You can encode state cleanly with ADT enums [2] where the language can statically detect invalid state transitions.

It's also a much more expressive language, providing you with great abstractions with no added cost. The ergonomics are pretty great compared to dealing with C once your peripherals / register accesses are wrapped, as often are already. For instance, check out the stm32 package [1].

Being able to rely on the compiler more means you get correct code faster, and debugging on embedded systems can be absolute hell. Anything that helps me avoid it is a massive win in my books.

[1] https://github.com/stm32-rs/stm32-rs

[2] https://hoverbear.org/2016/10/12/rust-state-machine-pattern/

First of all, the borrow checker is useful any time you have data that's potentially being accessed from multiple "places" in the code. At a bare minimum, it forces you to mark such data with type constructors such as Cell<> that make it clear where error-prone shared state can occur. Secondly, Rust is an appealing alternative to C++ as a "better C" language. C++ used to be quite acceptable as such, but recent C++ standards come with a huge amount of incidental complexity, so moving to something cleaner can make sense at least for "greenfield" projects.
Because Rust is memory-safe and C isn't. I agree that C is easier to write on embedded platforms and kind of enjoy writing that kind of code myself. But shipping a product requires more than just writing the code; it also entails all the verification work you have to do after the code is working to keep the product in the market, and the verification burdens on embedded software are only going to increase as people generally get more clueful about security. Memory-safe languages will (if they aren't already) be a less expensive way to get a product to market.
Protip: look at the tooling requirements for safety critical software (aviation, automotive, industrial, medical...). Nobody is going to allow you to use tools that are not certified for these use cases. Certification of compilers, static checkers etc. Is a huge undertaking that only a few commercial vendors perform on their offerings. Open source is out because nobody even attempts yo run them through that process. What you are left with is highly priced toolchains for Ada, C or C++. The cheapest offerings I know start at high four figures per developer. Validating a new toolchain would likely be to the tune of high 6 up to 7 figures. Don't hold your breath.
Interesting. This is indeed a massive undertaking as it implicitly covers a lot of LLVM as well. It might take a couple of years until a core language is certified.
I've done professional validation work in automotive, industrial (and utilities), and medical (I'm a low-level C vulnerability researcher and have been since 1995), and believe you are simply wrong.

There are, no doubt, a number of niche systems that require specific toolchains. There are, in our fallen world, systems that require Ada or even particular variants of C. If you want to tell me that aviation flight control systems are such a niche, I will believe you --- I've never had to assess one.

But it is not the case that industrial computing or medical device software are locked into memory-unsafe languages due to industry-wide certificational requirements; in fact, that's something I know not to be true from specific experience. And virtually all of the embedded systems I've had to assess over the years would have benefited, commercially, from a memory-safe implementation language.

You surely must be aware of IEC 61508 and ISO 26262 if you work in that field. These govern automotive software and industrial automation (the later has no domain specific standard). It is easily verified that these standards are adhered to in practice. I worked on IEC 61508 compliant systems. And all these standards require that the tools used for compiling, verifying and testing the software is tested and certified to be correct. This certification is performed by a Notified Body. This is mandated by law for medical systems and the standard procedure for the rest.

This is a major barrier to entry for new programming languages in these markets. Note that I am not saying that improved memory safety wouldn't be useful in embedded software. But the market is so conservative in parts that real uptake is at least a decade or two away.

You would be very disappointed if you knew how medical software is made. For instance most of it can crash without issue (and in practice, it does a lot!): all you need is to assess the risks that could result from a crash and mitigate it (for instance: the device should be designed in a way that stops doing anything as soon as its software crashes).

I've been a contractor for a medical devices company for one year, and the process where really lightyears away from what you are describing. Nothing even forces you to write and run unit tests

Yes, that's scary.

Sounds like one of my previous employers.

The next company I worked at was industry, not medical, and this one did it right. So while there's black sheep out there, not all of them are.

Side note: whether a device can fall back to becoming inert on a failure depends on the type of device and the specific risks involved with that failure mode. It may be that stopping to do anything is the wrong thing to do, e.g. in a blood pump.

All those standards have loopholes that allow you to use tools without certification provided you do the due diligence necessary. I know because I used to develop safety critical software using tools whose only merit was that they were in use before the standards required certification. Basically you run a standardized testsuite and write a document. You don't even have to pass all the tests, you just have to document why test failures don't affect the safety of your product.

Since many people are interested in using Rust for such applications, there are efforts underway to stabilize the compiler and do the necessary paperwork so that not every company needs to do it themselves.

These loopholes are slowly but surely being removed. DO-178 for aviation is the first standard to do that. I am certain that the others will follow.

What test suite are you talking about? I am really curious because this would completely upset the whole industry if what you ssid was true for the toolchain.

I fail to see how a borrow checker can even be remotely useful when there is only one kind of lifetime for everything: until the power to the device is shut off. The only memory-related issues you can have then are out of bounds accesses and using uninitialized or stale data.
Session types. Managing hardware state via the type itself, such that it is not possible to create code that does something with (e.g. an IO pin) unless it has first been properly set up, and it's guaranteed at compile time with no runtime checks.

Guarding against mutable aliasing with respect to hardware state could be enormously valuable.

How is a solution using a session type different from what you get with a C++ object with a constructor?

I don't quite understand what you are saying in your second paragraph.

What are you talking about? You can certainly have memory related errors on an embedded system.... and the lifetime of memory can often be shorter than the full on cycle of the device.... you can use memory within a function that is not needed outside of it, etc.

Borrow checking is unrelated to whether you are on an embedded device or not.

If you need reliability and long uptime in an embedded device, dynamic memory management is shunned because it is detrimental to both. The probability of fatal memory fragmentation from bad allocation patterns is too high.
This is a misunderstanding of how Rust's borrow check works. It is necessary for avoiding numerous memory safety issues, not just use after free. For example:

    let mut x: Result<i32,f32> = Ok(1);
    let y = x.as_ref().unwrap();
    x = Err(1.0);
    println!("{}", y); // unsafe cast of float to int
This issue was first described to my knowledge by Dan Grossman in "Existential Types for Imperative Languages". The context was trying to make unions work in a safe dialect of C (Cyclone).
I don't know why we're arguing about the utility of the borrow checker in static memory systems since the borrow checker is obviously not the only difference between Rust and C in that setting; we talk about Rust in embedded systems because it doesn't have a heavyweight runtime that requires (in practice) garbage collection to function, and so is suitable for environments where Java and Go aren't tenable.
We are not arguing these points because they were not brought up and do not really distinguish rust from other system languages.
Go has a battle-tested specification and multiple implementations.
There's llgo [1] too, which in my opinion is how Go should have been developed in the first place, instead of on the decrepit plan 9 toolchain, but hey, at least we have it haha.

[1] https://github.com/llvm-mirror/llgo

> Rust is more akin to C++ than C -- neither have an actual standard with competing compilers.

C++ doesn't have an actual standard? Are you sure about that?

Rust and Go is what I was referring to in that case -- sorry for the confusion.

Yes both C and C++ have standards, and I consider that to be a good thing, though apparently tptacek says I shouldn't care.

Ah, I see. My error.