Ask HN: Why is there not a memory-safe C?
I'm going through a lot of "Why Rust?" reading material, and starting to wonder why is there no memory safe subset of C?
While going through Rust tutorials, I'm realizing how much more sophisticated Rust gets than C in terms of the language. So many syntactical things to remember.
Could there be a less powerful subset of C that we can use in situations where we're not building operating systems or distributed systems?
91 comments
[ 3.4 ms ] story [ 144 ms ] threadC++ applications are some of the most vastly complicated programs in existence so I don't follow the argument that they break on projects larger than a breadbox.
If you never do anything of those your program is anecdotally memory safe. Turn all things into libraries (strings, basic data structures, etc) and verify those closely and your C is now what many people call "memory safe". Add valgrind on that and you're pretty safe.
Good patterns and good tooling can combat any issues you have with C if you need to use C. Otherwise just use one of the "!C" languages out there
Other good things:
At the same time your C becomes a very special variant of C. When you use the constructs from the SEI CERT C Coding Standard, a lot of things becomes much more complicated than usual. You are more careful, but at the same time spend more time writing code. And even if you do all that, of course you can never be sure about third-party libraries.
At some point I just gave up and switched to D, with no regrets.
Another thing is, even the basic constructs of the language (for simpler needs like the ones I described in original question)are deemed unsafe - like string processing. Someone told me in passing that I should use Redis's SDS[1] if I want safe strings in C. Do not use char * and manipulate them. I admit that I haven't spent my time understanding why that is the case, or have validated whether it is mitigated magically by using SDS. The point is the need for doing such things for basic elements of a language - strings.
What I wonder is why there isn't a language that is C, with safe defaults, easy-to use syntax, as fast as C, and consequentially less powerful. I was hoping Rust was it, until I started to feel scared just looking at the code. At the risk of prejudice, I feel it is more of a glorious C++ than a language I could use for simple general purpose programs(that are still as fast as C).
[1] - https://github.com/antirez/sds
EDIT : Grammar and reference
Because part of the speed of C is due to the fact that it doesn't do much for you. The moment you want the language to guarantee your memory safety, you'll have to pay either via speed penalty or via added complexity. At least this has been the case with the languages I know. Maybe someday something else will emerge which proves me wrong.
I recommend you stick with it. Your understanding of it will improve with practise.
Memory safety isn't free -- there's either a language complexity (borrow checking), a runtime complexity (garbage), or a programming complexity (avoiding making mistakes). While there's no one correct option, there is in my opinion a wrong one. Human minds are fallible.
The misconception that people have is that C has data structures provided. C is a language of only primatives. There are pointers and there are different width numeric types and structures comprised of those numeric/pointer types. Nothing else is provided by default. No strings, no lists, nothing.
For many situations operating at this level of abstraction is ok. Many programs do not ever need a string type. Unfortunately in situations where strings were needed initially the people at Bell Labs decided to use a pointer to a character and come up with convention for that layout of memory.
This means that everything must understand those conventions! This is a dangerous and leaky abstraction. As the more times you implement something the more times you can mess it up.
SDS provides a single implementatuon for string manipulation by hiding data within the pointer of the string and type aliasing the original char*. It is abstracted from your initial stucture.
If you do this with everything you write, using the type system to your advantage, than most of your issues with C will not be an issue.
I know the guy behind redis has many videos on his software. From what I've heard he is a modern day software engineering genius and I'm assuming he will have good examples of how to implement this non-leaky abstraction in his software walkthroughs.
Have you tried to figure how the safe subset of C you're suggesting would look in Rust? If you're doing something simple, it shouldn't be that bad.
Do you have a specific use-case in mind? I'm curious what your requirements are in terms of performance and simplicity, that languages like Go, Java or even Haskell do not meet. Is it a platform thing?
If you allocate 2x the memory you need then there's no risk of overflows. It's riskier but it makes all of the code dead simple, extremely fast, and is a good middleground between embedded and normal.
You do get large binaries though...
http://c2lang.org/site/introduction/changes_from_c/
Your question still stands. However, it's not too hard to be "memory safe" in modern c++.
I'm not entirely convinced that is the case, but surely you will agree that it's much easier to audit Rust for memory-safety than C++.
Offcourse, one can argue that the 3rd party Rust libraries can use a lot of "unsafe" code which we as consumer may not know about. But the situation is still better than C++.
Well, while my answer is not meant to be serious, Go started as "C with some improvements", and the three person team who first wrote it had among them Ken Thompson, one of the first users of C. Because it is garbage collected, it mostly abstracts away the memory allocation from the programmer. There's penalty in performance, though. It's a trade-off that allows the programmer to not think much about it. Go favors simplicity over speed while Rust tries to have speed as a main goal, and sometimes the tradeoff is more complexity.
When you refer to Go plug-ins API, do you mean this[1]? Because I wrote a fair amount of Go and wasn't even aware of this package. And while I don't know whether it is terrible or not, I'm pretty sure this package in particular is not relevant for most Go applications. So maybe you meant something else?
I didn't say Rust users want a GC language to replace C, I don't know where this is coming from.
[1] https://golang.org/pkg/plugin/
Regarding plugins, yes that is the package I am referring to. The idea of dynamic loading is completely ignored in the Go programming language but it is used in many, many other languages (i.e. Java, Python, Lua). Go's support for this feature is lacking and should be fixed for it to interact with other languages.
Go has garbage collector and that is not zero cost.
Rust has zero cost abstractions, so Rust is sort-of "memory safe C".
However, Rust is notoriously hard to learn, read and write in. So that sort of shows you that desugning low-level, zero-cost language is HARD
"Zero-cost" has always been about runtime cost. Other costs are not part of the slogan. Everything always has a cost!
Only sometimes. For complex enough problems I think mental burden of writing and maintaining software in less complex language will become more burdensome than the intrinsic burden that comes with more complex language.
I've seen this argument a number of times before, but always discarded it as something that only people not familiar with post-1.0 syntax could bring (cause all those ~@[Omg]s really were hard to read). Do people really still struggle with its syntax, and if so, why?
Coincidentally I personally really seem to struggle with reading sources in Go, although I'm yet to figure out why exactly it is the case and why I'm not as slow when reading sources in e.g. Perl.
The best safe C derivate is D, but there are also some minor offsprings, like safe-C or mscc or the popular cfi option, the Intel bounds-checking library or safeclib. Most C compilers let you track the buffer bounds nowadays, but rarely someone uses it.
I'd argue it's called D. Go and C are two quite different languages. Whereas if you've been coding in C you can easily switch to D and benefit from safer constructs instantly, almost without feeling a difference.
There is Safe-C[0], but it's not directly compilable as C and vice versa, and there are libraries like Cello[1] which provide higher level mechanisms like garbage collection to C.
[0] http://www.safe-c.org/ [1] http://libcello.org/
If I can nitpick your last sentence for a second, that's the wrong way to think about it. The parts of C that are dangerous aren't the "powerful" parts; in particular, whether your can write a distributed system is beside the point. The dangerous parts of C include basics like arrays that you need for every program. Fixing them won't affect your ability to do almost anything.
https://en.wikipedia.org/wiki/Cyclone_(programming_language)
https://cyclone.thelanguage.org/
http://wiki.c2.com/?CycloneLanguage
Edit: Wow now I absolutely cannot find a darn thing about that distro. Used to be one last page detailing the project left. It was quite interesting, wish they hadn't shut down the homepage at least.
Edit 2: Looks like it was called AuroraUX here's a bit more info on it:
https://www.openhub.net/p/AuroraUX https://www.phoronix.com/scan.php?page=news_item&px=MTIyMTI
Even found a blog post from someone who was apparently developing AuroraUX:
http://ultravioletos.blogspot.com/2009/09/opensolaris-distro...
Gotta love mysterious little Operating Systems. Looks like they were being maintained by Blastwave which was some sort of OpenSolaris focused company.
Found a Spanish wikipedia entry still alive which mentions support for Cyclone:
https://es.wikipedia.org/wiki/AuroraUX
(Google Translate if you don't know Spanish :)
Edit:
I think anatoly found it:
https://news.ycombinator.com/item?id=17143218
Edit 2: Looks like "ohloh" was the project descriptiong page I last saw became openhub:
https://www.openhub.net/p/AuroraUX
https://cyclone.thelanguage.org/
Looks like Rust took some inspiration from Cyclone.
It's said to have been based on DragonFly BSD, with new components written in Ada (!), but also supporting Cyclone.
Functional safety is a specialised branch of engineering all of its own.
That said, IME new C projects (not maintaining old monsters, for which a new language or library will not help) are mainly small components or libraries where you need to be close to hardware. In those cases, it might be better to reap benefits of being simple, clean and close to problem domain (hardware) and mitigate dangers by keeping the C code small, modular and clean.
Not criticizing, just asking an honest counter-question: what for (use cases)?
However, they are not part of the C ecosystem, they don't compile into C and don't provide anything like musl gcc wrapper to be able to incrementally add memory safety in certain situations.
[1] https://www.cs.rutgers.edu/~santosh.nagarakatte/softbound/
[2] http://sva.cs.illinois.edu/index.html
[3] http://chrisseaton.com/plas15/safec.pdf
[4] bounds checking feature in tcc https://bellard.org/tcc/ http://download.savannah.nongnu.org/releases/tinycc/
Au contraire!
Can I bring [1] to your attention? The paper argues that our intuition about memory safety means allowing for local reasoning about state.
[1] https://arxiv.org/abs/1705.07354
But if you really do want it, C offers the opportunity to replace malloc() with whatever you like... or to not malloc at all...
You can use local variables, but no pointer types - no variables with pointer types, no functions that return a pointer type, and don't use the * or & operations which dereference or create a pointer.
Initialize everything - there are compiler warnings to help with that.
Admittedly, this subset of C isn't particularly useful. No dynamic memory, no strings or file i/o since those functions from the standard library take pointers. You could definitely do some arithmetic and return the value from main() in a completely memory-safe manner using this subset, though.
Anyone trying to improve C has to face the dilemma that being "C but better" doesn't seem to offer enough for C users to switch (you get all the headache of a "new" language for one or two features), and languages adding enough improvements to justify cost of switching have to evolve to the point of no longer looking like C.