They give an example of a tetris clone in Zig. This is how they include GLFW with Zig: https://github.com/andrewrk/tetris/blob/master/src/c.zig and all other locations in the project just import this c.zig file.
In contrast in Rust, I think you have to actually declare all the functions you want to use from a C library. For example: https://github.com/PistonDevelopers/glfw-rs/blob/master/src/....
I kid, it's really impressive work.
Did you mean two? Or did you come to new conclusions after that experience? Or did I misunderstand that entirely? ;)
I'm just saying a vulnerability in C is just going to be an out of bounds exception in another language. both cases the code is not correct, one is just safer than the other when it comes to exploits.
Keep making more languages, we can't know for sure if we don't try.
So, we already know we can knock out extra classes of errors with such languages. It was proven in theory and in the field. Using or improving them is just good engineering. We can also make more languages in trial-and-error discovery process to see if we find more benefits. Exceeding C's benefits, though, is already empirically proven to be worthwhile whether it's a Myrddin, a SPARK, or an ATS.
Whatever the case, as far as I am aware, sooner or later Nim will prevent nil values by default.
Definitely a memory safety issue. Definitely worth preventing if possible. If not preventable, definitely worth handling better than mere segfaults.
My theory is that people have converted "memory unsafety can cause segfaults" into "segfaults are memory unsafe." Segfaults are actually the desired outcome, and initializing a pointer with null, instead of leaving it uninitialized is how that outcome is achieved.
And it's not better to "explicitly" abort, because you get zero benefit from that. And because testing for null would be slow. (This is why JVM implementations often let it segfault.)
I think we'll have to agree to disagree here, though. Segfaults are memory safety problems, and unwrap is significantly different because it's not a memory safety problem.
I'd say that language L is memory-safe, then a language M which is like L, except that some programs terminate earlier, should also be memory-safe. We'll have to "agree to disagree" about that.
I'd say a compiler's choice of behavior-maintaining implementation technique should not determine whether a language is memory-safe. We'll have to "agree to disagree" about that.
I'd say a definition of memory safety should correspond to some mutually relevant notions of being safe (with consequences around security and ease of debuggability), and that it shouldn't include some kinds of unrelated notions about termination safety while omitting other kinds of termination safety. We'll have to "agree to disagree" about that.
> I didn't say that all segfaults are always security problems, just that the often are.
My mistake, I assumed you were trying to make a point that had some bearing on the discussion.
It's tradition that failures of a language's safety system to handle memory are "memory, safety issues." However, that they can be used for code injections in some scenarios is even more reason to think of them that way and make our languages prevent them where possible. Quick example:
https://tk-blog.blogspot.com/2009/01/exploitable-userland-nu...
Note that Nim uses C as an intermediate language for compilation, so unchecked dereferences of possibly-null pointers are not memory safe.
I don't really know anything about Oberon so can't speak to that.
P.S. Personally, all my hobby projects are in lisp. I write applications though, not system stuff.
It was a language and OS, following the same spirit of Xerox PARC OSes.
I loved the complete OS experience, but nowadays Swift, .NET Native,Rust among others would be better options.
In terms of ABI stability, Rust has none.
In terms of API stability, they seem willing to drop features within a major release cycle or two.
In terms of language stability, they seem to only guarantee stability within one major release (e.g. 1.x).
Rewriting for the sake of language updates is a huge resource drain when you're talking about code bases with lifetimes that are likely to be longer than their authors'.
For reference, just about every major shipping OS contains significant LoC dating back to the 70s or 80s.
I don't think any feature/api that was at one point marked as stable post 1.0 (when they started making any guarantees) has ever been removed.
> In terms of language stability, they seem to only guarantee stability within one major release (e.g. 1.x).
While it's true that 2.x has the rights to break everything, this is incredibly dishonest to state in a vacuum, because it's a well established point that 2.x should never exist. The devs want all code to work forever, and only a catastrophe would push them to pull the lever. They have of course reserved the right to require fixes in special cases -- e.g. fixing a bug in the type checker might break some code, and that's acceptable. Although even then they'll try to phase in the fix over a few releases. Every release is tested against the entire crates.io ecosystem to try to catch these regressions.
You are absolutely correct on ABI stability, though.
I suspect that even if 2.0 happened, the only apis that would be removed would be ones that we're reasonably sure nobody uses (via crater), and even then there would be debate.
And like Gankro said, 2.0 is a thing that's not supposed to happen in the first place.
No! That would be a basic violation of the compatibility promise that we've upheld since 1.0.
Are you going to criticize C for marking gets() as deprecated?
But really, it's not about rules-lawyering over technicalities about what our policies are: the underlying attitude we share towards new Rust versions is that it should be trivial to upgrade. And we collect data to make sure that we're on target there: part of it is the train-based release strategy, part of it is stuff like testing everything on crates.io, part of it is things like the community survey, where most people said their code has never broken, and if it did, it was extremely easy to fix.
An example of a thing considered "trivial to upgrade" over is when the stdlib adds a new method to a type. Libraries may already have implemented a method of the same name on that type via a trait. In this case, folks upgrading would have to explicitly specify which method they're talking about.
It's these kinds of things that are not counted as breaking changes, because the alternative is freezing the stdlib forever. Other languages have the similar problems.
New releases are tested on the entire ecosystem to ensure that this rarely happens, if ever.
This is completely false. Do you have an example?
Besides, this is completely off topic in a Zig thread. From the FAQ: "Zig is not afraid to roll the major version number of the language if it improves simplicity, fixes poor design decisions, or adds a new feature which compromises backward compatibility."
Review the "Breaking Changes" of release notes, and the number of discussions regarding removing API after a short (in the view of someone who works on said "systems code") period of deprecation.
> Besides, this is completely off topic in a Zig thread. From the FAQ: "Zig is not afraid to roll the major version number of the language if it improves simplicity, fixes poor design decisions, or adds a new feature which compromises backward compatibility."
And thus, it won't be used as a systems language.
Technically we can remove those at any time (happens in librustc all the time), the deprecation period is to help and motivate migration of nightly users, nobody else can touch those APIs.
This is another instance where our being up front about any theoretical breakage leads to misunderstandings. Every new release of GCC and Clang has breaking changes in the Rust sense too--they just don't call them "breaking changes" (and we stopped doing that too, because of comments like yours). For example, I had some code break when upgrading GCC the other day due to newer versions becoming stricter about copy constructor semantics in C++11. In Rust we (used to) call this type of thing a "breaking change" out of an abundance of caution--even though it was nothing more than the compiler getting stricter about code that never should have compiled in the first place. Unfortunately being up front about it led to confusions and comments like yours.
This makes combining Rust into already existing C code a hassle, as it is generally worse then even just trying to use C and C++ together which is already fairly annoying and error-prone. This is compounded by Rust's safety goals which generally require a different design to a problem then the "C-style" approach would be - IE. You're probably not going to get good Rust code by simply replacing a .c file with a Rust file, because the entire time you're going to have to make use of the unsafe C functions that your program includes.
Perhaps the bottom line is that (for good reason) Rust is much more then just an improved C. And because of that, combining C and Rust is always going to feel like combining two separate systems together with a compatibility layer in-between - because that's really what it is - and that's just not very attractive.
I know little about Zig (I'm reading about it for the first time now) and don't at all predict it to replace C. That said, I would love a language which has a focus on maintaining very good compatibility with C while fixing the various pieces of its design that are fixable within the bounds of the language. For example, I noticed it mentioned in the README that in Zig that pointers are nonnull by default, and can be made nullable by adding the 'maybe' attribute. This is a feature I would love in C, but really isn't there (gcc has a nonnull attribute, but it is really essentially useless and nothing like you'd want). Essentially, I would like a language where it feels like writing code for the same system, but in a different (better) syntax - where I can drop it into a project and it works together with the system like any other .c file (Though with a different compiler, obviously), but the code inside is much better then what you can do in standard C. I think that such a language could retain the reasons why people (like me) still like C, while fixing a lot of the uglier sides to the language that everybody is aware of but aren't going to get fixed any time soon. And perhaps most importantly, I think such a language is definitely possible (Though it may not be able to employ all the features you may want) - but I don't have the time nor probably the skills to really do it well besides list off the things it should fix and shouldn't fix.
As for starting a project though, if you ignore difficulty to learn then I would agree with you: There's probably little reason not to just go for Rust if you're already willing to learn a new language. For most projects the issues I outlined above don't really matter assuming the entire project is written in Rust - besides the want for a language closer to C in design. That said, I readily concede that I have no idea if Rust's borrow-checking semantics could ever work without the extra features they added that take the design away from being 'C-like' - and considering that's one of the definitive features of Rust, is easily worth losing the 'C-like' detail if it is necessary.
But when considering a project like the Linux Kernel, the GNU coreutils, git, GTK, and other various large projects where conversion to Rust is probably impossible without a complete rewrite, being able to use a 'better' C while still retaining the aspects that people like would probably be a nice step in the right direction. The chances of it happening are nil, but it would still be a step in the right direction.
> ust uses the C++ ABI, not C.
Rust uses its own ABI, not any of the various C++ ones.Our strategy for interop with C is two-fold: first, a very thin, direct wrapper. These are the various *-sys packages. They know how to link in (and maybe even build!) the underlying C library, and provide functions you can call from Rust. Then, on top, people can write a more idiomatic Rust wrapper, working in Rust's safety guarantees.
That takes some work and time to get right, but hopefully, it means that in the end, Rust users of C libraries shouldn't have to deal with the stuff you're talking about.
C technically has name-mangling too, but generally speaking either there is no mangling at all, or they just add an '_' to every symbol name.
Edit: To address your second point, I think that is a step in the right direction - and I think that Rust's compatibility with C libraries is probably fine, and comparable to most languages. I don't consider that to be so big of a turn-off that I wouldn't want to use Rust for a new project, though obvious writing wrappers isn't always fun or error-free.
But, assuming I'm understanding what you're getting at correctly, I'm not sure that will really solve the core problem I'm trying to get at: Generally speaking, nobody wants to be maintaining compatibility wrappers for APIs that exist entirely within their program and are probably changing all the time, and that's really what you need if you want to replacing part of a C program with Rust.
For example, to take it to the 'extreme' - the Linux Kernel module API changes virtually every version of the kernel (And the ABI is not guaranteed at all), and maintaining a complete Rust wrapper for it would not be a fun time even if a certain amount of it can be auto-generated. The API includes a very complicated mess of functions, inline functions, macros, structures (with varying different types of alignment and padding). None of it is guaranteed to stay the same across versions. And I think it is fair to say that most C programs have internal APIs like this (Though not as crazy) that are changing all the time and not intended to be seen by the 'outside world'. It's these types of things that I see being a problem for interfacing with Rust - APIs that are changing all the time in complicated ways which make writing and maintaining a wrapper very annoying and error-prone.
Oh, and another thing that makes C interop hard that you didn't mention: unions. We have those coming, though, and it should help a lot.
My original point (which has gotten a bit muddled in the details) was just that there could be room for a language closer to C that offers to fix some of the more annoying issues, while still keeping very good compatibility with C overall and avoiding the need for 'wrappers' and such to interface it with C code.
Firefox is shipping Rust code right now. There are various examples of Linux kernel modules written in Rust. There are rewrites of the coreutils in Rust. I don't understand why you claim this.
It is, and that is cool. That said, what it's shipping is essentially a separate library written in Rust that exposes a C API for parsing mp4s - it replaced one libraries usage with another, with a presumably sable API consisting of nothing but C struct's and C functions. Perhaps a key to point out is that it doesn't actually interface with Firefox very much at all - it isn't capable of accessing any Firefox state for instance. I also didn't list Firefox as you'll note - it's not a C project, it's a mishmash of languages already (Mostly JS and C++ from my understanding). I'm really talking about projects that are currently C-only adding in Rust code to the mix - they are structured much differently then a project like Firefox and the interfaces are generally much more complex as far as C goes.
I will add though, servo is making pretty impressive efforts. It is again a complete rewrite though, not an integration of Rust into Gecko which is the type of thing I'm talking about. A rewrite of a library in Rust against a stable API is generally possible, depending on the API - though the amount of work may make it prohibitive to get something usable.
> There are various examples of Linux kernel modules written in Rust.
I would like to see an example of a legitimate Linux Kernel module written in Rust that is in some form of use. I have never seen one besides a toy implementation and I can all but guarantee you it doesn't exist, for the reason that it would be way to much of a hassle to attempt to get it working with the mixture of macros, inline assembly, inline functions, gcc attributes, etc.
> There are rewrites of the coreutils in Rust
Coreutils is admittedly not a very good example, if only because coreutils isn't actually that big/complex of a project (Though supporting all the GNU flags and arguments is a pretty big task). They're also mostly just separate exe's anyway, so you could replace one or two with Rust code without much of a difference - I'd gladly remove Coreutils from the list if you would like.
Seriously this.
I write an awful lot of kernel code. There's absolutely no way I'd try to convince folks to accept Rust code into the OS repository, where it will have to:
- Be maintained for decades
- Be ported to a litany of platforms, several of which may only have decent toolchains from the GCC department.
- Vend stable ABI
- Not require keeping a litany of compiler versions around just to keep older code building.
Do you have a single example of this? We keep very close tabs on breakage in the wild even, and especially, for changes we were allowed to make (unlike say GCC, which happily breaks code if the language standard says the code never should have compiled in the first place). There are times when we've refused to make changes that we were allowed to make (because they were changes to unspecified behavior, which C/C++ has more of than Rust, and which GCC/Clang changes all the time) out of concern for breaking existing code.
As far as I'm concerned, honestly, this is just FUD.
That may change as the language matures -- great. I keep an eye on Rust so that I can eventually try actually using it for kernel-level development work.
C11 removed gets, for example.
The post-gcc 2.95 C++ ABI breakage was pretty suck, though.
[edit]
And please don't misinterpret my criticism as intentional FUD.
I work on $MAJOR_OS and am achingly tired of writing C, so I keep my eye on Rust as one possible salvation, once it's had time to mature.
It's just a very hard sell when there's no ABI stability, we're talking about code that's expected to last for decades, etc.
GCC did not provide a switch to get the old copy constructor behavior back when they made it stricter and broke my code.
C replacements will need to succeed in other OS not married with UNIX culture.
Which means Swift, .NET Native, Java/C++, C++17, on the OSes from Apple, Google and Microsoft.
You've seen where their OS code comes from, and what it's written in, right?
Just to be very clear, I'm very well-versed in life outside C and imperative programming. This isn't "UNIX culture". This is "systems programming" culture, and it's simply pragmatic.
Mac OS X and their predecessor might have an UNIX heritage, but C was always left for the very lowest layer. Already on NeXT the device drivers were written in Objective-C, being replaced by C++ on Mac OS X.
Anyone paying attention to their Swift talks during the last two WWDCs knows where the boat is steering. Cris is quite clear in stating Swift should be usable in all scenarios where C is being used and Sierra already got some adoption in userland components like the dock and launch deamons, now rewritten in Swift.
Microsoft has declared C89 as good enough with the future being C++ and .NET Native.
The C runtime library was rewritten in C++ with extern C for the public symbols.
The C99 compatibility and upcoming C11 are only done to the extent required by ANSI C++. For anything else there is clang.
As of Windows 8, the device driver framework has been changed to C++ and there was a talk from Herb Sutter where he mentioned the plan was to migrate the kernel to compile with a C++ compiler.
The idea of Core C++ Guidelines actually originated at Microsoft, before Bjarne and CERN guys got involved.
Google doesn't allow native code on ChromeOS and on Android they make pretty clear that the NDK is just to make game developers happy and nothing else.
They are all aware that C isn't going away tomorrow, but are driving efforts to make it as relevant in the future on their platforms as Assembly is today.
When I started working in IT, the only OS written in C was UNIX.
The language is not a sacred cow and the only thing preventing replacing it is the ubiquity of UNIX like OSes.
OS not bound to the UNIX culture and POSIX compatibility are free to chose other language as their systems language.
Not doing so, is usually a decision to cater to the status quo and ubiquity of existing developers and library (with their endless CVE entries).
The biggest issue is that it's a bit harder to maintain ABI compatibility. You have to carve out reserved vtable space, avoid exposing STL in your interface, etc, but it's doable.
As for Swift, it has heavy userspace dependencies that make it non-viable for kernel work. Rust does much better there.
Regarding Swift, C developers used to tell me C++ would never be used to write kernels, because it had a runtime and was too bloated.
Since those days, I have seen BeOS, Symbian, Genode, Mac OS X IO Kit, Windows 8+ Kernel Driver Framework.
Also not to forget, Objective-C kernel code used in NeXT.
Also the biggest open source C compilers are nowadays written in the language which those C developers considered too bloated for any real work.
This is true.
However, if we put aside the Rust code that is actually shipping, there's still plenty of work going on in sharing Servo's code with Gecko's.
One example of this is the ongoing experimental work to move Servo's style system into Gecko. Servo's style system doesn't have much of an "API surface"; everything is a surface. There's plenty of reaching in and grabbing Firefox state and vice versa.
Some of this is just done directly by reading or writing to structs. Some of this is done by writing small wrapper C functions (https://dxr.mozilla.org/mozilla-central/source/layout/style/...) and using bindgen. Bindgen has C++ method/ctor/dtor generation abilities that would obviate almost all of these manual bindings, though we aren't using them yet[1].
Overall, mixing C++ and Rust at a rough API surface in a large codebase hasn't been that hard. It's not easy either, but it's doable and I don't think it's anywhere close to being "nearly impossible without a complete rewrite".
[1]: The reason behind this has to do with name mangling -- Rust doesn't understand C++ name mangling, so bindgen generates the mangled function names and wraps them in a nicer API. This is all great, except that it means that the generated bindings stop being cross-platform, and you need to twiddle with the build system to either dynamically generate them or have a way to statically generate them for all platforms. Right now the servo-gecko integration uses a temporary build system that will be replaced soon, so this hasn't been a priority.
Rust supports all of these features. So your complaint is that nobody has written a translator from these things to Rust yet. That's a matter of filing PRs against bindgen, not some basic problem with the language.
Rust macros can't do all the same things that C #define macros can. You can't insert inline C code into Rust code without some serious work. And the fact is, as you pointed out, regardless of in the future, right now it doesn't work, and those features are absolutely necessary for writing something like a Linux Kernel module.