Live data from Hacker News

Concurrency in Rust

doc.rust-lang.org

131–140 of 160 posts

Re: Concurrency in Rust

#131
post #124

Earlier quoted context omitted.

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

> Right now, catching exceptions (panics) using recover() is still considered unstable. ... 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

And it's for this reason that I don't think I'll be choosing Rust for any of my projects in the near future. This cavalier attitude toward memory exhaustion is not only concerning itself, but also makes me doubt the robustness and design principles of the rest of the system.

Besides, if you make exception-safe code difficult to write, nobody in practice will write it, so you'll end up with a system that's tantamount to one that just aborts. Saying that "Rust the language handled OOM just fine without stdlib!" and "we can convert OOM to panic!" is useless when these measures don't help real world code.

> In Rust, exceptions (panic) are used for truly exceptional situations

I've never accepted the argument that we need to use one error-recovery scheme for "normal" errors and another for "exceptional" ones. That kind of claim sounds reasonable, sober, and measured, but it leads to bad outcomes in every system I've seen, because the "exceptional" case in practice becomes a hard abort. A unified error handling scheme is a boon because it greatly simplified the cognitive analysis of errors.

Java is a good example of how to do right-ish. Serious errors are Throwables not derived from Exception, so normal catch blocks are unlikely to catch them. But serious errors are still exceptions (if not Exception), and all the usual language features for processing exceptions, including unwinding, stack trace recording, and chaining, operate normally.

Uniformity of error processing in Java is a great feature, and the language gets it without sacrificing the ability to distinguish between serious and expected errors. Now, I'm not arguing that Rust get checked exceptions, but I do have to insist that experience shows that you don't need two completely different error handling mechanisms (say, panic and Result) to mark problem severity.

> 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.

I'm not comfortable to casual changes in core runtime semantics.

> On modern virtual memory operating systems,

Are you just defining "modern" as "overcommit"? People (especially from the GNU/Linux world) constantly assert that allocation failure is rare, but I've seen allocations fail plenty of times, due to both address space exhaustion and global memory exhaustion. I don't have any firm numbers, but I haven't seen any from the abort-on-failure camp either.

> 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?

SQLite [1] and NTFS [2] come to mind, as well as lots of tools I've discovered.

[1] https://www.sqlite.org/malloc.html

[2] guaranteed to make forward progress; pre-reserves all needed recovery resources; yes, I know NTFS runs in ring zero, but it's not the case that the kernel doesn't have to deal with dynamic memory allocation

Re: Concurrency in Rust

#132

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; }); }

That's a mistake, clarified: https://github.com/rust-lang/rust/pull/32538

Re: Concurrency in Rust

#133

Earlier quoted context omitted.

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

> Sure, but if linux has this issue, then C++ programs on linux will also have this issue Why even bring Linux into the discussion? A Rust program running on Windows has the same problem. > No. My point was simply that no language has a complete solution to this problem. A correct C++ program running on Windows will not spuriously abort. Neither will a C++ program running on a Linux system configured not to overcommi…

Can you point to an example of such a "correct C++ program" that is larger than some sample code or a toy program demonstrating this technique?

I'm just wondering if this argument is all hypothetical, or if there are any teams of C++ programmers who are actually disciplined enough to be able to handle this case in practice, in large scale software. I know that in most code that I've seen, the only use of std::bad_alloc has been to log the error and abort.

Re: Concurrency in Rust

#134

Earlier quoted context omitted.

> That doesn't change the fact that few people actually do it or need to do it. I am one of those few people. We are running some scientific code (currently, unfortunately, written in C++) on a heterogeneous bunch of compute nodes. Some computations can be extremely memory-intensive, and sometimes in ways that we didn't predict. So it's useful to be able to fail gracefully and record that computation X on node Y with…

That's something that Rust fully supports with panic handlers. Doing arbitrary work before the process goes down is useful and supported. (But you will have to be running Linux in a non-default configuration for it to be reliable, of course!)

