Cell vs catch_unwind

It's safe to use a Mutex in a catch_unwind because it implements poisoning. There is very little rationale about what can or cannot be passed across catch_unwind, but one thing is clear to me: RefCell is the singlethreaded equivalent of Mutex, but it doesn't have poisoning, so an unwind would drop the locks and leave the contents in a potentially unsafe state.

But: Cell is very different!

Cell types come in two flavors: Cell<T> and RefCell<T> . Cell<T> implements interior mutability by moving values in and out of the Cell<T> . To use references instead of values, one must use the RefCell<T> type, acquiring a write lock before mutating.

(from std::cell)

Cell can never be in an "unsafe" state, because either you fully move into it, or fully move out of it. So it makes no sense that Cell can't be used across catch_unwind? idk.

am I missing something?

1 Like

(This is merely to point out and link to the documentation on the matter.)


pub fn catch_unwind<F: FnOnce() -> R + UnwindSafe, R>(f: F) -> Result<R>

Trait std::panic::UnwindSafe

A marker trait which represents "panic safe" types in Rust.

This trait is implemented by default for many types and behaves similarly in terms of inference of implementation to the Send and Sync traits. The purpose of this trait is to encode what types are safe to cross a catch_unwind boundary with no fear of unwind safety.

What is unwind safety?

In Rust a function can "return" early if it either panics or calls a function which transitively panics. This sort of control flow is not always anticipated, and has the possibility of causing subtle bugs through a combination of two critical components:

  1. A data structure is in a temporarily invalid state when the thread panics.
  2. This broken invariant is then later observed.

Typically in Rust, it is difficult to perform step (2) because catching a panic involves either spawning a thread (which in turns makes it difficult to later witness broken invariants) or using the catch_unwind function in this module. Additionally, even if an invariant is witnessed, it typically isn't a problem in Rust because there are no uninitialized values (like in C or C++).

It is possible, however, for logical invariants to be broken in Rust, which can end up causing behavioral bugs. Another key aspect of unwind safety in Rust is that, in the absence of unsafe code, a panic cannot lead to memory unsafety.

That was a bit of a whirlwind tour of unwind safety, but for more information about unwind safety and how it applies to Rust, see an associated RFC.

That’s a why, not an what.

If you mean what I think you mean, "the what" is on that same page almost immediately after the part @CAD97 quoted:

Who implements UnwindSafe ?

Types such as &mut T and &RefCell<T> are examples which are not unwind safe. The general idea is that any mutable state which can be shared across catch_unwind is not unwind safe by default. This is because it is very easy to witness a broken invariant outside of catch_unwind as the data is simply accessed as usual.

Types like &Mutex<T> , however, are unwind safe because they implement poisoning by default. They still allow witnessing a broken invariant, but they already provide their own "speed bumps" to do so.

Cell provides mutable access to its interior so can easily violate invariants if made UnwindSafe, for an example see this playground.

get_mut takes an &mut self, not an &self.

hmm, I know we have RefUnwindSafe but, do we have MutUnwindSafe?

Also, since UnwindSafe is an auto-trait, a Cell<T: UnwindSafe>: UnwindSafe, example here.

okay so:

I can’t move Rc<*const AThing> into a closure?

I can’t move rc::Weak<Cell<Option<ThingINeedToDropWhenTheClosureIsCalled>>> into a closure?

I think?

here:

$ cargo build
   Compiling hexchat-plugin v0.2.10 (/home/soniex2/git/cybrespace/rust.hexchat.hexchat-plugin.testplug/hexchat-plugin)                                                                                                            
error[E0277]: the type `std::cell::UnsafeCell<std::option::Option<PrintHookHandle>>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary                               
   --> src/lib.rs:662:31                                                                                                                                                                                                          
    |                                                                                                                                                                                                                             
662 |         closure.set(Some(self.hook_print("Close Context", move |ph, _| {                                                                                                                                                    
    |                               ^^^^^^^^^^ `std::cell::UnsafeCell<std::option::Option<PrintHookHandle>>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary       
    |                                                                                                                                                                                                                             
    = help: within `[closure@src/lib.rs:662:59: 666:10 ctxp:std::rc::Rc<*const internals::HexchatContext>, hook:std::rc::Rc<std::cell::Cell<std::option::Option<PrintHookHandle>>>]`, the trait `std::panic::RefUnwindSafe` is not implemented for `std::cell::UnsafeCell<std::option::Option<PrintHookHandle>>`
    = note: required because it appears within the type `std::cell::Cell<std::option::Option<PrintHookHandle>>`                                                                                                                   
    = note: required because it appears within the type `std::marker::PhantomData<std::cell::Cell<std::option::Option<PrintHookHandle>>>`                                                                                         
    = note: required because it appears within the type `std::rc::Rc<std::cell::Cell<std::option::Option<PrintHookHandle>>>`                                                                                                      
    = note: required because it appears within the type `[closure@src/lib.rs:662:59: 666:10 ctxp:std::rc::Rc<*const internals::HexchatContext>, hook:std::rc::Rc<std::cell::Cell<std::option::Option<PrintHookHandle>>>]`         
                                                                                                                                                                                                                                  
