Live data from Hacker News

Concurrency in Rust

doc.rust-lang.org

121–130 of 160 posts

Re: Concurrency in Rust

#121

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 lea…

> There is no way to kill a thread in Rust 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.

If thinking about ownership of orphaned data is horrible, that still doesn't mean that there is no general solution.

I can't support your argument, because I'm not capable. If I was, I probably wasn't asking in the first place.

Re: Concurrency in Rust

#122

Earlier quoted context omitted.

> Yes it does. That some compilers provide a way to disable mandatory language features is no argument. It's actually very relevant that huge amounts of C++ deployed in the world use -fno-exceptions, and many shops (for example, Google!) have a policy of "we do not use exceptions". I don't care about how well languages handle OOM in theory; what matters is how well they handle it in practice.

> for example, Google! Google's C++ coding standards have done tremendous harm to the C++ community by perpetuating obsolete programming practices like two-phase initialization and lossy error reporting. Google's C++ standards also teach people that it's okay to use the STL and not worry about allocation failure, which hurts program robustness generally. I'm not the only one who thinks so: see https://www.linkedin.co…

I think it's hard to argue that Google is in the wrong by not wanting to rely on std::bad_alloc for dealing with OOM.

> Google's C++ standards also teach people that it's okay to use the STL and not worry about allocation failure, which hurts program robustness generally.

Actually, I think making std::bad_alloc call std::terminate improves program robustness by a lot over trying to gracefully recover from all allocation failure. Certainly it reduces security vulnerabilities.

Re: Concurrency in Rust

#123
Can someone enlighten me as to why the first snippet has a data race? Won't the resulting array become [2,3,4]?

    let mut data = vec![1, 2, 3];

    for i in 0..3 {
        thread::spawn(move || {
            data[i] += 1;
        });
    }

Re: Concurrency in Rust

#124

Earlier quoted context omitted.

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: http://www.jonathanturner.org/2015/11/learning-to-try-things... tl;dr: "Result"s are like exceptions which are caught by default. You ca…

> There is an exception-like mechanism in Rust, in the form of the "try!" macro. Correct. That's not the problem. If Rust's standard library returned Result in all cases where allocation could fail, I'd be satisfied. My primary issue is that they didn't , because Result is awkward. Rust's designers went wrong in trying to have their cake and eat it too. They wanted to avoid exceptions and not make people care locally…

I don't think that there's any guarantee in Rust that malloc failure will abort rather than panic. That just happens to be the current implementation. I'm not sure I've ever heard of anyone running into that being an issue in practice, as opposed to this kind of abstract discussion. But I think that it wouldn't be considered a breaking change to switch from aborting to panicking if there were any kind of demand for it.

In Rust, exceptions (panic) are used for truly exceptional situations, like programmer error (indexing beyond the end of an array, division by zero) or things that practically are not expected to happen in a recoverable way in the course of ordinary use, like malloc failure. On modern virtual memory operating systems, malloc failure is so unlikely, and in application code there's so little you could reasonably do if it happened, that it is considered be a truly exceptional case.

On the other hand, Result is used for those kinds of errors that are expected to happen in practice even with working code on reasonable systems. IO errors, errors decoding UTF-8, etc.

Right now, catching exceptions (panics) using recover() is still considered unstable. There is some work ongoing to try and work out the API to help ensure safety, by marking types based on whether they are exception-safe or not; so you can use recover() with types that are built in an exception-safe way, or you can wrap types in AssertRecoverSafe to assert that you are providing exception-safety guarantees yourself, but you can't just arbitrarily recover from panics in code that has access to arbitrary data without someone having added an annotation somewhere that they believe that the code is exception-safe. https://github.com/rust-lang/rust/issues/27719 Note that based on the latest discussion, recover() will likely be named something else involving "unwind" to be more explicit about what it's doing.

And exception safety is quite important to the Rust authors. Note that Mutex has a built-in exception safety mechanism, poisoning the mutex on panic so that other users can't accidentally access the protected resource without being aware that another thread panicked while holding it.

Now, there are times when handling memory allocation failures properly is more important, such as in embedded systems or in operating system kernels, where you don't have a virtual memory abstraction with over-provisioning. However, in those cases you couldn't use the standard library anyhow, as the standard library depends on OS support; so you might as well use alternate data types that do return Result on allocating operations.

