PhantomData Thread Safety


#1

Hi,

I was playing around with Rust zero-size structs, to implement thread safe zero-cost abstraction, and found that PhantomData derives Sync and Send from it’s type.
https://doc.rust-lang.org/nomicon/phantom-data.html
https://doc.rust-lang.org/nomicon/send-and-sync.html

If I understand thread safety correctly PhantomData doesn’t have any data inside, so no way for mutability, and it should be thread safe by default regardless of type argument.

I’ve searched in internet and haven’t found any answer for that. So my question what the reason for PhantomData to derive Sync and Send from it’s type argument?

Code example:

#[macro_use]
extern crate lazy_static;

use std::marker::PhantomData;

struct StructA {}
trait TraitA {}
impl TraitA for StructA {}

struct SyncStruct<S:Sync> {
    sync_variable: S
}

struct StaticStruct {
    // Works fine:
    static_variable: SyncStruct<PhantomData<StructA>>
    
    //Compilation error here:
    //   error[E0277]: the trait bound `TraitA + 'static: std::marker::Sync` is not satisfied in `std::marker::PhantomData<TraitA + 'static>`
    static_variable: SyncStruct<PhantomData<TraitA>>
    //    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `TraitA + 'static` cannot be shared between threads safely
}

lazy_static! {
    // Works fine:
    static ref STATIC_VARIABLE1: SyncStruct<PhantomData<StructA>> = { return SyncStruct{sync_variable: PhantomData} };

    //Compilation error here:
    //    error[E0277]: the trait bound `TraitA + 'static: std::marker::Sync` is not satisfied in `std::marker::PhantomData<TraitA + 'static>`
    static ref STATIC_VARIABLE2: SyncStruct<PhantomData<TraitA>> = { return SyncStruct{sync_variable: PhantomData} };
    //    |_^ `TraitA + 'static` cannot be shared between threads safely
}

#2

I’m not an expert, so take what I say with a pinch of salt, but here goes…

PhantomData exists for when you want to ‘fake’ a type. It can be useful for example when you know the lifetime of something in C code, and you want to fake that lifetime in Rust to make consumer code safe. I think for this reason, although in iteslf PhantomData is safe, it is actually more restrictive than it needs to be because it might be covering something unsafe.

It’s hard making safe abstractions over C sometimes! :slight_smile:


#3

https://doc.rust-lang.org/stable/std/marker/struct.PhantomData.html

To quote the docs:

Adding a PhantomData field to your type tells the compiler that your type acts as though it stores a value of type T, even though it doesn’t really. This information is used when computing certain safety properties.


#4

The type “inside” PhantomData may be used for its associated functions and those may not be thread safe (eg they call into thread-unsafe FFI).


#5

If I understood correctly type inside can have static functions, which can be called using PhantomData, and if that function is not thread safe than PhantomData should derive.

Thank you for your response


#6

Would prefer to use something more straightforward but it works for me:

struct SyncPhantomData<T: ?Sized> {
    phantom: PhantomData<AtomicPtr<Box<T>>>
}

lazy_static! {

    static ref STATIC_VARIABLE2: SyncPhantomData<TraitA> = { return SyncPhantomData{phantom: PhantomData} };
}

#7

What are you trying to do exactly in the grand scheme of things? If you want to force your type to be Send and Sync no matter what, you can impl those traits manually for it rather than fudging the type inside PhantomData.


#8

Hi, basically I already have workable prototype, that works fine for me. Idea is to have a LookupKey to find necessary plugin component in container. I see it as if each plugin component should be identifiable with a string value, and should implement particular interface, so clients can interact with a plugin.

In Rust I can’t simple declare generic type attribute for struct, I also have to use it inside of a struct, compiler suggest me to use PhantomData.

Regarding the grand scheme, I see this LookupKey, which will be used as constant, and it will hold plugin component string id, and type as generic attribute, for safe lookup. Since LookupKey is constant, rust requires that LookupKey should be Sync thread-safe. However the concept of LookupKey can be used to lookup for any component, either it is Sync or not.

I really like rust concept of rust for ownership, borrowing and thread-safety. So the the real owners for the plugins should be plugin containers, and I see that thread safety guarantees should be applied on the moment when plugin is registered in container, in other words if the plugin container is Sync, than only Sync traits can be registered in Container, if plugin container is not-Sync, than Any trait can be registered. I’ve made couple tests and it works well so far :slight_smile: