Why you should, actually, rewrite some of it in Rust
unhandledexpression.com
unhandledexpression.com
A while back, I ran my subtitle decoder through "cargo fuzz", and I was pleasantly surprised at the results: Close to half a billion fuzz runs found 5 runtime panics, all of which were detected by Rust before they could compromise security. If I'd written this code in C, several of those errors would have been exploitable. I like to think I'm a lot more paranoid than the average programmer. But the MPEG2 format is gnarly and, sooner or later, I'll miss a potential overflow when bit shifting, or get confused following internal "pointers" in a subtitle packet.
Rust has a few advantages for this work:
1. Rust does not require a garbage collector or other specialized runtime. This makes it far easier to pretend to be boring C code. This is a significant advantage over some other excellent languages like Haskell, etc., which require non-trivial runtimes.
2. Rust is very fast by default. For low-level programming, this matters.
3. The Rust infrastructure for testing and fuzzing is surprisingly good and easy to use, which makes it easier to produce bullet-proof libraries.
The downside is that even if you're already familiar with C++, it's probably going to take a couple of weeks to become comfortable with Rust. And if you don't really understand stacks, heaps, memory layout and references, it may take even longer.
I do agree with the underlying thesis: In 15 years, I'll be heartbroken if we're still facing an endless stream of security updates and remote root compromises. But it's going to require literally billions of dollars of programmer time to put a dent in this problem.
Wouldn't the apples-apples comparison be fuzzing a C program? If AFL would catch the same bugs, Rust isn't better than C in this case.
You can find a list of my vobsub bugs in the Rust Fuzz Trophy Case: https://github.com/rust-fuzz/trophy-case They are:
1. The shift overflow: This was harmless in both C and Rust, I believe.
2. The arithmetic overflow: This was a runtime panic in Rust, resulting in a clean crash. In C, this might have been exploitable with a complex enough attack.
3. The three "invalid slice" errors. All of these were clean runtime panics in Rust, and I'm pretty sure that at least two of them would have been exploitable in C with enough work.
So Rust has a couple of advantages here. Out of 5 errors found by the fuzzer, the worst thing that could happen in Rust would have been crashing the program in a controlled fashion. In C, we would have been looking at memory corruption and quite possibly escalation.
Secondly, Rust's runtime checks actually make fuzzers work better. Even simple checks like "is the end index of this slice great >= to the beginning index?" tend to catch problems almost as soon as they occur.
The thing that really matters is that these errors which the fuzzer found aren't exploitable -- memory errors in rust are guaranteed to panic (unsafe aside) instead of susceptible to overflows, etc. Since fuzzing is very much more of an art than a science, this guarantee is important.
AFL isn't even nearly guaranteed to catch everything, but what it's shown here is that the program contained bugs which would've resulted in an exploitable event in C. The program probably contains more bugs, but following this pattern, it's more likely that they result in panics in most of the worst cases, not exploitable events.
In that sense, fuzzing Rust is more certain to make the bad things actually be caught than with C where there's a chance that the fuzzer developed input that caused UB but the UB didn't happen to be caught by the mechanisms that are supposed to discover bad events during fuzzing.
If a C program works at all, there has to be some computable expression for the length of a C array. Code that uses an array passed as a pointer has to know how big the array is, even though C lacks a language construct for this. The problem is finding that expression. For many cases, it can be automatically inferred, especially if you have cross-module analysis and can see where the original array came from.
I once proposed an extension to C to allow expressing array size information.[1] Discussions indicated it was technically sound but politically too much trouble. It gives a hint, though, of how to approach size information in C. Instead of
int read(int fd, char buf[], size_t len);
one would write int read(int fd, char& buf[len], size_t len);
Same generated code for the call, but now the source code contains size information, which could be used for subscript checking by the caller and callee. A serious C to Rust converter needs to be able to detect relationships like that.It doesn't have to be 100% automatic. If the converter could knock off most of the easy cases (especially those involving strings), and accept hints on the hard ones, it could be usable.
[1] http://www.animats.com/papers/languages/safearraysforc43.pdf
You might be able to get it to work sometimes for a very simple subset. I'd be extremely skeptical that it'd be very useful, though.
That's an overly glib answer. The fully general form of this problem is undecidable, but it would be theoretically possible to build a "reverse borrow checker" that analyzes a Rust program, detects raw pointers that are provably used exclusively in ways compatible with Rust's lifetime semantics, and converts them to references. But it would probably be a fairly major project requiring lots of specialized knowledge, because lifetime semantics are complicated. Nobody has attempted it yet as far as I know, although the author of Corrode has expressed interest.
As part of an incremental re-write or port, this is still useful. It is extremely close to being able to straight-up cross compile CVS, for example.
Not all security vulnerabilities are due to pointer arithmetic or out of bounds execution or pick-your-rust-is-better-idiom.
Heartbleed is a great example. It wasn't caused by an error with how C handles memory or strings or anything else. It was caused by failing to validate untrusted input. Rust isn't going to help you with that.
Could the affected parts be re-written in a way where the boundary checks would have worked? Yes. As they could have in C, C++, D, Fortran, or any number of other languages.
If memory safety and correctness was so truly valued above everything else, we'd be seeing a lot more Ada than we do today.
And this doesn't even cover the fact that not everything can be re-written in Rust, mainly due to compilation target restrictions.
Exasperated Edit: No, I'm not saying code should never be re-written. I'm not even saying that Rust is a bad tool if the decision to re-write is made. What I am saying is that Rust is not a silver bullet. Rust is not going to solve every problem ever. Rust is one of many choices for writing memory safe, C ABI compatible code.
The reasons provided by this article are insufficient to solely justify a re-write in Rust; the exact same arguments could be used as justification for rewriting everything in Ada. Or Haskell. Perhaps some thought will be put in the next "re-write everything in Rust" article that takes that into account.
Then again, none of the past articles have.
Heartbleed implemented their own memory management system (and had a bug in their use of it). This is pretty unusual even in C. It's extremely unusual in Rust, but who's to say that a similar implementation in Rust would come across the same challenges and implement their own memory management too? And write the same bug? Ultimately this kind of system would be unsafe code anyway, so Rust won't help you there. The question is if a Rust programmer would design such a thing (it's very non-Rust-y). The answer is probably "no". However, the same can be said about C programmers and the heartbleed code, so you have a bit of a no-true-scotsman vibe happening here.
So, yes, heartbleed could have happened in Rust too. I do feel it's less likely (Rust more strongly forces you to compartmentalize unsafe abstractions), but all of that is really debatable.
As I noted elsewhere in this thread ( https://news.ycombinator.com/item?id=14753692 ), I've actually been working on a low-level media decoder in Rust. Compared to my earlier work in C (the xmlrpc-c library, for example), my Rust code has stood up very well to heavy fuzzing. My xmlrpc-c code, on the other hand, was written with ridiculous levels of paranoia, and it has been the subject of multiple CVEs thanks to a third-party XML parser I used.
So in terms of practical engineering reality, Rust seems to make a big difference. And even more importantly, it means I can trust other people's libraries a bit more.
Rust's practical advantages seem to be:
1. Runtime bounds checks! I wouldn't have thought that this was the most important thing, but when fuzzing, it's the last line of defense against quite a few potential exploits.
2. Debug-mode checks for integer overflow. Rust defines integer overflow as an error, and debug builds will actually check for it. This turns out to work really well with fuzzers.
3. The borrow checker. In my experience, this is actually less useful than (1) and (2) above in terms of preventing security bugs. But it does help.
So as somebody who's written parsers for untrusted data in both C and Rust, I definitely found that Rust helped me a lot. Of course, Rust can't help with some forms of stupidity, such as writing a bad custom memory allocator and using it to handle key material. OpenSSL's problems go well beyond the ordinary hazards of C.
C.A.R Hoare talking in 1981 about how their Algol compiler customers reacted to the proposal of turning off bounds checking.
"Many years later we asked our customers whether they wished us to provide an option to switch off these checks in the interests of efficiency on production runs. Unanimously, they urged us not to--they already knew how frequently subscript errors occur on production runs where failure to detect them could be disastrous. I note with fear and horror that even in 1980, language designers and users have not learned this lesson. In any respectable branch of engineering, failure to observe such elementary precautions would have long been against the law."
The mainstream adoption of UNIX and with it, C, made many without the knowledge of how computing outside AT&T walls since the dawn of computing looked it, to cargo cult ideas that were proven wrong elsewhere.
But given that victorious get to re-write history and UNIX got adopted by the enterprise, those OSes written in safer systems programming languages get forgotten.
The recent example was to learn about PL/8 and how IBM used it, in a compiler framework similar to LLVM, to do the research that lead to RISC.
http://rsim.cs.illinois.edu/arch/qual_papers/compilers/ausla...
Imagine that! The RISC computer was designed using an OS written in a system programming language using Algol safety checks, including bounds checking, in the 70's.
EDIT: Added a link to the PL/8 research paper.
I happen to like rust, though I would not suggest that it is the only solution to the classes of problems it avoids. I would, however, suggest that people very strongly consider using languages that avoid the problems that rust avoids whenever possible. Like, literally any time you're considering writing C or C++, at least strongly consider rust or another much safer option.
Sure one boundary error could have been detected in any language, but there will always be fewer in a language that simply disallows them. Consider how infrequently Gotos are used now there are no longer errors with setting state before a jmp/goto, because modern constrol flow tools function, case, inheritance, generics and others tools have strong semantics with good implementations that work well and reduce complexity.
Unfortunately language adoption is not a pure meritocratic affair. Bad languages can stick around for far too long because of external factors. Consider that Ada never had real use outside the DoD when it was shiny and new. That group isn't exactly known for evangelizing. The Rust community has better marketing strategy and has chosen a highly meritocratic approach to language design.
Really?
Validation and sanitization are the last, not the first line of defense. It is not about erecting barriers, it is about failure modes. All your software components, from core to border, should fail fast, safe and in a predictable way -- the exact opposite of what happens in C.
In any sane programming language (e.g. Java -- not particularly sane, but boring enough) Heartbleed would have resulted, at worst, in OutOfBoundsException or similar, and the only damage would have been the denial of service. And to do bounds checking, you don't need to have garbage collector -- it is the most basic thing the runtime can do, and performance penalty is minimal. Why it is customary to skip this in C/C++, I don't know.
Lack of bounds checking, not pointer arithmetic or allocation lifecycle, is the major source of security issues with existing software.
No, this is not true. Use after free is more pernicious nowadays.
If a language, for example, don't have NULLs and you need to use Sum Types (like ML or Swift) you have solved FOREVER and FOR ALL THE DEVELOPERS that problem.
But say that just because this is one problem among dozens, then is better FOREVER AND ALL THE DEVELOPERS to stay with the problem?
Instead, why not put as our goals to eliminate all the problems that are possible?
The Rust language is more than just memory safety. Memory safety is certainly the most prominent and unique feature of Rust, but the language also includes a lot of features that make writing good, bug-free code easier.
For example:
`Result`, when returned, forces all callees to check for errors. It's all too easy to throw away error codes in C/C++. This is probably one of the most important features in Rust, besides memory safety.
To easily consume an `Enum`, you have to use `match`, which forces you to handle all members of the enum. In C/C++ it's easy to build a switch for an enum, and then later when a new member is added to the enum and you forget to update all your switches.
Support for multiple return values (through tuples) means being able to avoid the nightmare of output pointer arguments to functions.
No NULL pointers.
Unit testing built into the canonical tooling.
Documentation testing built into the canonical tooling.
The `Option` type, which lets you return "nothing". In C/C++ you'd have to return a bool, and again use a nightmarish output pointer argument.
`OsRng` in the rand crate (go ahead, try to write a function that safely reads urandom in C).
No undefined behavior.
The list goes on. The flavor of it, is that Rust makes it easier to write defensive code. Defensive code is code that is hard to use wrong. For example, for a backup solution I built using Rust, I made all the cryptographic types their own unique, opaque types. EncryptionKey, HmacKey, Salt, etc. For someone new to the codebase, these types make it obvious what each function is asking for, and make it really difficult to use the functions or the types the wrong way. And they are super easy to build and use in Rust, see my code: https://github.com/fpgaminer/preserve/blob/master/src/keysto...
C++ would require a whole set of classes, and require instantiating a new class every time I want to add a new cryptographic type. Rust only requires 3 lines per type.
TL;DR: The real magic is that it's easier to do the right thing in Rust.
Nowadays, you should just call getentropy (http://man7.org/linux/man-pages/man3/getentropy.3.html) instead of trying to read from /dev/urandom, unless you have special requirements.
Warning is produced by nearly every good compiler. They aren't forced because sometimes I don't really want to check for return values.
> In C/C++ it's easy to build a switch for an enum, and then later when a new member is added to the enum and you forget to update all your switches.
why would you update all other enum values in accordance to addition to a new one? I understand you could possibly be skipping one switch case because you just added it in enum definition but didn't update switch case, but why would you update rest? Is this a forced requirement in Rust? This is bad if it is.
> Support for multiple return values (through tuples) means being able to avoid the nightmare of output pointer arguments to functions.
std::tuple?
> No NULL pointers.
but what if you need them? Lets say a type safe, non-zero nullptr? I understand some people want to bring up whole NULL is bad mistake argument but remember some of us work with maintaining large code where such changes are non-trivial.
> Unit testing built into the canonical tooling. Documentation testing built into the canonical tooling.
This is good argument :)
> The `Option` type, which lets you return "nothing". In C/C++ you'd have to return a bool, and again use a nightmarish output pointer argument.
std::optional? Or many other implementations of optional type? I've been using them since ages(before rust was even born) and writing them is no NP problem. Don't credit rust as if it brought the `Option` type.
> `OsRng` in the rand crate (go ahead, try to write a function that safely reads urandom in C).
Is this intended to be sarcasm? try <random> header and see if it fulfills your requirement, otherwise there are tons of libraries. But wait, what? This is another already solved problem unless I'm missing something.
> No undefined behavior.
Lol do you even unsafe bro?
> C++ would require a whole set of classes, and require instantiating a new class every time I want to add a new cryptographic type. Rust only requires 3 lines per type.
Depends on your cpp skills and judging by this post, they're certainly less than expected from a beginner. I hope you learn about certain languages (even the ones you are defending) before writing things on internet, since 90% of what you wrote won't even come near acceptable so I suggest doing your research properly.
Regarding safely reading urandom, you don't seem to be aware that concurrency issues occur when accessing it from multiple processes. This is something that the borrow system can aid with by formalizing access to a resource like urandom and not allowing contention for it to occur at runtime.
Also, nobody appreciates aggro kid language.
* option and result are also available in C++, the former is even standardised.
* random numbers support is quite good.
* C++ compilers will warn if not all members of an enum have been handled in a switch. It's also easy to assert/throw in the default case as a safety measure.
* tuples are likewise supported
* I believe something like your opaque types could be done with templates and tag classes in a similar amount of LOC.
But yes, in Rust it's generally easier to do the right thing. This is the big benefit of learning from others' mistakes and not having to maintain backwards compatibility with ancient decisions.
To me, this is super important. But yeah, it's still a good thing to have!
IIRC there are TMP-implemented versions of this that are safer (though a bit ugly to use) though.
It's even worse here on HN, where the a certain group seem to thrive on blasting not-Rust, especially if that not-Rust is C or C++. I've seen comments to the effect that anyone writing in C or C++ today is literally acting with reckless indifference to life and safety. It's terrible.
But I've basically stopped commenting in Rust threads. There's too much "not even wrong" as regards C and C++ to be bothered with.
It's easy to find fault in C/C++. It's easy to parrot the Rust marketing. It's not so easy to actually have spent the time to learn and use Rust, let alone articulate the subtle ways that its design, taken as a whole, is better than alternatives.
Building those constructive arguments is _hard_. I remember reading an article about error handling. It had to have been at least 10 pages worth of content, covering the history of error handling throughout all the old and modern languages, and using examples for where each fails or succeeds. It culminates, more or less, in explaining exactly why the error handling of languages like Rust outperforms in the field of systems programming.
(NOTE: The author of that article was actually arguing for the error system used in the language they developed for their OS, IIRC. But Rust was pretty darn close to the ideal the author was going for, and they pointed out as much.)
My point is that it took 10+ pages to make a solid argument for _just_ error handling. Not many people have the experience, wisdom, time, and willingness to write even a fraction of such insightful content in a comment on HN.
That said, well written, balanced comments still generally float to the top in even controversial HN threads.
EDIT: I would also like to point out something specifically in response to "I've seen comments to the effect that anyone writing in C or C++ today is literally acting with reckless indifference to life and safety."
There is a tremendous amount of pent up anger in the programming community against C/C++. The language should have been put our to pasture a _long_ time ago. Rust is the first language which has a real chance to displace it. So it's no wonder that a lot of us are willing to just burn C/C++ to the ground and jump ship.
Not to mention that as programmers we may be quickly approaching a time when security bugs become a governmental issue, and the _last_ thing we want is the government mandating how we write code. So it is in everyone's interest if we self regulate. Again, that would explain why everyone is kinda jumpy about C and security bugs.
Then the UNIX FOSS, written mostly in C took over, and set us almost 20 years back in security and safety.
Now the reason why some people claim that Rust would not have stopped it, is that a similar leaky buffer system is possible to be written in Rust. This however is just applicable if you just use byte buffers for everything. At that point you become close to emulation. Also if the system malloc is shit, you just use jemalloc which is the standard for Rust. So there is no need to roll your own malloc replacement in Rust
Yes, it would. In Rust, the server would have crashed when it overran the buffer instead of sending that potentially-secret memory back to the client. Heartbleed is a great example of why rewriting in Rust is good.
The problem was that many companies had certain problems paying for Ada compilers and rather used the free C compiler delivered with their OS SDK.
Your argument does apply to Ada, but freeing from the heap in Ada is unsafe, which has made it difficult to use in a safe way for a lot of cases.
Rust really does make an advance in this regard: safety, and no significant runtime requirements.
D and modern C++ might work for disciplined teams, but seem a few steps behind rust safety-wise.
I think you'll want to check your facts again:
https://tonyarcieri.com/would-rust-have-prevented-heartbleed...
You didn't read the article. He acknowledged that the hyperbolic calls to "IMMEDIATELY REWRITE EVERYTHING IN RUST!!1!!1one" are silly. In bold, the author states:
> What I’m advocating for is much simpler: surgically replace weaker parts but keep most of the project intact.
Which, by the way, is exactly how Mozilla is applying Rust-written rewrites to Firefox (dogfooding their own programming language).
It really is very straightforward to start inserting Rust into a C/C++ codebase. I did the Rust port of netcode.io[1] which started out by just calling C code for the portions that I didn't have working in Rust. Eventually the port was 100% Rust but I was really impressed with how seamless mixing C/Rust was.
Even now our unit tests run against the C code at a functional level so we know when the protocol diverges which lets us easily catch breaking changes from the reference C implementation.
Much like everything, don't apply an adage blindly and do a proper evaluation but I think if you're in the a space where memory or performance matters Rust is a very strong contender that plays well with existing ecosystems.
Some badass below even just said that "writing C++ code longer than a few lines without UB is humanly impossible". Apparently all these FUBAR C/C++ systems (Linux and Windows kernels, all? web browsers, LLVM that the dear rust uses, device drivers, web servers, router software, VMs of Lua, Python, C#, Java, ..., etc.) work well enough to transfer that kind of garbage to me.
It's a bit like the decades old vim vs. emacs, C vs. C++, C++ vs. C# vs. Java, etc. feuds.
I'm still a huge fan of C++ but Rust gets me to where I want to go with C++ with much less fuss and a lot more confidence.
Now Rust comes along with its safety claims (which are mostly true) and suddenly these computer users have what they believe is a legitimate complaint. They now see the frustrations with running C/C++ software as no longer facts of life but, instead, development externalities that users are forced to pay based on the decisions of the developers.
And so you have a bunch of people who've likely never written much in either C/C++ or Rust who are trumpeting Rust because they're sick of dealing with CVEs, segfaults and all the other 1970s baggage that the C-cosystem imposes. And it doesn't help that C/C++ developers tend to react defensively and take the advocacy as an impugnment of their developer honor. Saying "It's only inept C/C++ developers that write code with UB or memory safety issues. Disciplined developers can write safe C code!" just infuriates people even more since the weight of evidence suggests that any non-trivial C program will eventually have a bug or vulnerability of this kind. Humans make mistakes. That's an immutable fact that we need to take into account when relying on humans to do anything. Programming is a thing that is part of that anything. To err is human, to catch as many of those mistakes as possible is the compiler's job.
But I think both the Rust community and the Rust-resistant community should be wary of those trumpeting Rust who are part of neither of those groups. There are many good reasons to use Rust and some good reasons not to. Often times a hybrid Rust/C solution would be the best. We should be careful to try to take ego out of the decision making process as much as possible. And we should remember that when users are asking for Rust, what they're really asking for is an end to the pain that they've had to deal with. If it can be accomplished in some other way, then that works too.
Personally, I'm considering C++ for a new software project despite my extensive background in security. I think Rust is a great language, I just don't have much interest in it.
It is true that we built Rust to address shortcomings of C and C++, some some form of crtitique has to happen. But many Rust programmers, and especially Rust leadership, will gladly say there's still some good reasons to write C and/or C++ today. Many of those reasons will still be around for a very long time.
In any case Rust is still solidly on my list of things to learn to become a T-shaped person with lots of insight from different angles but it's annoying to even be unable to skim through comments without seeing garbage, this doesn't happen that often, even the Java, C#, C, C++ quartet stopped going at each other's throats online a long long time ago and it's just skirmishes now and not a constant onslaught.
As others have said, I think this is more of an HN issue than anything else; you won't see this kind of discourse in Rust-centric spaces. I know that's not super helpful, but I'm not a HN moderator, so...
Back then, wasn't it readable Python versus unreadable Perl?
Lua has it's own problems, of course, but I don't see it as a language developed as a consequence of frustrations with another.
Yes, they work well enough, until they don't, because realistically, they are rife with dependencies on undefined behaviour. Linux can only be compiled with gcc last I checked.
Kinda reminds me of the "coal vs. renewable energy" feud. The first tier arguments are formed around {economics, externalized costs, existing infrastructure, base load vs intermittent load} and then second-tier arguments align around {culture, history, identity, momentum, geography, political party}. You are making a second-tier argument against Rust when the article is making a first-tier argument for Rust.
I would argue that you are attributing to "the Rust community" the more outrageous attitudes which are a minority of the Rust community and ignoring the majority which have much more rational and banal attitude. And likely you are attributing the inverse to the existing C/C++ attitudes towards Rust (conveniently ignoring lots of the best arguments for Rust). It's exactly what happens in those feuds you described and in modern US politics (Democrats vs. Republicans, liberals/progressives vs. conservatives, secular vs. religious, etc). By focusing on the strawman arguments, you are missing the potential benefits of using a newer generation language.
I'm not using C and C++ professionally at all so the only place Rust has to fit in is fun or potential long term professional opportunity (assuming Rust is a fair bit more widespread than now by then, otherwise C and C++ win in the native field for employment opportunities), I'll not have fun (and I won't learn anything I don't immediately need that won't bring me fun) in a sea of vitriol against anyone who doesn't hate C and C++ so I'm happy Rust community guidelines forbid such idiocy in official spaces and documentation.
Rust is apparently targeting C and C++ devs but at the same time the ones that like it are going crazy and saying stuff about C and C++ being impossible languages (I agree they are by far the most spartan and hardest in the mainstream right now but not impossible as evidenced by lots of quite reliable software written in it, some that lives depend on).
Either the most obnoxious ones left, or they learned that this was not a productive approach. It's been refreshingly quiet lately...
But I disagree that there's a war every time Rust comes up, as a general thing. Many of the Rust proponents here are quite reasonable, willing to listen, willing to explain gently. (The conversation on the current article has gotten a bit out of hand, I'll admit.)
I wasted an hour and a half or even two here today as well, I at least got a reply from a Rust core team member who responded to me (yay), was nice enough and told me the community rules disallow wars, that's something I'd not get otherwise, but still, this feels very very unproductive in the end...
I don't think you read the article. The author specifically cites lots of different types of hyperbolic "rewrite everything in Rust!" arguments and explains how this article is not that and he doesn't advocate for that. The author of the article advocates "surgically replace weaker parts but keep most of the project intact." This is exactly how Mozilla is dogfooding Rust in Firefox.
And no one is arguing that all of those C/C++ systems you mention are "FUBAR" -- that's a strawman. Rust advocates, as well as advocates of many of the newer-generation, lesser-used languages advocate for advancing programming beyond the languages designed in the 1970s. We've had an explosion in programming language diversity over the past 30 years and we've developed some great research on how to prevent certain classes of defects. The longer those C/C++-written systems exist without a rewrite in a safer language (or thorough audits including massive amounts of fuzzing), the larger the impact of inevitable non-zero-probability of those vulnerabilities which Rust prevents.
The article's author completely admits that at their best, C/C++ developers can avoid all of the problems that Rust solves. But not everyone is a rockstar and not everyone performs at 100% all of the time. Rust is written for humans and the compiler is a strict nanny, preventing you from getting yourself into danger, the same ways you can with C/C++ memory issues. It also has tooling for unit tests, benchmarking, fuzzing, and standardizing code formatting conventions in the language. You can bolt these things onto C/C++, but that means that you have to go find them (which new developers won't do unless sufficiently motivated to).
I consider Java browser applets unsafe trash and I'd never run one I don't absolutely trust. This person has similar (or even worse) opinion of C and C++ languages but runs tons of software in them with seemingly no problems since his idiotic comment made it in here and arrived to me intact despite all the C and C++ software on the way from him to HN to me.
It's not just memory safety. Rust has sweet error handling — the syntax is almost as noise-free as exceptions, but code flow is as predictable as error codes. In Rust I handle rare error cases that I wouldn't have bothered with in C (and thus my Rust programs fail in orderly fashion instead of running with garbage values).
Runtime arithmetic overflow checks have saved me many times. Knowing I have this safety net I can be cavalier about using smaller integer types.
I don't have to free memory explicitly, and yet peak mem usage is as minimal as it can be, and I did not have a leak yet.
And the whole ecosystem is fantastic compared to C. There's a culture of having unit tests. Ease of using dependencies via Cargo, and the zero-cost abstractions, help having dependencies broken down into small and focused libraries.
I feel like a lot of the cool things in Rust get obscured in the never ending safety dance. There is some rad stuff in Rust compared to its closest competitors (C and C++) -- cleaner error handling, a standard package manager, no aliasing -- I want to hear about what else Rust can do for me! More safety guarantees, ehhh, I'll take them but they aren't a huge win in our work.
No.
const USAGE: &'static str = "usage string";
I did rewrite it in Rust. I rewrote a small low level command line app in Rust. I really wanted to like Rust going in but I freaking hated it going out. They had a few good ideas but they never knew how to say no. The part I hated the most was the Rust macro language. And for real low level to the metal systems programming, everything is unsafe; so you may as well be writing in C.
Go is pretty minimal. They knew how to say no. I like modern C (C99 and C11). It has most of the good ideas from C++.
const USAGE: &str = "usage string";
and there's talk of maybe making it const USAGE = "usage string";
> The part I hated the most was the Rust macro language.We don't like it either; there's a replacement coming. We reserved the "macro" keyword before 1.0 and made the current one use the less-good "macro_rules" to help ease the eventual transition.
But.. but.. Scheme macros are so nice!
> And for real low level to the metal systems programming, everything is unsafe; so you may as well be writing in C.
Sounds wrong to me. People have done things like booting up x86 with all of the hardware bit-twiddling behind safe interfaces (for example: https://os.phil-opp.com/).
Yes, this is a C/assembly thing. But it is also a Linux/BSD/Unix thing. You really cannot call Rust a systems programming language if signal/setjmp/longjmp are not fully supported. It just goes with the territory.
And for now, there are definitely ways to handle signals using libraries.
Do you have any source that can confirm that? Because what you wrote seems really wrong. I see linuxes/bsd/unix EVERYWHERE (Android/iOS/OSX/almost all backend servers etc.)
You might want to take a look at this series: http://blog.japaric.io/fearless-concurrency/ .
As a note to others, arithmetic checks doesn't happen in release builds unless you used something like checked_add, add "debug-assertions = true" to [profile.release], or use -C overflow-checks flag with the compiler.
https://github.com/nox/rust-rfcs/blob/master/text/1535-stabl...
A few examples:
* You may need to use PhantomData in your struct definitions to properly track lifetimes. Not sure I fully understand.
* C functions don't always return. Sometimes they longjmp (c.f. PostgreSQL). Calls into C code need to prepare for this and it's not trivial nor does it generalize to all projects.
* Integrating with the memory management scheme is probably possible but not trivial. For instance, Postgres memory contexts (regions/arenas) can hold any kind of object, and code can switch between them and create/destroy them at will. You could rely on rust to manage it's own memory, with circular references an Rc that might leak.
* Postgres has its own calling convention for SQL-callable functions and conventions for loadable modules. Integrating with this is a pain because macros are hygenic and can't make up new symbols. Maybe procedural macros will help when they arrive.
* Rich APIs that rust supports don't always translate well to C. When integrating with a large application that pushes you toward the lowest-common-denominator APIs.
* A lot of C macros don't translate well to rust and just including a C header in rust is a lot of work. Hoping procedural macros will help here too.
I really need procedural macros to stabilize, otherwise it just puts too much pain on the UDF author.
Why exactly is that? I'd have expected a C wrapper to take care of most of that? Are you concerned about the Datum<->native type C macros?
Are you planning/hoping for this to be all safe, or is there going to be unsafety in the wrapper?
Rust's hygenic macros don't let you do that. So defining a UDF has a bunch of boilerplate, unless I am missing something.
Procedural macros should alleviate this, but it will be a while. I might just release and document the boilerplate needed.
EDIT: Yes, I am trying to be mostly safe. I will see how close I can get for at least the basic stuff.
Right - but I don't think you need to do so. That'd only be required if you'd use language 'C' - why not instead use language 'rust'. That'll force a simple wrapper function which looks up and calls the real function, but that's fairly harmless, no? Might actually be good, because you can do some extra checks and conversions there.
I'm concerned if we start pushing that too much too early, the initial friction will burn people. You only get one first impression.
For anyone who thinks "yes, now is the time" I have questions for you:
Have you released and distributed software written in Rust? What's the install story like for end-users? Is it in a repository for a major OS (e.g. can I yum install it, or apt-get it) and if so which?
As far as I can tell there's no major package manager accepting or even ready to accept programs written in Rust yet. Am I misinformed?
At least Debian, Fedora, Arch, and Gentoo have rustc and cargo in their repositories. Oh and Alpine, recently. FreeBSD (IIRC) has it in ports.
In the not-too-distant future, there's a pretty important package that will require Rust: Firefox. HEAD already does today.
And even then, having the compiler and package manager available is a step along the way, but insufficient. Anything non-trivial depends on libraries, which with C programs are typically also packages... which for my project means I need to create 73 Debian packages to distribute my software. That's a big hurdle.
Debian has made a tool, "debcargo", to automatically turn crates from crates.io into Debian packages, so it should be "just run this tool." I have not used it myself though.
Oh, I thought of something better than just the compiler: https://github.com/burntsushi/ripgrep#installation
* homebrew
* chocolatey
* arch
* gentoo
* fedora
* RHEL/CentOS
* Nix
All have at least one Rust program packaged :)
Anyone releasing software in C, Python, Haskell, Ruby, Lisp, or whatever language has extra work to do. I don't think there's any sizeable difference between any of them.
Distributions such as Fedora and openSUSE need to have a package for everything installed on the system (and everything required to build said packages). Now take into account the you can have multiple versions of the same library in a single rust project. While you could handle all of this on paper, automating it and putting it all into every distributon's build system is a very big task.
Ruby has similar problems, NodeJS is even worse than Ruby in this respect but I believe that Rust might make this the hardest for distributions. Personally I've never liked cargo that much as a user either, but as someone who works on openSUSE and was thinking about packaging rust packages I decided against it. It's just far too difficult and I'm not convinced it's worth the effort, since it feels like people have forgotten the reason why distributions exist in the first place (to provide a cohesive experience and provide you seamless management of your software). Having a shiny new package manager for every language just makes life difficult for everyone involved.
Fixing that is a big focus of this year https://github.com/rust-lang/rust-roadmap/issues/12
I'm saying this as someone who has started writing their projects in Rust and has plans to ship said projects as part of openSUSE and eventually SLES. If you cannot satisfactorily maintain a Rust package inside OBS then it's a non-starter. I imagine that many other distributions feel this way. We were burned with Ruby (and continue to be burned by it) and I doubt that anyone is going to jump on the "just auto-generate an RPM for every version of every crate we need" solution.
It's great that Firefox will have Rust code, but I'm not convinced that will make the situation better for distributions. It'll just make the packagers more stressed out.
It is just one of many things on our plate, though.
pub extern "C" fn count_substrings(value: *const c_char, substr: *const c_char) -> i32
If you incrementally convert from C/C++ to Rust, you end up with those constructs in the final Rust program. Not good.[1] http://siciarz.net/24-days-of-rust-calling-rust-from-other-l...
- the C interface can easily be thin wrappers around pure "nice" Rust functions, that just do the necessary unsafe conversions
- and, whether or not that is used, when a function is no longer being called from C (that is, everything that calls it has been converted to Rust) the arguments can be switched to something more natural and strong static typing will point out all the places that need to be updated.
I cannot believe that a pure Rust codebase would want to be passing raw pointers around. And the alternatives don't seem at all better: only ground-up complete-rewrite migrations (hard to justify so often won't even start) or not migrating at all (meaning one has a C codebase that is still full of unsafe pointers).
Its an echo chamber of fandom. Over time I've come to despise "evangelism" or any kind. I'm sure Rust is great. I just don't think it's news every time a Rust fan says that its great.
YouTube lectures of people actually using it? That would be interesting. Interviews of people actively porting a large C project to Rust. That would be interesting.
Rehashing its feature sheet? Not so much.
"That’s because I have been working on this subject for a long time now:
* I did multiple talks on it * I even co-wrote a paper * I did it both as client and personal work"
This was days ago, why do you care?
When someone in one message both recommends Rust and says that it's absolutely impossible to ever write correct programs in C or C++ that are more than few lines long what am I supposed to take away from that? Rust itself uses C++ written LLVM, is it unreliable too then? Seriously... And implicitly, anyone liking C or C++ is an idiot and a bad programmer.
While there’s _some_ SIMD support in Rust nightly builds (but not in Rust stable), it’s just not good enough. For one thing, Intel only supports their intrinsics for C (C++ also gets that ‘coz compatibility) and Fortran. Another thing, Rust strong type-safety concept complicates SIMD code, especially when the code does integer math. These __m128i / __m256i / __m512i registers are often treated as different data types by even consecutive instructions.
While Rust might be good enough for some areas, performance critical CPU bound code is not one of them.
When you’re coding assembly, you have to code everything in it, including code running on the scalar parts of CPU cores. With intrinsics you can code these parts the usual way, e.g. for( size_t i=0; … )
With intrinsics you can even compose your vectorized code the usual way, VC and Clang have __vectorcall calling convention for that, Intel has very similar __attribute__((regcall)).
(no true Scotsman)
What do you call modern C?
"We found a terrifying vulnerability in your codebase. You should rewrite it in modern C++."
"OK, done!"
<Time passes>
"We found a terrifying vulnerability in your codebase. It must not have been modern C++!"
A personal opinion from a person who loved C++ for quite some time, before diving deep enough to realize how dark is the abyss.
Why the sarcasm?
> Writing C++ code longer than a few lines without UB is humanly impossible.
Do you want me to provide one for you? Because that will definitely change your definition of 'humanly impossible' tasks and might also bring you back from realms of using comically bad phrases..
Anybody who has the faintest clue about both languages should know that.
It's undefined behavior, despite not calling free anywhere. Replace with std::string and it could be use after free. In C, UAF wouldn't happen that silently for heap memory.
From my POV compilers should warn that this code is potentially dangerous. My rule is: never return a reference or pointer from a function to something whose lifetime you don't have control of.
With C++, good luck even reading and understanding The ISO C++ Standard. Not to say fully internalizing all possible interactions between all nuances. I presume not all of them may even be truly 100% fully fleshed out and explored in The Book. As to writing a compiler - even just writing a parser for C++ is famously super hard!
Anyone that is able to keep in mind the documented 200 cases of UB is my hero.
I mean, seriously, understanding the borrow checker is nowhere near as hard as understanding this: https://akrzemi1.wordpress.com/2013/10/10/too-perfect-forwar...
There's a No True Scotsman aspect to C++ advocacy. Anything that is obviously dumb either is because the programmer did it wrong, because it belongs to the ever-growing list of features you avoid or "doesn't come up that often".
> The whole struggle with lifetimes
How did you try to learn them? I'm interested in trying to make this easier!
> the redundant syntax
Which bits are redundant?
anyone thinking "I'll just write these 50k SLOC very well so it'll be ok" should not consider a career in programming.
Life over here is great, but I can the warts of the language looking backwards. The promised land in the future doesn't look quite as bright as the promises Rust offers. The only thing keeping me from trying it is the giant codebases in my current projects.
How do you see it?
Nowadays I only use C++ on hobby projects or when Java/.NET need some kind of integration work.
To me it seems that as code bases grow organically, it will become very hard to get which code is in which ANSI version, specially problematic given some of the changes that took place between standard revisions.
At one place we are in a race before a tight deadline and at the other we are breaking a large monolith into smaller packages. At one place we implement a new practice for all our code, and at the other we implement all our best practices as we chop code out of the monolith. Both places are seeing a reduction in bugs. The place with the packages is probably reaping a larger benefit because they are aggressively re-working old code and have better test coverage and can do so with confidence.
Code bases do grow organically, but like places they can be tended and pruned to make impressive orchards or topiaries or left to the wild to become impassible jungles. Tools like RAII, exceptions, classes, etc... are just tools for managing this.
I think the tools in C++11/14 are at least as powerful as in any language except perhaps Rust. Every time I hear a C developer say "C can be just as safe..." I am imagine a person with nothing but a pocket-knife trying to manage a whole forest.
https://github.com/shinglyu/RustPythonWould your project benefit a lot from algebric datatypes -> OCaml is a great fit!
You want to port as soon as you can: C++ is your friend.
You plan to write HTTPS service nodes: Go has the best standard library out there.
That changes the equation considerably, compared to something like Go where none of these benefits exists. For example, exporting code from Go not only requires the runtime and GC, there's also significant function call overhead because of the way Go maps goroutines to threads.
There are many other good languages, but few of them have these benefits. C++ is probably the option with the least amount of friction against C, but then you're also inheriting most of the unsafety of C.
Rust also has its own costs. Its biggest limitation when it comes to mixing in code with that written in another language is probably its poor support for shared ownership (i.e. multiple references of the same object), while in most languages, shared ownership is the norm.
Multi-language setups (which you invariably get with an incremental rewrite) also generally create other interfacing issues when they share objects. How are you going to represent `std::shared_ptr<T>` or `std::vector<T>` in Rust, for example? They are opaque types, after all. Can you guarantee that Rust properly observes C++ semantics with regard to assignment, move assignments, and destructors when dealing with instances declared entirely in C++ code once you start mixing and matching C++ and Rust?
- [Calling Haskell from C - HaskellWiki](https://wiki.haskell.org/Calling_Haskell_from_C)
And the FAQ on the Haskell wiki directly addresses this too:
- https://wiki.haskell.org/Introduction#I_already_have_a_large....
So apparently it is doable!
For many projects that is a showstopper.
https://stackoverflow.com/questions/3859340/calling-haskell-...
haskell is not tailored for that.
of course there are other domains for which haskell is top-nocth and rust falls flat.
So, yeah, no allocations in situations like that. Allocate at start-up, and re-use your buffers (for that kind of code).
Or you know, why rewrite it at all? You could instead try and use a safe C implementation (such as gcc/clang with the sanitisers or something like https://staff.aist.go.jp/y.oiwa/FailSafeC/index-en.html), but to be frank if I was to rewrite my programs I would use a truly modern language like the ones that I mentioned above.
> Why Rust? Why not Idris? Why not Haskell?
it can easily call C code
it can easily be called by C code (it can export C compatible functions and structures)
it does not need a garbage collector
if you want, it does not even need to handle allocations
the Rust compiler can produce static and dynamic libraries, and even object files
the Rust compiler avoids most of the memory vulnerabilities you get in C (yes, I had to mention it)As for GC, I think that Mercury avoids its use https://lirias.kuleuven.be/bitstream/123456789/131304/1/Mazu...
IMO Rust's only true advantage over it's direct "competitors" is it's safety guarantees.
As for why not use sanitizers and such: That's an ongoing cost of using the sanitizer. Rewriting in Rust is a one-time cost, and can be done incrementally.
I also find that 'not null' access types, typed pointers and in general the in/out/in-out parameter passing mode help making ownership clearer... And there's also the limited types in the toolbox, to 'hide' ugly pointers/resource ids, etc.
Spark2014 still excludes pointers and aliasing, but I think some work is in progress. Previous HN thread : https://news.ycombinator.com/item?id=14346032
The HONEST question is because developers hate progress. We could have left in the past C/C++ by now, but not, is better to move forward with the same problems as DECADES ago.
Rust is a potential contender because is not that far from C/C++, and have {}, and the last point is not even a joke.
-----
Is weird to me why a clean fork of C/C++ was not made, if the prospect to move to something else was too much progress for the minds of the industry, the slow progress is a good alternative. You could have C.1, C.2, C.3, etc...
Think how much is about "modern C, modern C++, coding well" and then why not commit all that wisdom as the language?
The creator of Pascal made a point that a new version of a language must remove and correct the mistakes of the precious version, not accumulate problems.
That is fight AGAINST entropy.
Plus, this need a serious marketing push, and more than the work a few on them. Is each year harder and harder, similar to Cobol (where each year on drugs make you less likely to quit).
Now, the best hope is a painfully and too long gradual switch to rust or something else...
BTW Haskell has been used to write OS kernel modules: [1], [2]. It's still quite a bit cumbersome.
I also wonder why ATS language does not receive enough love in this area.
[1]: http://www.pfq.io/ [2]: https://tommd.wordpress.com/2009/09/13/kernel-modules-in-has...
I mean both are reasonable arguments that get made often on here, but the one doesn't necessarily follow from the other.
I really hate to see the naysayers comment on this article without even having read it. I think the author made a very good point.
Multi-platform support: Pray that your project's build system makes it straightforward to get all the dependencies going on every platform. Pray that your dependencies are easy to access and of the correct version. Pray that the manufacturer's fork of an ancient gcc isn't too buggy to use. C made it possible, not easy.
Tools: Both languages are too impoverished to have tooling as comprehensive as the stuff available in, for example, Java, although VS pushes very hard on this front. rustc already gives better error messages than any C++ compiler.
Code base: See the build system problem. Maintaining open source projects in C and C++ is an exercise in endurance. Single header file libraries have come into vogue as a result - and they're a hack. There is a lot of decent code in both languages not being used because it's hard to get running.
Community: Rust is in a better position here in part because it's smaller. If I encounter a C++ hacker in an online space it's just as likely to be an aggro kid as a seasoned pro.
Good Haskell code is rather fast, but that's beside the point. For many projects, the only measure of goodness should be how nimbly one can then use and experiment with one's code. In Haskell, like Lisp before it, one ends up with a language to use one's code. Like writing a Mathematica package and getting all of Mathematica for free, only better.
How does verification prevent buffer overflows? It can reduce their frequency, but you can never know you got them all. They find issues in libCURL and openSSL that have existed for years. Very few people are better than the people working on those libraries.
It is better to be safe by default then add risk only when you need it. I say this as a C++ developer, I think const should be the default state of all variables and one should have to opt out of that, and Rust did that right and C is so far from that I see no reasonable way to use plain old C safely. At least with C++ I can hide my stuff in classes and verify one thing at a time.
> They find issues in libCURL and openSSL that have existed for years
Neither of them are formally verified. https://github.com/seL4/seL4 however is formally verified.
I don't know if its too hard to do to real software or if C doesn't allow for it, or if it is just too expensive. The simple matter is that is not an option for the vast majority of projects. Unless you can make it practical, I assert it has no place in a practical discussion about preventing software issues.
Formal verification only works if the formal verification was mistake-free. (And how are you going to prove that? With a formal verification? It's turtles all the way down...) Also, it only works for the aspects that were formally verified. Heartbleed, for example, could be regarded as a flaw in the specification; formally verifying that the code matches the spec won't save you from that.