Thanks, that's good to know!

> But you will have to be running Linux in a non-default configuration for it to be reliable, of course!

Of course. It does seem to work in practice with our C++ code, although probably that's due to our usage pattern.

Re: Concurrency in Rust

#135
post #59

Earlier quoted context omitted.

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

> Rust's notoriously poor error messages 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 tim…

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.

Rust rewards gumption [2] and perseverance.

[1] https://doc.rust-lang.org/error-index.html [2] https://en.wikipedia.org/wiki/Gumption_trap

Re: Concurrency in Rust

#136
post #104

Earlier quoted context omitted.

> Even C++ doesn't have mandatory exception support Yes it does. That some compilers provide a way to disable mandatory language features is no argument. > even Rust can catch panics from failure-oblivious code. Not while maintaining that code's invariants it can't.

Are you saying C++ make it easy to write exception-safe code? Because Rust explicitly encodes exception safety into the type system with the RecoverySafe trait, you need to write unsafe code to bypass that, and the documentation on unsafe explicitly covers exception safety.

Rust doesn't consider exception safety to be a matter worth 'unsafe's time. All code must simply be memory-safe in the face of unwinding. RecoverySafe is basically "it's hard to leak busted state out of a region of code that panicked". That is, mutable references aren't RecoverySafe, and mutexes and the like poison their contents if they witness a panic while locked.

But RecoverySafe is just preventing things like "your binary heap was only partially heapified" and not "your heap is now full of uninitialized memory". You can get poisoned values out of mutexes just fine, so everything needs to put itself in a memory-safe state if a panic occurs.

One can bypass RecoverySafe in safe code with the AssertRecoverySafe wrapper.

It does however turn out that safe code in Rust is generally quite exception-safe by default. This is because safe code can't do anything too dangerous, panics are generally only caught at thread or application boundaries (so data that witnesses a panic is usually well-isolated) and there's way less places that can unwind compared to "override everything" C++. But exception safety is indeed something unsafe code needs to fight for (see the aforementioned binary heap in std).

Re: Concurrency in Rust

#137
post #124

Earlier quoted context omitted.

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

> Right now, catching exceptions (panics) using recover() is still considered unstable. ... 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 And it's for this reason that I don't think I'll be choosing Rust for any of my projects in the near future. This cavalier attitude towa…

  Besides, if you make exception-safe code difficult to 
  write, nobody in practice will write it, so you'll end up 
  with a system that's tantamount to one that just aborts. 
  Saying that "Rust the language handled OOM just fine 
  without stdlib!" and "we can convert OOM to panic!" is 
  useless when these measures don't help real world code.
I'm not sure where you get the "difficult to write" part from. It's no more or less difficult to write than in any other language, as far as I know; you just do have to go through the effort to indicate that "yes, I did think this through and believe this is exception safe" for types that you want to be able to use across an exception-catching boundary.

As I said, work is ongoing to determine if this AssertUnwindSafe approach is actually workable in practice. The initial implementation had some usability issues, but it looks like it may be more workable now that you can use it on the entire closure if you need to. It's still a speedbump, but a very minor one.

  That kind of claim sounds reasonable, sober, and 
  measured, but it leads to bad outcomes in every system 
  I've seen, because the "exceptional" case in practice 
  becomes a hard abort.
Can you point out what these bad outcomes or bad systems have been? I agree that in practice, the most common case is that the exceptional case becomes a hard abort, but I don't necessarily agree that that's a bad thing.

For people who are not trying to write extremely fault-tolerant code like SQLite, and going to great lengths to do so, that is a good thing; adding some half-assed normal error handling around these truly exceptional cases is more likely to lead to mistakes and problems down the line than just aborting is.

For people who are trying to write extremely robust, fault tolerant code, you can either handle panics, or avoid the standard library and do error handling via results. Both should approaches should be viable, depending on your requirements; the standard library does take exception safety into account, so it shouldn't on its own cause issues if you handle errors via panics.

  I'm not comfortable to casual changes in core runtime 
  semantics.