error[E0277]: the type `std::cell::UnsafeCell<usize>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary                                                              
   --> src/lib.rs:662:31                                                                                                                                                                                                          
    |                                                                                                                                                                                                                             
662 |         closure.set(Some(self.hook_print("Close Context", move |ph, _| {                                                                                                                                                    
    |                               ^^^^^^^^^^ `std::cell::UnsafeCell<usize>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary                                      
    |                                                                                                                                                                                                                             
    = help: within `[closure@src/lib.rs:662:59: 666:10 ctxp:std::rc::Rc<*const internals::HexchatContext>, hook:std::rc::Rc<std::cell::Cell<std::option::Option<PrintHookHandle>>>]`, the trait `std::panic::RefUnwindSafe` is not implemented for `std::cell::UnsafeCell<usize>`
    = note: required because it appears within the type `std::cell::Cell<usize>`                                                                                                                                                  
    = note: required because it appears within the type `std::rc::RcBox<*const internals::HexchatContext>`                                                                                                                        
    = note: required because it appears within the type `*const std::rc::RcBox<*const internals::HexchatContext>`                                                                                                                 
    = note: required because it appears within the type `core::nonzero::NonZero<*const std::rc::RcBox<*const internals::HexchatContext>>`                                                                                         
    = note: required because it appears within the type `std::ptr::NonNull<std::rc::RcBox<*const internals::HexchatContext>>`                                                                                                     
    = note: required because it appears within the type `std::rc::Rc<*const internals::HexchatContext>`                                                                                                                           
    = note: required because it appears within the type `[closure@src/lib.rs:662:59: 666:10 ctxp:std::rc::Rc<*const internals::HexchatContext>, hook:std::rc::Rc<std::cell::Cell<std::option::Option<PrintHookHandle>>>]`         
                                                                                                                                                                                                                                  
error: aborting due to 2 previous errors                                                                                                                                                                                          
                                                                                                                                                                                                                                  
For more information about this error, try `rustc --explain E0277`.                                                                                                                                                               
error: Could not compile `hexchat-plugin`.                                                                                                                                                                                        

To learn more, run the command again with --verbose.

So my example was showing that having

impl<T: ?Sized> UnwindSafe for Cell<T>

would be unsound, but it looks like what you want is

impl<T: RefUnwindSafe + ?Sized> RefUnwindSafe for Cell<T>

that… seems more likely to be sound. I can’t think of a counter-example off the top of my head at least.

(btw, anyone knows where that UnsafeCell<usize> (specifically the usize part) came from? I don’t know where it came from :/)

Cell is implemented on top of UnsafeCell.~

Scratch that, it’s something to do with RcBox: https://doc.rust-lang.org/src/alloc/rc.rs.html#267-270

okay, but why’s this erroring?

It’s sort of internal implementation details that shouldn’t really be shown in the error message. The compiler needs to prove &Rc<*const HexchatContext>: UnwindSafe (which I don’t actually see stated explicitly, seems there’s a step missing between the last 2 lines of the type stack?). (EDIT: this playground shows why I think there must be a hidden reference here)

This will start by attempting to prove that impl<'a, T: RefUnwindSafe + ?Sized> UnwindSafe for &'a T holds. Rc<*const HexchatContext> doesn’t have an explicit RefUnwindSafe implementation, so will do structural recursion for the auto-trait. That will end up at having to prove UnsafeCell<usize>: RefUnwindSafe for the structural trace shown in the error message. This is explicitly denied by impl<T: ?Sized> !RefUnwindSafe for UnsafeCell<T>.

There could be an explicit RefUnwindSafe implementation added for Rc to make this either compile, or give a better error message. Again it seems sound at first glance, but I’m really not 100% sure:

impl<T: RefUnwindSafe + ?Sized> RefUnwindSafe for Rc<T>

(maybe there are cases where an Rc can panic during cloning? I think that would have to be proven sound to show that this implementation is sound).

I’m using move closures

I can’t seem to reproduce the same sort of error message where it claims a closure capturing by-value Rc<*const _> is !UnwindSafe. That should hit impl<T: RefUnwindSafe + ?Sized> UnwindSafe for Rc<T> and be ok (or at least give an error message that HexchatContext: !RefUnwindSafe). Any chance of being able to pull a small reproducer of that error out of your codebase?

EDIT: Ohhhh, this might just be a follow on error from the first one. The Rc<*const HexchatContext> might be ok during the first attempt to prove that the closure is UnwindSafe, but since the rc::Weak<Cell<_>> fails that it tries an alternative proof that Rc<*const HexchatContext>: RefUnwindSafe which it fails.

so the problem is Cell not being RefUnwindSafe?

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.