Send + Sync are great. The downside of concurrency in Rust is:
1) There isn't transparent integration with IO in the runtime as in Go or Haskell. Rust probably won't ever do this because although such a model scales well in general, it does create overhead and a runtime.
2) OS threads are difficult to work with compared to a nice M:N threading abstraction (which again are the default in Go or Haskell). OS threads leads to lowest common denominator APIs (there is no way to kill a thread in Rust) and some difficulty in reasoning about performance implications. I am attempting to solve this aspect by using the mioco library, although due to point #1 IO is going to be a little awkward.
> 1) There isn't transparent integration with IO in the runtime as in Go or Haskell. Rust probably won't ever do this because although such a model scales well in general, it does create overhead and a runtime.
By "transparent integration with the runtime" you mean M:N threading. M:N threading is just delegating work to userspace that the kernel is already doing. There can be valid reasons for doing it, but M:N threading isn't us not doing the work that we could have done. In fact, we had M:N threading for a long time and went to great pains to remove it.
In addition to the downsides you mentioned, M:N threading interacts poorly with C libraries, and stack allocation becomes a major problem without a precise GC to be able to relocate stacks with.
M:N will never be as fast as an optimized async/await implementation can be, anyway. There is no way to reach nginx levels of performance with stackful coroutines.
> OS threads leads to lowest common denominator APIs (there is no way to kill a thread in Rust)
This has nothing to do with the reason why you can't kill threads in Rust. We could expose pthread_kill()/pthread_cancel() on Unix and TerminateThread() on Windows if we wanted to. The reason why you can't terminate threads that way is that there's no good reason to: if you have any locks anywhere then it's an unsafe operation.
> some difficulty in reasoning about performance implications.
I would actually expect the opposite to be true: 1:1 is easier to reason about in performance, because there are fewer magic runtime features like moving or segmented stacks involved. Could you elaborate?
Will kill a process. It has an isolated heap, so it won't affect other (possibly hundreds of thousands of) running processes. That memory will be garbage collected, safely and efficiently.
This will also work in Elixir, LFE and other languages running on the BEAM VM platform.
EDIT: masklinn user below pointed out correctly, the example is exit/2, that is exit(Pid,kill). In fact it is just exit(Pid, Reason), where Reason can be other exit reason, like say my_socket_failed. However in that case the process could catch it and handle that signal instead of being un-conditionally killed.
> This is called from within a threads execution, right? I think the question is about being able to kill a thread externally.
Yes the GP has the wrong arity, exit/1 terminates the current process but exit/2[0] will send an exit signal to an other process and possibly cause them to terminate (depending on the provided Reason and whether they trap exits).
This is a normal behaviour of Erlang which enables things like seamless supervision trees, where exit signals propagate through the supervision trees reaping all of the processes until they encounter a process trapping exits and a supervisor can freely terminate its children[1]
This can work because erlang doesn't have shared state[2][3], and BEAM implements termination signaling (so processes can be made aware by the VM of the termination of other processes)
It works correctly either way -- externally with exit(Pid,kill) or by the process itself as exit(kill). The last one is just a shorthand for exit(self(), kill). Where self() is the process id of the currently running process.
But the way you showed was not one in which anyone was interested, synchronous exceptions work in more or less every language, and you can't assume readers know your self-killing is actually implemented via asynchronous exceptions since they don't know the language.
Haskell has killThread, which rather than being an anti-pattern is often used as an effective way to accurately enforce a timeout on a thread. This functionality seems like it would be very difficult to achieve with most other runtimes.
https://news.ycombinator.com/item?id=11370004
Yes. In Haskell you use `killThread` which throws an asynchronous exception to the thread. It is certainly difficult to perfectly cleanup resources in the face of asynchronous exceptions. However, once there are functions available to help you with this (e.g. use a bracket function whenever using resources) it becomes tractable.
This functionality is critical to being able to timeout a thread.
Yes, there is still an issue with async exceptions when there is an exception during the cleanup handler of bracket. Probably this has not received the attention it deserves because fundamentally if a cleanup handler throws an exception you may well still have resource issues. But the article also proposes ways of solving this issue, so lets not give up on async exceptions.
Note that this isn't exactly a safe operation, since a killed thread may stop in the midst of something. It's safer to have it process messages on a loop and include a quit message.
Can you clarify what you mean by "kill goroutines?" Because my understanding was if you return while inside a goroutine it get's handled by the GC immediately, and (as someone else mentioned) you can use context to send deadlines/cancellation signals to go routines.
The ability to kill an arbitrary goroutine from the outside. To use context you need to write your specific goroutine such that it checks for cancellation and will eventually handle a cancellation request. This cannot be done with an arbitrary goroutine.
That's not a way to "kill goroutines". That's a way to "ask goroutines to die when they get around to it." Useful, but a fundamentally different thing. Go does not have a way to kill goroutines, nor, per some of the other discussion in this thread, do I ever expect it to.
Think about the interaction with (non-memory) resource ownership. This is just horrible, and I wouldn't even want it in a higher-level language. If you want to carefully notify threads that they must terminate, set up a channel, or write to a shared variable, but please do not just forcibly terminate threads.
Let me interpret the situation in managed languages from a Rust programmer's lens: All managed resources are actually owned by the runtime, and merely borrowed by your program. Thus, killing threads is “safe”: no managed resource can possibly become orphaned. The result is very pleasant as long as your program only uses runtime-managed resources. But things quickly become hairy when you want to use foreign libraries (typically written in C, or exposing C-compatible interfaces), because it's very difficult to arrange things so that cleanup routines are guaranteed to be called before your thread is killed.
> Would seem silly to use rust for all it's safety features to then call into c libraries, anyhow.
Why? One very important use case for Rust's unsafe sublanguage is making wrappers around C libraries that can be used in the safe sublanguage. If anything, using C libraries is more pleasant in Rust (or C++) than in managed languages, because you don't particularly need to accomodate or work around the idiosyncracies and quirks of a complex runtime system. That's pretty much the entire point of my above post (GP to this one).
> So much for Heartbleed wouldn't happen with rust.
You may not agree, but the position consistently taken by Rust is that, while avoiding unsafe code is highly desirable, it isn't always possible. What is always possible is to isolate unsafe code, so that, if the safety guarantees of the safe sublanguage are ever violated, you know what parts of your program to audit.
I think Steve Klabnik could clarify this, but the book at that link is in the process of being rewritten. I think it might be good to wait until it is. I personally found it slightly difficult to follow compared to other options like the soon to be published Programming Rust.
I am in the middle of working on a second draft of the book. This page is one of the oldest bits of docs, overall, and isn't my best work. It's not _wrong_, I just have very high personal standards. It was adapted from older documentation and was written in the time up to 1.0, where I had a LOT on my plate.
It certainly is _confusing_ if not _wrong_. You silently add "move" to the closure without any mention or explanation (then later say "note that we're copying i" without explaining that you're talking about the "move" keyword).
The bit about Mutex also has a "just type this to fix the problem with no explanation of how or why it works" flavour (although I guess if you already grok mutexes then its use here might be obvious to you).
Not a criticism of the tutorial, but it does something common in Rust tutorials which is really a problem with the language at this point: Rust tutorials always spend a lot of time interpreting Rust's notoriously poor error messages (e.g. "what this message that doesn't mention Sync is trying to tell you is that you need Sync on this type"). That's great when you're doing the tutorial, but as soon as you're on your own man are those errors frustrating.
I've actually heard the opposite feedback; Rust's error messages try to be super helpful.
Note that concepts like ownership and Sync are new to most programmers, and it's impossible to explain them in an error message. This is where the extended error messages (via --explain) and the book come in.
The tutorial has this style because Rust espouses catching things at compile time, so the tutorial demonstrates this being done by doing the wrong thing a few times and reiterating why it gave an error.
----
Could you give examples of confusing error messages? I'd love to improve them. The one you mention .... doesn't exist. This (http://is.gd/keukPm) is what that error message looks like, and (a) it mentions Sync, (b) it also mentions that `Arc<bool>` cannot be shared between threads safely.
If you were referring to "So, we need some type that lets us have more than one reference to a value and that we can share between threads, that is it must implement Sync." from the book, the last part about Sync has nothing to do with that error message. If you follow the book, the error message is clear without the context of threads -- `data` was moved into the first spawn() call and the subsequent ones can't use it. One does not conclude that Sync is necessary from this error message, and that's not what the book is trying to say.
This sentence is actually skipping a step, one like http://is.gd/RPNOm4, where the compiler asks you for a Send type (or a Sync type, depending on the exact code). Instead of stepping through this example, it just introduces Sync directly by noting that we're dealing with threads anyway and the reader already knows what Sync/Send are. It doesn't conclude that Sync is necessary from the error message.
> The bit about Mutex also has a "just type this to fix the problem with no explanation of how or why it works" flavour (although I guess if you already grok mutexes then its use here might be obvious to you).
The previous sentence says "for example a type that can ensure only one thread at a time is able to mutate the value inside it at any one time.", which is exactly what a mutex does. Unless you want a lower level explanation which IMO isn't necessary. It could explain locking more though; I'll fix that.
I've found the Rust compiler to provide very useful messages. That said, it does take a certain amount of experience in the language before you grok the terminology and appropriate remedies to overcome some compiler errors. As a beginner, I'd typically open about 5-10 different documentation, blog post, SO and tutorial pages to try to figure out what I was doing wrong. Once I understood the underlying concept, I could go back and understand what the compiler message was telling me, as plain as day. After awhile, I had a strong enough understanding of the borrow checker that I could usually interpret the compiler error messages in a pretty straightforward fashion. But it takes time if you're coming from a non C/C++ language!
If you're learning Rust, I would recommend tracing back every compiler error you encounter to this page [1] to see some other examples and resolutions (if the compiler's suggestions don't make it clear as to what to do). It's the most underrated page in the entire Rust documentation set, and could be the launch point for a lot of teaching and learning. Read the book to start, certainly (and the revisions Steve Klabnik has been making are very, very good), but that error index page is very valuable for the day to day issues.
I love the extended errors. Note that you can just use `rustc --explain <error code>` without opening a web page, but of course it won't be formatted then.
> But it takes time if you're coming from a non C/C++ language!
Ultimately there's little substitute to learning the concepts behind the language :) Error-driven-development is fun, and quite useful once you know the language, but if used as the only way to learn the language it can be problematic. That said, I enjoy using errors to explain Rust concepts -- as long as you explain them. If a new user is hit with a steaming pile of errors you can't really expect them to understand it directly.
I have talked with a lot of folks coming from a non-systemsy background, and sort of have an idea of the things that get confusing (I once had similar problems when learning C++). I'm planning a blog post series (no ETA, pretty swamped with other work :/ ) that teaches both Rust (specifically, the safety bits like the borrow checker) and what's going on under the hood (regarding memory, the stack/heap, etc) using each as a crutch to explain the other in a leapfrog way, to introduce the language and low-level programming to folks coming from languages like JS.
Yeah, this is what I mean. So this was originally written before "move" or even any of the current closure implementation existed. And with so much to do, the focus was on making what existed accurate more than a holistic approach.
Furthermore, almost a year after 1.0, we have a lot better understanding of what people struggle with when trying to learn Rust, so we have a better idea of how to teach it.
Please file bugs for notoriously poor errors. We have put a lot of work into many of them, but there's a lot of ways to go. It seems like most people really like them or really hate them.
Rust tutorials always spend a lot of time interpreting
Rust's notoriously poor error messages
I'm wondering where you got the sense that Rust's error messages are "notoriously poor." Can you expand on that?
I have always found Rust's error messages to be quite good. Of course, they can be hard to interpret occasionally if you don't already grasp the concepts they are referring to. But I don't see any way to solve that in error messages; at some point, you have to learn enough about the language for the error messages to make sense. And occasionally, the suggestion they provide for how to fix the issue isn't the right suggestion, but I don't know if there's a way to always do that without strong AI that understands what you mean. The best you could hope to do is to provide suggestions for all of the possible fixes, though that could lead to some messages that are too verbose to the point of being useless.
I think it would help to provide some examples of error messages you have had trouble with, to help figure out ways to make them better.
Another thing to know about rust concurrency is that it supports safe "scoped" threads, or threads which have plain references to their parent threads stack.
This makes it very easy to write, for instance, a concurrent in-place quicksort (this example uses the scoped-pool crate, which provides a thread pool supporting scoped threads):
extern crate scoped_pool; // scoped threads
extern crate itertools; // generic in-place partition
extern crate rand; // for choosing a random pivot
use rand::Rng;
use scoped_pool::{Pool, Scope};
pub fn quicksort<T: Send + Sync + Ord>(pool: &Pool, data: &mut [T]) {
pool.scoped(move |scoped| do_quicksort(scoped, data))
}
fn do_quicksort<'a, T: Send + Sync + Ord>(scope: &Scope<'a>, data: &'a mut [T]) {
scope.recurse(move |scope| {
if data.len() > 1 {
// Choose a random pivot.
let mut rng = rand::thread_rng();
let len = data.len();
let pivot_index = rng.gen_range(0, len); // Choose a random pivot
// Swap the pivot to the end.
data.swap(pivot_index, len - 1);
let split = {
// Retrieve the pivot.
let mut iter = data.into_iter();
let pivot = iter.next_back().unwrap();
// In-place partition the array.
itertools::partition(iter, |val| &*val <= &pivot)
};
// Swap the pivot back in at the split point by putting
// the element currently there are at the end of the slice.
data.swap(split, len - 1);
// Sort both halves (in-place!).
let (left, right) = data.split_at_mut(split);
do_quicksort(scope, left);
do_quicksort(scope, &mut right[1..]);
}
})
}
In this example, quicksort will block until the array is fully sorted, then return.
for (let i=0;i<5;i++) {
/* `let`-scoped `i`s are created individually at each iteration, so it is safe to capture them by references. */
}
Rust supports this pattern by a built-in syntax. That's what `move` means. If you prepend the `move` keyword before the closure, the variables will be captured by values, not references.
The ES6 "fix" is an abomination in my opinion. You're closing over a mutable variable, so you should see the mutations! IMO the real fix would be to write
for (var i = 0; i < 5; i++) {
let i_ = i;
/* use i_ in the closure */
}
In your ES6 solution if you e.g. increment "i" inside the loop after the closure the closure will see the mutation!
The real cause of confusion is mutation and javascript's scoping rules.
If you write `let data = data;` in the closure, then you can achieve the effect of `move` without the special syntax (unless `data` can be copied, in which case there is really no way to force it to be moved inside the closure without `move`).
However, in Rust, you get an error if you accidentally capture by reference in the closure passed to `thread::spawn` so it's not as hard to get right as it is in JS.
Thanks for the explanation, but how would the move keyword know that you're referring to the i variable? Ie - what is there were multiple variables (such as a for loop with j in there)?
Wouldn't it have been a better approach to add some sort of demarkation, such as i* or i^ (or whatever) to indicate this?
> Wouldn't it have been a better approach to add some sort of demarkation, such as i* or i^ (or whatever) to indicate this?
That's the path C++ took[0], the Rust people thought it had too much syntactic and semantic overhead, and that having just "move" and "referring" closures would be much simpler. If you want to mix them up, it's easy enough to create references outside the closure (and capture them by value with a move closure)
A closure captures all variables from its environment that it is using (no more than that). They can be captured by reference (which is the default), or by-move (which is done with the `move` keyword). In case it is being captured by move, _all_ captured variables will be moved. If you wish to capture a specific variable by reference in a move closure, create a reference (`let y = &x`) outside of the closure and use the reference y instead of x inside.
In short, the "move everything" nature of "move" keyword here is not an issue because you can just make references for items you don't want moved. References themselves are "moved" by just copying their addresses.
It's more accurate to say that it takes ownership of the captured environment. Whether that means copying the values or moving ownership depends on whether the values are of a type that implements Copy
Move and Copy are identical at the assembly level. The only difference is what you can do with the older binding. Semantically speaking, both cause a memcpy, though the optimizer may elide them.
That said, you're right that saying "copy" is misleading, for this reason. But moves _are_ a kind of copy.
Does Rust have a way to work with SIMD concurrency as opposed to just fork/join concurrency? Something along the lines of how openmp or cilk let you do a parallel for all?
You seem to have concurrency confused with parallelism. Concurrency just means working on another task before the prior one has completed. You're describing parallelism which is doing multiple tasks at the same time.
By SIMD concurrency, I meant that I was curious about a construct that actually translated into simultaneously executing code, not just a construct that denotes work that "could" be done simultaneously.
Rust supports SIMD, with some utility structs that let you do it easily. I don't know of any libraries that auto-simd things though IIRC LLVM can do this on its own sometimes.
I think the parent was referring to "lightweight task"-based concurrency like C#'s PLinq:
// C#
int sum;
Parallel.ForEach(myCollection, item => sum += item);
Which operates using a reasonably sized thread pool rather than a thread per item. Something similar in Rust (Excuse my rusty rust) would look like.
let someList = ...
parallel::for(someList.iter(), |item| {
// Do thing with each item
};
I think there is ongoing work on this topic, but it will likely only be library-level and not language-level (Just like Linq is a language level feature in C# but PLinq is a library). There are some third party crates that do this like simple_parallel.
Edit: Low-level simd exist as intrinsics and of course through llvm vectorizations.
The simplest library for this kind of data parallelism is Rayon[1]. The README provides a nice overview and links to a series of blog posts about the details.
A simple google search would have told you that there is work being done the introduce simd in Rust. There appear to be some basics there right now but not much if i understood my quick search.
An active discussion about concurrency in rust with first developers commenting is a very appropriate place to ask that question, and ultimately much more likely to yield valid and current information than Google.
This looks terribly overcomplicated/overengineered to me,
to the point where I doubt many are going to adopt/switch to this style, esp when used to more convenient approaches [even the standard C++ approach, faulty as it may be].
Also note how much boilerplate one has to write and how the code snippets bypass error handling (do it differently in "real" code but don't show us how). Bleh.
This prevents boiler plate issues, and allows the compiler to help you discover threading issues at compile time rather than runtime.
It's easy enough to just mark all you structs send+sync and still shoot your foot off just like in any language. The point is, you need to be explicit that your trying to shoot your foot off, as opposed to other languages which basically pull the trigger for you.
You don't have to worry about most of this. Doing concurrent things in Rust is pretty clean. Designing new concurrent abstractions from scratch is where you need to worry about Send and Sync and be careful. And it's totally worth it, entire classes of concurrency errors just go away.
The error handling can get verbose, though with the new `?` operator and `catch` syntax it's much cleaner now.
I don't think there's a data race there, but the compiler can't check that. What the compiler sees is more than one thread accessing the variable `data`, which could cause a data race.
A failed malloc aborts the process. If this is important to you, don't use the heap abstractions in the stdlib then. This is no different from the situation in C++.
(You can also plug in a custom allocator which behaves differently)
Overcommit doesn't mean that malloc never fails. malloc will fail if you ask for an allocation that won't fit in the virtual address space (for instance because of your rlimit settings, or if you asked for a 3G chunk on a 32-bit system).
A lot of folks are pointing out that malloc can fail, which is true, but the important part is that there are situations where your application will just abort randomly in the middle of nowhere (i.e. not during malloc) and there's nothing you can do about it. There are also situations where malloc fails and returns a null, but given the existence of situations with no errors on a malloc, handling the error in these cases isn't close to a solution. No language or stdlib can solve this problem 100%.
At a baremetal level, it helps having this, but you're probably better off not using the stdlib anyway.
Bullshit. Every part of the C++ standard library (which is _much_ bigger than Rust's) can gracefully propagate resource exhaustion errors, including memory allocation failure, to callers. That Rust can't is a design flaw.
> there are situations where your application will just abort randomly
That's sure as hell not the case in any environment I choose to use.
> can gracefully propagate resource exhaustion errors, including memory allocation failure, to callers
only on malloc. If the kernel overcommits, your process will abort when you try to use the memory, possibly way after the malloc and there's nothing you can do about it. That's the point being made here.
> That Rust can't is a design flaw.
(This is false, see Steve's reply above about this)
Not true. For example, it can fail if there is no big enough chunk of virtual address space available (i.e. your heap is fragmented enough and your attempted allocation is big enough). I've even seen 64-bit processes manage to do this, my mmapping lots of multi-GB things at once and then trying to do large allocations.
Overcommit will fail if you go over the overcommit ratio. In addition, the process will die if you start using the fake pages the kernel gave you. But yes, malloc can fail on Linux.
You're correct in the way that it's default behavior, but the behavior can be turned off.
Also, if you're using cgroups (or anything that leverages that like containers) you can put a limit on the memory resource and then malloc will fail. Which is common enough, and only becoming more common as people are collocating a lot of disparate workloads on nodes (using the kurbenets scheduler).
No custom allocator can make a task that fails to allocate gracefully report an error. Rust's error handling design is just terrible, and mostly a consequence of eschewing exceptions.
Had Rust opted for exceptions, it'd be a much better, and actually usable, language. Rust's terribly error-handling strategy is the chief reason not to use it.
>
No custom allocator can make a task that fails to allocate gracefully report an error.
Yes, you can. You can panic the thread, and you can recover from panics. But honestly this is never enough to gracefully recover from OOM. I can't think of any software that uses exceptions to gracefully recover from OOM (i.e. without crashing the process) in a way that works. How many Java applications do you see that catch OutOfMemoryError on a fine-grained level?
> Had Rust opted for exceptions, it'd be a much better, and actually usable, language. Rust's terribly error-handling strategy is the chief reason not to use it.
It's hyperbolic to call Rust's error handling strategy not "actually usable". I use it every day and never have any issues with it.
I believe you that you do. That doesn't change the fact that few people actually do it or need to do it. There are many more people who think they need to recover from OOM but actually don't and would be better off if they didn't try. (There have been many security vulnerabilities that have resulted from attempting to handle OOM gracefully that wouldn't have been an issue if malloc just aborted the process.)
> and mostly a consequence of eschewing exceptions.
A panic is the same thing as an exception. If you want to catch a panic, use recover(), it's meant to be used exactly for these end-of-the-world panic scenarios (and for FFI/etc).
You can plug in a custom allocator that panics on OOM (I think the standard one aborts).
As Steve mentioned, custom allocators can mean two things. The type that exists in rust today is one where you can make OOM panic, but not have allocation methods return Result. A planned extension will let you have allocators with different semantics entirely work with stdlib types (via defaulted type parameters); and this will let you use regular error handling with stdlib types too.
> A planned extension will let you have allocators with different semantics entirely work with stdlib types
And what about all the code that doesn't? It's because so much code exists that's completely oblivious to the possibility of these stdlib functions failing that I don't think that merely adding the option to do the right thing is good enough. The existing failure-oblivious APIs need to be explicitly deprecated.
The only ways to redeem Rust is to either support exceptions as first-class citizens with mandatory runtime support or to convert all existing allocating stdlib functions to return Result and mark all the existing failure-oblivious ones as being as deprecated as gets(3) in C.
> The existing failure-oblivious APIs need to be explicitly deprecated.
That's total overkill. For 99% of applications, process abort is fine, and dealing with it is just noise. Those 1% are usually things like kernels that use custom standard libraries anyway.
We're not doing the 99% a favor by making them think about OOM every time they do something that might allocate.
> The only ways to redeem Rust is to either support exceptions as first-class citizens with mandatory runtime support or to convert all existing allocating stdlib functions to return Result and mark all the existing failure-oblivious ones as being as deprecated as gets(3) in C.
This is silly hyperbole. Ask anyone who works in security whether the danger of xmalloc() is comparable to the danger of gets(). In fact, I've seen many security folks recommend only using xmalloc() with process abort instead of trying to explicitly handle OOM failures!
Because the Rust people don't believe in making "catch" a first-class primitive in the language, and in fact, fully support a runtime option to turn all panics into aborts.
Even if abort-on-panic were to be killed as a legal mode of operation, and even if the stigma were to be removed from std::panic::recover, we'd still be left with a language with two error handling strategies and endless programmer confusion over which to use.
Rust's designers have done permanent damage to the language by not making exceptions the primary error reporting mechanism available to programmers, and it's not a mistake they can undo now.
> Because the Rust people don't believe in making "catch" a first-class primitive in the language, and in fact, fully support a runtime option to turn all panics into aborts.
recover() exists. You're right, there's a stigma to it, because you're not supposed to use it unless you really need to (hence, no programmer confusion). It's supposed to be used for situations like:
- Catching panics before crossing an FFI boundary
- End-of-the-world situations like OOM where you want to still handle it somehow
- Ensuring that applications can recover from internal panics in libraries (though there should be little to no panics in the libraries anyway)
The stigma for recover is for using it where you're not supposed to; as a substitute for regular error handling. In this situation, you are supposed to, so the stigma doesn't apply.
The fact that it's not a first-class primitive seems mostly irrelevant to me. Rust does a lot of things in library functions and types, even our concurrency safety mechanisms are something that can be duplicated in a library. As long as it can be used, what does it matter?
The fact that you can set the panic handler at runtime is also irrelevant. If you want to catch panics, don't do that.
Exceptions can be turned into aborts in C++ as well, and are in many types of programs, because exceptions do have downsides for some problem domains. If Rust forced exceptions on everyone, there'd be people complaining about that just like you're complaining now.
I see the split between `Result` and `panic!` as more like Java's split between checked and runtime exceptions, except `Result` is much more usable than checked exceptions because it's part of the main data flow path, and so can use method chaining combinators instead of unwieldy try/catch blocks. OOM in Java is, like in Rust, not a checked exception, because it's not something you'd want to handle everywhere it can happen, but rather something to propagate up the stack transparently.
This is an area where you just can't actually please everyone. I have heard the same opinions you've expressed in this thread, just as strongly, for even including unwinding at all. That aborts should be the only option, and that the cost of unwinding is far too high to be included in a true systems language.
Language design is tough. I'm glad we have multiple languages.
There is an exception-like mechanism in Rust, in the form of the "try!" macro. It's a lot more flexible, but somewhat more verbose (Haskell has the same mechanism in a way that looks a lot more like exceptions, so that's not an inherent flaw). The best explanation I've seen is this:
tl;dr: "Result"s are like exceptions which are caught by default. You can (explicitly) propagate them upwards by using try!(...). This is nice because it means that you can tell what exceptions can occur in a block of code only using "local" information.
It makes the case that you do in fact want two different error handling mechanisms, because there are two quite different kinds of errors. The author argues that running out of memory is most practically treated as an unrecoverable error which aborts the process.
That's not a property of rust the language, but the standard library. I believe you could use other allocators that behave differently. In any case, the standard library panics on OOM, and panics are described at the bottom of the linked page.
Malloc never fails, but you might die if you touch the memory. In general, modern OSes don't have a good story about exhausting available memory beyond "let's kill a bunch of processes to free up memory".
malloc can fail, even on default linux (overcommit enabled), if you go above the process's vmem limit for instance (because 32b or rlimited). And of course not all OS overcommit, Windows famously does not.
1) There isn't transparent integration with IO in the runtime as in Go or Haskell. Rust probably won't ever do this because although such a model scales well in general, it does create overhead and a runtime.
2) OS threads are difficult to work with compared to a nice M:N threading abstraction (which again are the default in Go or Haskell). OS threads leads to lowest common denominator APIs (there is no way to kill a thread in Rust) and some difficulty in reasoning about performance implications. I am attempting to solve this aspect by using the mioco library, although due to point #1 IO is going to be a little awkward.