But you are comfortable with the sheer amount of undefined and unspecified behavior in C and C++? Remember, at the moment Rust only has a single implementation and no formal specification, while C and C++ have many different implementations, and the standards allow very wide amounts of leeway in how implementations differ.

Now, Rust not having a formal specification or multiple implementations is not a good thing; it's just a fact of life for a language that is not yet very mature. But I think that this particular behavior is something that should be considered similar to unspecified behavior at the moment. Just like out of memory situations or stack overflow behave differently on different platforms in C and C++ at the moment, how the Rust runtime behaves on out of memory could also be subject to change or different implementations. Given the standard library API, you couldn't return a result, but either aborting or panicking would both be consistent with the language as currently defined.

  People (especially from the GNU/Linux world) constantly 
  assert that allocation failure is rare
I'm not asserting that allocation failure is rare. Just that there are some cases where you don't have a chance to handle it at all, like GNU/Linux where you overcommit, and that handling it in any way other than abort is rare.

  SQLite [1] and NTFS [2] come to mind, as well as lots of 
  tools I've discovered.
Neither SQLite nor NTFS use exceptions, nor are they applications, so they aren't very good examples of applications using exception handling to deal with memory allocation failure.

SQLite is written in C, which doesn't have exceptions, nor a standard library similar to the C++ or Rust standard library. SQLite has had to implement all of their data structures by hand. You can do exactly the same in Rust by using #![no_std] and just using the core library, which only defines basic data types and never allocates.

NTFS is written in the NT kernel, which doesn't have support for exceptions either, nor does it use the C++ standard library.

So yes, you can actually write code that handles allocation failure properly. The examples you've given both eschew a high-level standard library, and instead implement all of their data structures and memory handling themselves, reporting errors by passing error values back. All of which you can do in Rust using #![no_std].

Meanwhile, there are lots of user-space applications that people use all the time which have no special handling for OOM situations; they rely on the OS to provide them with sufficient amounts of virtual memory, and either be killed by not handling an exception, aborting explicitly on getting NULL from malloc, or being killed by an OOM killer if they exceed the capacity of the machine and try to access an overcommitted page.

I'm sure there are some examples out there, somewhere, of user-space applications that actually do catch such issues, and attempt to do graceful cleanup. On the other hand, I don't know how successful they will be, especially if they have to be cross-platform; since any kind of cleanup you may do, such as writing state out to disk before dying, will hit the kernel's page cache, which may involve allocating memory, which may fail in such a situation, even if you do try to handle the issue gracefully in user-space you may not have anything you can do.

Re: Concurrency in Rust

#138
post #137

Earlier quoted context omitted.

> Right now, catching exceptions (panics) using recover() is still considered unstable. ... 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 And it's for this reason that I don't think I'll be choosing Rust for any of my projects in the near future. This cavalier attitude towa…

Besides, if you make exception-safe code difficult to write, nobody in practice will write it, so you'll end up with a system that's tantamount to one that just aborts. Saying that "Rust the language handled OOM just fine without stdlib!" and "we can convert OOM to panic!" is useless when these measures don't help real world code. I'm not sure where you get the "difficult to write" part from. It's no more or less dif…

There's more to the world than end-user applications though. I think your mental model is that there are two kinds of Rust user: OS kernel writers and people who create applications with menu bars and save buttons.

What about network services that would rather begin failing requests on overload than shut down entirely and restart, incurring potentially big delays in the process? What about scientific computing projects that are happy delaying work once they've hit pre-defined limits? I think you're suffering from a failure of imagination.

If Rust's goal is to supplant C, it needs to be capable of everything C is capable of doing. Arguing that applications in general need X or Y is a canard, because most of those applications have no specific need of the kind of direct memory control that Rust affords.

To put it another way: who are you trying to satisfy? Are you trying to compete with Go, Nim, Python, and Java and provide high-level facilities that work most of the time, at the cost of control, or are you trying to compete with C and C++, which still fill an essential niche?

