I do not mean to pick on C++: the same problems plague C, Ada, Alef, Pascal, Mesa, PL/I, Algol, Forth, Fortran … show me a language with manual memory management and threading, and I will show you an engineering tragedy waiting to happen.
Does Ada really belong in that list? AIUI it was intended to be used with a much higher-level concurrency model than native threads and thus didn’t suffer that pitfall.
The only thing inaccurate about it is the concurrency claim. There were safe, concurrency models before Rust. They included Concurrent Pascal (used in Solo OS), Ada Ravenscar (deployed widely), and Eiffel’s SCOOP (deployed widely). The truth would be Rust is doing better by making one that combines more expressiveness, dynamic memory, and no GC in one. Ada side isn’t sitting on their hands, though.
Note that Graydon’s claim includes “manual memory management.” I don’t know Ada, but can you use it safely without a GC? Or do you need “unchecked deallocation”?
It has more safety than he lets on but it only goes so far. They use lots of little, tactical solutions like storage pools, pointer checks, input validation, compiler warnings on weird stuff, etc. If these don’t help your use-case, it comes down to Unchecked_Deallocation dropping your safety down to C’s level. Rust has far stronger safety on dynamic memory than Ada. That’s its biggest win given safe concurrency is old news but dynamic, memory safety tool separation logic. Don’t know about you but I’d rather do Rust than separation logic. ;) However, I am going to push AdaCore to add Rust’s borrow-checker as a default or an option to close the remaining hole.
Now, if you’re program or component is static, then Rust can’t touch Ada due to its trump card: SPARK. That can straight prove the absence of all kinds of errors with an automated prover. Plus conformance of code to behavioral specs you feed it. I posted its results on Lobsters recently. There’s potential here on both sides that can go like this: (a) Ada adds Rust-style safety for dynamic memory & concurrency on top of SPARK to be safest thing in existence; (b) Rust adds SPARK-like capabilities & other static analysis for its “unsafe” code to exceed Ada. These represent imho the highest-impact possibility that exists for either language in terms of safety. I’m rooting for both but hope at least one does this.
If you use the Ada equivalent of a bare malloc, then you need Unchecked_Deallocation to free it. In some classes of applications dynamically allocated memory is simply never freed, which is fine if your allocation patterns are all up-front.
Ada gives you other tools, though; you can create an arena high up on the stack, dynamically allocate into it, and let the allocations free when the arena pops, all in a typesafe manner – the arena is baked into the pointer type, so you can’t reference the object in the arena outside the scope of the arena.
No, and neither does Fortran, or even arguably Forth, but the author doesn’t want over fifty years of compiler development to get in the way of some good shilling.
This sort of shit is why I suspect almost everyone who trumpets who AMAZING!!!!one1 rust is.
I think this is only sort of related to what Steve’s saying, but Rust seems to mostly be pitched to current systems programmers for whom safety is a big, novel thing. But there are many, many non-systems folks for whom safer-than-C++ is just a base expectation and it’s having the capabilities of a systems language that’s new.
Go appears to have pulled more people from the Python/Node/Ruby/PHP/… side than the C++ side. (Pike mentions it in the old “Less is exponentially more” post, and it matches what I see in ‘moving to Go’ blog posts, newbie questions, etc.) By headcount, probably most of us are in the non-systems world. And there, certain aspects of safety are taken for granted–I’m not manually malloc/free‘ing or writing outside the bounds of an array or such in, say, Python. Maybe they’re not as safe as Rust, but they’re safe in some sense.
However, systems programming is novel to Pythonistas, Rubyists, etc., and it’s quite a thing to tell folks like me you can write a miniature OS kernel or unleash the full power of your CPU on a problem or share data among threads, and that you can do it all without many of the headaches that traditionally came with debugging safety issues at runtime.
A frequent dig against Go is that it has no fundamentally new ideas in terms of computer science, but maybe a helpful consequence of that is that folks often talk about Go in terms of its user benefits–even stuff far afield from language design like fast compiles and static binaries–rather than about its big motivating idea.
So is he saying “we’re aiming to take down C++, and if this happens to be nice for people who would never touch C++ then that’s nice, but it’s completely by accident”?
That’s rather troubling as a prospective user. Don’t get me wrong, C++ badly needs to be taken down, but that’s something I can get behind really only in the abstract. It makes me glad that Rust exists and is gaining traction, but it doesn’t make me want to use it myself.
I think “pron” had best comment about that angle. He pointed out the kind of people doing C and C++ that they’re targeting are mostly not on HN and other places Rust is gettimg feedback on. That means the language might get distorted heavily toward the needs of other crowds while missing what would appeal to intended target.
My recommendation was they start hitting all the sites where C/C++ users stay, their conferences, their companies' inboxes, etc. Bring them in as much as possible right now to see what team needs to focus on. Success might even require breaking changes if current methods didnt get enough feedback from these people. Might not. The comments I see in most places are currently heavily skewed toward people who arent doing C or C++ apps every day.
Talking to current C/C++ users is certainly interesting in that they have lots of practical experience shipping systems, and some (who are adaptable about tools and focused on safety, say–think of folks heavily using C++11 features) may start doing stuff in Rust. But another way to change the future of systems programming, maybe easier than getting existing C(++) users to change tools, is to get future systems folks into your camp–folks who are currently doing non-systems work or learning to code. For me that’s where technomancy’s concern comes in; to capture the future of systems work maybe you want to appeal to people who aren’t part of its present.
I agree. The those future developers are going to learn from articles, projects, and experiences of current ones. So, current ones ability to use it effectively will tell us stuff about future ones.
Alternatively, he could be saying “safety is still a problem in memory-safe languages, because there’s more to safety than memory-safety”. As the second last paragraph says:
A few valiant attempts at bringing GC into systems programming (…) still failed to provide a safe concurrency model.
In a safe language, ConcurrentModificationException doesn’t exist. In a safe language, trying to use an already closed file simply doesn’t happen. In a safe language, you can rule out arbitrary violations of your problem domain’s rules, not just memory corruption.
Rust has a much better concurrency story than either OCaml or Haskell. Rust’s type system allowed the standard library’s implementors to define zero-overhead yet safe wrappers around the operating system’s threading and locking mechanisms. Contrast with Haskell’s STM, whose implementation requires heavy runtime support, or Erlang’s model, which makes it difficult to concurrently modify different parts of a shared data structure (and requires heavy runtime support too).
On the other hand, Haskell has a better data parallelism story than Rust. rayon is nice, but Parallel Haskell is just so much more powerful, and greatly benefits from Haskell’s purity.
I don’t think Rust has better concurrency story than Haskell, certainly not “much better”. (Probably better than OCaml though.) For example, Haskell has well optimized lightweight thread, Rust still doesn’t. Rust is better “without runtime”, but if that doesn’t matter Haskell runtime is very scalable and certainly more mature than Rust. By chance IO managers in both languages are called mio, but I think Haskell Mio is in better shape than Rust mio.
At a foundational level, single-machine concurrency control is making multiple threads of execution take turns to manipulate shared resources. (I can’t speak about distributed systems, of which I know nothing.) Fancy runtime system features (e.g. green threads) are built on top of this conceptual foundation, and, in any case, ultimately an implementation detail. When I say Rust has a better concurrency story, I’m talking about the language’s semantics: the borrow checker’s rules directly address an abstract version of the concurrency control problem. Haskell has nothing like this.
Do bare in mind that these are just his opinions and that he stepped back from the project a while ago, so he isn’t presenting any kind of official view of the project. I do think the current core team thinks that Rust should have great ergonomics for people coming from scripting languages and other backgrounds, and not merely C++
Rust the language doesn’t say that OOM must panic. This is a property of Rust’s standard library. You can opt out of those semantics by opting out of the standard library, which is obviously a pain, but not quite that bad. Namely, you can still access libcore, which is a subset of the standard library that doesn’t allocate.
A “global” certainly does not require a macro. A mutable global will require unsafe. You might be thinking of the lazy_static! macro, which essentially provides a safe convenience for running one-time synchronized initialization code to produce a value.
Rust’s standard library is part of Rust. I think it should be simply admitted that Rust made different tradeoff than C here, and in a sense Rust is less safe than C with regard to OOM safety. I happen to think it was the right tradeoff, but it does make Rust less suitable if you want OOM safety.
Handling OOM in a complex application is so difficult that I think it’s more honest to make the default be a panic–that’s what most of the complex C code bases I’ve worked in do, either explicitly or (ugh) by missing enough null checks that they’re going to fault quickly anyway. It is often a better strategy to just panic and let the supervisor start over than to attempt to actually handle every possible OOM in a stable way.
Yes, but it’s important to point out that there’s an opt out strategy available. It definitely won’t make everyone happy of course right now, but it’s there! :-)
Out of curiosity: if you hit an OOM is there much you can reasonably do? It seems like if you had things you could free you would have already freed them?
Big and complex applications often have lots of freeable data floating around for a purpose. Like, look at your browser. It caches tons of stuff for performance. But as soon as that memory is really needed for something, they can just drop it.
Games, mapping applications, image editors, etcetra tend to have lots of data and assets that are worth keeping loaded in memory if there’s enough if it, but can always be reloaded if needed.
Another example would be a chat client that keeps logs. It’s a nice feature, but it’s not cool to crash when an alloc failed because there’s a gigabyte of history.
Some of these applications often self-regulate by imposing some arbitrary limit on the caches, history, etc. But it is more about being nice (not consuming too many resources, for arbitrary definition of too many) and optimistic (“if we only use this much, we probably won’t run out of memory.. on a system that doesn’t run a lot of other stuff”) than it is a solution.
Some applications cache data to the disk to conserve memory. It’s a shame we don’t have good apis for having a little discussion about memory use with the system; it would be nice if we could let applications either allow the system to swap data out for them, or let them voluntarily go of that data to make room for others.
I don’t know why people keep perpetuating the myth that there’s practically nothing you can do about OOM. If anything, I think dealing with memory exhaustion should be more important today than ever before as applications have grown in complexity and often can slurp as much RAM as they are given while people run on them multitasking systems with more than order-of-magnitude differences on the available memory. The said systems are often busy with a multitude of such memory hungry applications simultaneously.
Ideally, if an application that really can’t recover from OOM by freeing stuff of its own, the system could ask the browser and the games and other stuff to make some room, which should amount to the same procedure that they’d run themselves when an alloc of their own fails.
The solutions we use now are pretty spartan and frankly shit. Linux with overcommit grabs the OOM killer and kills random shit, which is absurd, except maybe as a last resort after other, better solutions have been exhausted. Other systems actually let allocs fail, and may employ rlimits to constrain the processes and hopefully leave enough breathing room for everyone. It might or might not help. But it’s hard to make rlimits right when an application can use anywhere between 50 megabytes and a dozen gigabytes, on a system that might have 512MB or 32GB. It helps even less when so many applications are written with the prevailing mentality from GNU/Linux land. What hurts me most is that people keep perpetuating the myth and discouraging people from actually caring about memory exhaustion and recovery.
A “please collect your garbage now” signal (with a size hint?) could be a good start.
It would be nice if we could let applications either allow the system to swap data out for them, or let them voluntarily go of that data to make room for others.
This was the idea behind POSIX_FADV_VOLATILE. Alas, as far as I know it went nowhere.
Does Ada really belong in that list? AIUI it was intended to be used with a much higher-level concurrency model than native threads and thus didn’t suffer that pitfall.
The only thing inaccurate about it is the concurrency claim. There were safe, concurrency models before Rust. They included Concurrent Pascal (used in Solo OS), Ada Ravenscar (deployed widely), and Eiffel’s SCOOP (deployed widely). The truth would be Rust is doing better by making one that combines more expressiveness, dynamic memory, and no GC in one. Ada side isn’t sitting on their hands, though.
http://www.embedded.com/design/programming-languages-and-tools/4375616/ParaSail--Less-is-more-with-multicore
I think it is reference to existence of Ada.Unchecked_Deallocation, and the fact that it is, well, unchecked.
Explicitly unsafe features are not usually regarded as violating safety. I mean, Rust has
unsafe
.Note that Graydon’s claim includes “manual memory management.” I don’t know Ada, but can you use it safely without a GC? Or do you need “unchecked deallocation”?
It has more safety than he lets on but it only goes so far. They use lots of little, tactical solutions like storage pools, pointer checks, input validation, compiler warnings on weird stuff, etc. If these don’t help your use-case, it comes down to Unchecked_Deallocation dropping your safety down to C’s level. Rust has far stronger safety on dynamic memory than Ada. That’s its biggest win given safe concurrency is old news but dynamic, memory safety tool separation logic. Don’t know about you but I’d rather do Rust than separation logic. ;) However, I am going to push AdaCore to add Rust’s borrow-checker as a default or an option to close the remaining hole.
Now, if you’re program or component is static, then Rust can’t touch Ada due to its trump card: SPARK. That can straight prove the absence of all kinds of errors with an automated prover. Plus conformance of code to behavioral specs you feed it. I posted its results on Lobsters recently. There’s potential here on both sides that can go like this: (a) Ada adds Rust-style safety for dynamic memory & concurrency on top of SPARK to be safest thing in existence; (b) Rust adds SPARK-like capabilities & other static analysis for its “unsafe” code to exceed Ada. These represent imho the highest-impact possibility that exists for either language in terms of safety. I’m rooting for both but hope at least one does this.
If you use the Ada equivalent of a bare malloc, then you need Unchecked_Deallocation to free it. In some classes of applications dynamically allocated memory is simply never freed, which is fine if your allocation patterns are all up-front.
Ada gives you other tools, though; you can create an arena high up on the stack, dynamically allocate into it, and let the allocations free when the arena pops, all in a typesafe manner – the arena is baked into the pointer type, so you can’t reference the object in the arena outside the scope of the arena.
No, and neither does Fortran, or even arguably Forth, but the author doesn’t want over fifty years of compiler development to get in the way of some good shilling.
This sort of shit is why I suspect almost everyone who trumpets who AMAZING!!!!one1 rust is.
I think this is only sort of related to what Steve’s saying, but Rust seems to mostly be pitched to current systems programmers for whom safety is a big, novel thing. But there are many, many non-systems folks for whom safer-than-C++ is just a base expectation and it’s having the capabilities of a systems language that’s new.
Go appears to have pulled more people from the Python/Node/Ruby/PHP/… side than the C++ side. (Pike mentions it in the old “Less is exponentially more” post, and it matches what I see in ‘moving to Go’ blog posts, newbie questions, etc.) By headcount, probably most of us are in the non-systems world. And there, certain aspects of safety are taken for granted–I’m not manually
malloc
/free
‘ing or writing outside the bounds of an array or such in, say, Python. Maybe they’re not as safe as Rust, but they’re safe in some sense.However, systems programming is novel to Pythonistas, Rubyists, etc., and it’s quite a thing to tell folks like me you can write a miniature OS kernel or unleash the full power of your CPU on a problem or share data among threads, and that you can do it all without many of the headaches that traditionally came with debugging safety issues at runtime.
A frequent dig against Go is that it has no fundamentally new ideas in terms of computer science, but maybe a helpful consequence of that is that folks often talk about Go in terms of its user benefits–even stuff far afield from language design like fast compiles and static binaries–rather than about its big motivating idea.
So is he saying “we’re aiming to take down C++, and if this happens to be nice for people who would never touch C++ then that’s nice, but it’s completely by accident”?
That’s rather troubling as a prospective user. Don’t get me wrong, C++ badly needs to be taken down, but that’s something I can get behind really only in the abstract. It makes me glad that Rust exists and is gaining traction, but it doesn’t make me want to use it myself.
I think “pron” had best comment about that angle. He pointed out the kind of people doing C and C++ that they’re targeting are mostly not on HN and other places Rust is gettimg feedback on. That means the language might get distorted heavily toward the needs of other crowds while missing what would appeal to intended target.
My recommendation was they start hitting all the sites where C/C++ users stay, their conferences, their companies' inboxes, etc. Bring them in as much as possible right now to see what team needs to focus on. Success might even require breaking changes if current methods didnt get enough feedback from these people. Might not. The comments I see in most places are currently heavily skewed toward people who arent doing C or C++ apps every day.
Talking to current C/C++ users is certainly interesting in that they have lots of practical experience shipping systems, and some (who are adaptable about tools and focused on safety, say–think of folks heavily using C++11 features) may start doing stuff in Rust. But another way to change the future of systems programming, maybe easier than getting existing C(++) users to change tools, is to get future systems folks into your camp–folks who are currently doing non-systems work or learning to code. For me that’s where technomancy’s concern comes in; to capture the future of systems work maybe you want to appeal to people who aren’t part of its present.
I agree. The those future developers are going to learn from articles, projects, and experiences of current ones. So, current ones ability to use it effectively will tell us stuff about future ones.
Alternatively, he could be saying “safety is still a problem in memory-safe languages, because there’s more to safety than memory-safety”. As the second last paragraph says:
In a safe language,
ConcurrentModificationException
doesn’t exist. In a safe language, trying to use an already closed file simply doesn’t happen. In a safe language, you can rule out arbitrary violations of your problem domain’s rules, not just memory corruption.Isn’t that fine? I’d think that most applications programmers shouldn’t be touching Rust - we’re already well-served by OCaml/Haskell/…
Rust has a much better concurrency story than either OCaml or Haskell. Rust’s type system allowed the standard library’s implementors to define zero-overhead yet safe wrappers around the operating system’s threading and locking mechanisms. Contrast with Haskell’s STM, whose implementation requires heavy runtime support, or Erlang’s model, which makes it difficult to concurrently modify different parts of a shared data structure (and requires heavy runtime support too).
On the other hand, Haskell has a better data parallelism story than Rust. rayon is nice, but Parallel Haskell is just so much more powerful, and greatly benefits from Haskell’s purity.
I don’t think Rust has better concurrency story than Haskell, certainly not “much better”. (Probably better than OCaml though.) For example, Haskell has well optimized lightweight thread, Rust still doesn’t. Rust is better “without runtime”, but if that doesn’t matter Haskell runtime is very scalable and certainly more mature than Rust. By chance IO managers in both languages are called mio, but I think Haskell Mio is in better shape than Rust mio.
At a foundational level, single-machine concurrency control is making multiple threads of execution take turns to manipulate shared resources. (I can’t speak about distributed systems, of which I know nothing.) Fancy runtime system features (e.g. green threads) are built on top of this conceptual foundation, and, in any case, ultimately an implementation detail. When I say Rust has a better concurrency story, I’m talking about the language’s semantics: the borrow checker’s rules directly address an abstract version of the concurrency control problem. Haskell has nothing like this.
Do bare in mind that these are just his opinions and that he stepped back from the project a while ago, so he isn’t presenting any kind of official view of the project. I do think the current core team thinks that Rust should have great ergonomics for people coming from scripting languages and other backgrounds, and not merely C++
[Comment removed by author]
Which, ironically, is the place it most falls short. Bust-on-OOM and macro-hell just for a global, doesn’t make for a good “systems space” language.
To clarify:
libcore
, which is a subset of the standard library that doesn’t allocate.unsafe
. You might be thinking of thelazy_static!
macro, which essentially provides a safe convenience for running one-time synchronized initialization code to produce a value.Rust’s standard library is part of Rust. I think it should be simply admitted that Rust made different tradeoff than C here, and in a sense Rust is less safe than C with regard to OOM safety. I happen to think it was the right tradeoff, but it does make Rust less suitable if you want OOM safety.
Handling OOM in a complex application is so difficult that I think it’s more honest to make the default be a panic–that’s what most of the complex C code bases I’ve worked in do, either explicitly or (ugh) by missing enough null checks that they’re going to fault quickly anyway. It is often a better strategy to just panic and let the supervisor start over than to attempt to actually handle every possible OOM in a stable way.
Yes, but it’s important to point out that there’s an opt out strategy available. It definitely won’t make everyone happy of course right now, but it’s there! :-)
Out of curiosity: if you hit an OOM is there much you can reasonably do? It seems like if you had things you could free you would have already freed them?
Big and complex applications often have lots of freeable data floating around for a purpose. Like, look at your browser. It caches tons of stuff for performance. But as soon as that memory is really needed for something, they can just drop it.
Games, mapping applications, image editors, etcetra tend to have lots of data and assets that are worth keeping loaded in memory if there’s enough if it, but can always be reloaded if needed.
Another example would be a chat client that keeps logs. It’s a nice feature, but it’s not cool to crash when an alloc failed because there’s a gigabyte of history.
Some of these applications often self-regulate by imposing some arbitrary limit on the caches, history, etc. But it is more about being nice (not consuming too many resources, for arbitrary definition of too many) and optimistic (“if we only use this much, we probably won’t run out of memory.. on a system that doesn’t run a lot of other stuff”) than it is a solution.
Some applications cache data to the disk to conserve memory. It’s a shame we don’t have good apis for having a little discussion about memory use with the system; it would be nice if we could let applications either allow the system to swap data out for them, or let them voluntarily go of that data to make room for others.
I don’t know why people keep perpetuating the myth that there’s practically nothing you can do about OOM. If anything, I think dealing with memory exhaustion should be more important today than ever before as applications have grown in complexity and often can slurp as much RAM as they are given while people run on them multitasking systems with more than order-of-magnitude differences on the available memory. The said systems are often busy with a multitude of such memory hungry applications simultaneously.
Ideally, if an application that really can’t recover from OOM by freeing stuff of its own, the system could ask the browser and the games and other stuff to make some room, which should amount to the same procedure that they’d run themselves when an alloc of their own fails.
The solutions we use now are pretty spartan and frankly shit. Linux with overcommit grabs the OOM killer and kills random shit, which is absurd, except maybe as a last resort after other, better solutions have been exhausted. Other systems actually let allocs fail, and may employ rlimits to constrain the processes and hopefully leave enough breathing room for everyone. It might or might not help. But it’s hard to make rlimits right when an application can use anywhere between 50 megabytes and a dozen gigabytes, on a system that might have 512MB or 32GB. It helps even less when so many applications are written with the prevailing mentality from GNU/Linux land. What hurts me most is that people keep perpetuating the myth and discouraging people from actually caring about memory exhaustion and recovery.
A “please collect your garbage now” signal (with a size hint?) could be a good start.
This was the idea behind
POSIX_FADV_VOLATILE
. Alas, as far as I know it went nowhere.