I'm just not sure about the utility of providing a convenient way to recover from malloc failure in applications running on virtual-memory operating systems. Can you show me an example in C++ (or any other language) where this is handled properly in application code in any way that doesn't simply log and abort, in which all unwinding code in the same application also avoids allocation as it may occur while unwinding from an allocation failure, and in which these code paths are actually tested in the test suite to ensure they behave properly?

Re: Concurrency in Rust

#125

Earlier quoted context omitted.

The world is not Linux. I happen to believe that believe that overcommit in the Linux kernel is a disgrace. It is, however, at least possible to disable it. It's not possible to retroactively add real exceptions to Rust, or to change the signature of all memory-allocation function to return Result. Rust is supposed to be a general-purpose systems programming language, not a Linux programming language. Windows does no…

> The world is not Linux. Sure, but if linux has this issue, then C++ programs on linux will also have this issue, and the language can't solve that. That's all my point was. > or to change the signature of all memory-allocation function to return Result. When custom allocators part 2 happens, you can. I've already argued the "real exceptions" part above. > Rust is supposed to be a general-purpose systems programming…

While quotemstr's reaction is over the top, I do see the need to have a memory allocation approach that can handle OOM gracefully. Many types of software that could benefit from Rust's compile-time safety will want to allocate right up to the limit of available memory, such as audio/video processing software where more memory equals more simultaneous effects and less I/O.

I am endlessly frustrated by poorly designed audio software that aborts without saving if an OOM occurs. At the very least, a process should have the oppprtunity to save its state to disk, or ideally continue operating at a reduced capacity (e.g. a video codec might use fewer reference frames) after freeing some resources.

Re: Concurrency in Rust

#126

Earlier quoted context omitted.

> There is no way to kill a thread in Rust 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.

If thinking about ownership of orphaned data is horrible, that still doesn't mean that there is no general solution. I can't support your argument, because I'm not capable. If I was, I probably wasn't asking in the first place.

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.

Re: Concurrency in Rust

#127

Can someone enlighten me as to why the first snippet has a data race? Won't the resulting array become [2,3,4]? let mut data = vec![1, 2, 3]; for i in 0..3 { thread::spawn(move || { data[i] += 1; }); }

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.

Re: Concurrency in Rust

#128

Earlier quoted context omitted.

> for example, Google! Google's C++ coding standards have done tremendous harm to the C++ community by perpetuating obsolete programming practices like two-phase initialization and lossy error reporting. Google's C++ standards also teach people that it's okay to use the STL and not worry about allocation failure, which hurts program robustness generally. I'm not the only one who thinks so: see https://www.linkedin.co…

I think it's hard to argue that Google is in the wrong by not wanting to rely on std::bad_alloc for dealing with OOM. > Google's C++ standards also teach people that it's okay to use the STL and not worry about allocation failure, which hurts program robustness generally. Actually, I think making std::bad_alloc call std::terminate improves program robustness by a lot over trying to gracefully recover from all allocat…

> Certainly it reduces security vulnerabilities.

So does the power button. You can't get away with justifying breaking arbitrary functionality in the name of security.

Re: Concurrency in Rust

#129

Earlier quoted context omitted.

I profoundly disagree with your assertions about the correct way to handle malloc failure. While abort may be acceptable for some specific applications, general-purpose systems don't get to impose that opinion on programmers. Memory is a just another resource, and programs need to deal with resource exhaustion generally. Do you think programs should abort when the disk fills up?

Most programs do not need to deal with memory exhaustion. There's often little that can be done other than terminating anyway, many OS configurations remove your ability to effectively recover from it (overcommit and swapping making your app unusably slow so you would better off terminating), and adding rarely tested code paths is a good way to introduce bugs and vulnerabilities. Programs should abort when the disk f…

> Most programs do not need to deal with memory exhaustion.

You keep making this assertion, but it doesn't appear to be true. There are several examples are on this subthread. I don't think you're justified in treating memory and disk space separately. The concerns that apply to one apply to the other. I know you cite relative frequency of failure as a reason to distinguish, but I don't buy it, because it's not a qualitative difference. Resource exhaustion is resource exhaustion.

Re: Concurrency in Rust

#130
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(pool: &Pool, data: &mut [T]) {
        pool.scoped(move |scoped| do_quicksort(scoped, data))
    }

    fn do_quicksort(scope: &Scope, 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 
In this example, quicksort will block until the array is fully sorted, then return.
Post reply on HN