By appealing to arguments about the requirements of applications in general instead of requirements of systems programming languages specifically, you're suggesting that the former audience is the better bet.

That kind of targeting is sad, since one of the promises of Rust is that its memory safety would save us from the plague of security holes in low-level software. The decisions the Rust project is making right now make it less likely that Rust will be able to fully fill C and C++'s niche.

One of the purposes of having a standard library for a project is to be a universal resource for all users of a language. If Rust's standard library isn't suitable for all environments where Rust might be used (like C++'s standard library is), then maybe it should be packaged as a separate project, like Qt.

Re: Concurrency in Rust

#139

Earlier quoted context omitted.

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

> I do see the need to have a memory allocation approach that can handle OOM gracefully.

Do you find anything wrong with inserting an allocator that panics on OOM (IIRC the default one aborts on OOM) and using `std::panic::recover` to catch the panic? This is the same as throwing and catching an exception. Note that `recover()` is designed to be exception safe by default.

(There soon will be a way to make std heap APIs like box and vec use Result, which might be neater)

Re: Concurrency in Rust

#140
post #137

Earlier quoted context omitted.

Besides, if you make exception-safe code difficult to write, nobody in practice will write it, so you'll end up with a system that's tantamount to one that just aborts. Saying that "Rust the language handled OOM just fine without stdlib!" and "we can convert OOM to panic!" is useless when these measures don't help real world code. I'm not sure where you get the "difficult to write" part from. It's no more or less dif…

There's more to the world than end-user applications though. I think your mental model is that there are two kinds of Rust user: OS kernel writers and people who create applications with menu bars and save buttons. What about network services that would rather begin failing requests on overload than shut down entirely and restart, incurring potentially big delays in the process? What about scientific computing projec…

> If Rust's goal is to supplant C, it needs to be capable of everything C is capable of doing.

We have demonstrated this multiple times. You can either use your own stdlib like sqlite, or use recover. You may not like the solution, but the fact still remains that it still is a tangible solution (well, the latter one is -- "your own stdlib" is a pretty specialized solution which you shouldn't need) to the problem. Given that a solution exists, the only issue is with usability -- and you have to ask the question if there are any improvements to the OOM-handling API that can be made without burdening the users who don't care about OOM too much. There is one improvement which can be made that doesn't affect non-OOM users at all (custom allocators v2, which lets you use Rust error handling with stdlib heap types). This improvement is something the core team cares about and will probably happen (don't know about the time frame, since it handles a lot more things than just Resulty heap types). Other improvements will either mean having regular users check for null all the time, or make panics standard fare, neither of which are good ideas.

Please stop ignoring the fact that Rust does have a solution to the OOM problem; I'm tired of reiterating this argument. One can make arguments that it's much not as usable as C++ or C -- that's okay, but ignoring it entirely is just silly.

(As far as usability wrt C++ and C, I still don't see why it's less usable, C has the horrible check-every-time-or-else situation, and Rust's solution is more or less identical to C++ with the exception that it's the road less traveled on. Given that the API handles exception safety explicitly, this should not be that big a problem).

> you're suggesting that the former audience is the better bet

Not necessarily. The former audience encompasses the latter. Rust doesn't want to put undue burden on general users (like having to check all allocations or having to think about exception safety). That's a reasonable ask. It similarly doesn't want to put undue burden on systems users, and it doesn't -- not any more than C or C++. I don't think the Rust designers feel that they have, recover() is a pretty decent API with a lot of thought put into exception safety.

> If Rust's standard library isn't suitable for all environments where Rust might be used (like C++'s standard library is)

The reason #[no_std] was brought up was because you gave an example of sqlite, which does the same thing. It's meant to be used in certain situations in embedded programming or writing a kernel (note that Rust still has a "core std lib", called libcore, which is available and doesn't need malloc) where things like malloc may not even exist. Embedded programming in C++ does something similar.

Post reply on HN