Opt out of RefUnwindSafe (only), no_std, no unsafe

I have a type Error that I'd like to make unconditionally !RefUnwindSafe by adding an appropriate PhantomData field, without impacting any other auto traits. The question is, what PhantomData can I use to accomplish this in no_std context without writing any unsafe code? My current solution is

pub struct Error {
    // other fields...
    _not_ref_unwind_safe: PhantomData<UnsafeCell<()>>,
}

// SAFETY only source of !Sync is PhantomData
unsafe impl Sync for Error {}

but I would like a solution that doesn't need unsafe impl Sync or the like.

The std::io::Error type is Send+Sync but !UnwindSafe+!RefUnwindSafe.

pub struct Error {
    // other fields...
    _not_ref_unwind_safe: PhantomData<std::io::Error>,
}

impl UnwindSafe for Error {}

That's not no_std compatible, though.

Oh, I missed that requirement. I can't find anything in core that satisfies your needs.

I would probably define the following utility:

pub struct NotUnwindSafe {
    _not_ref_unwind_safe: PhantomData<UnsafeCell<()>>,
}

unsafe impl Send for NotUnwindSafe {}
unsafe impl Sync for NotUnwindSafe {}

Then use it in your Error.

pub struct Error {
    // other fields...
    _not_ref_unwind_safe: PhantomData<NotUnwindSafe>,
}
impl UnwindSafe for Error {}

This way, the unsafety is isolated to the file containing the utility.

Eventually, SyncUnsafeCell will be perfect for you. :slight_smile:

2 Likes

SyncUnsafeCell, but it's unstable.

(Jinx :slightly_smiling_face:)

2 Likes

I sort of thought this was possible for the other auto traits, but after digging around I don't see any way to kill Send but not Sync using only core + alloc (suggestions welcome). As for the rest, you can kill Sync with Cell<()>, UnwindSafe with &mut (), and Unpin with PhantomPinned.

Yeah, that's a reasonable approach. Incidentally you don't need the unsafe impl Send there, UnsafeCell<T> is already Send if T is.

Nice, I'll keep an eye on that in case it stabilizes soon.

1 Like

I looked through all the types in core and alloc, and I couldn't find anything stable that matches this description. I only found some unstable types in core that would work, as well as a few different types in #![no_std] libraries:

core::cell::SyncUnsafeCell<()> // unstable
core::ptr::DynMetadata<Cell<()>> // unstable, possibly an oversight
core::sync::Exclusive<Cell<()>> // unstable
qcell::LCell<'static, ()> // possibly an oversight
qcell::QCell<()> // possibly an oversight
spin::Mutex<()>
spin::RwLock<()>

Out of curiosity, why is your type Send + Sync + !RefUnwindSafe? Pretty much all types like that contain non-poisoning mutexes or locks, which seems unusual for a simple Error type.

1 Like

Because it conditionally includes a std::backtrace::Backtrace, which has the same property, when a specific cfg(...) is active. I need the PhantomData so that flipping on that cfg(...) is not potentially breaking.

In turn, Backtrace is Sync + !RefUnwindSafe because it eventually contains this gadget, which contains an UnsafeCell but also has unsafe impl Sync. Maybe Backtrace should really be RefUnwindSafe too and this was just overlooked, idk.

Update: Should `Backtrace` be RefUnwindSafe? - Rust Internals

1 Like

One more addition—I just realized that trait objects give a simple way to do this that works for any combination of auto traits. For example, if you want to kill only Send and UnwindSafe, you can use PhantomData<dyn Any + Sync + RefUnwindSafe + Unpin>, or with some other trait in place of Any. Seems preferable to looking up the auto trait impls for UnsafeCell every time…

[edit: corrected stuff about Unpin]

5 Likes