Code style Q: how to name a field holding PhantomData?

I am writing the following code:

    #[derive(Default, Debug)]
    pub struct OnceBox<T> {
        inner: OnceNonZeroUsize,
        phantom: PhantomData<T>,
    }

would would be the best name for phantom field? phantom is too long to my taste :frowning:

In the standard library, I see phantom, marker, _marker, and _p.

1 Like

Yeah, this is exactly what I am asking -- my own code demonstrates similar richness, and I spend a second thinking about this every single time...

I wanted to suggest :ghost:, but that's not valid even with #![feature(non_ascii_idents)].

15 Likes

Hm, actually, ghost might not be as bad as a name..

1 Like

ghost, fake, mock, void, null, pseudo, bogus, shadow, unused, blank, mirage

1 Like

spoon, cake, mcguffin, lacuna, palimpsest, skeuomorph

5 Likes

cake wins.

Everybody knows the cake is a lie.

8 Likes

If this is a personal choice rather than one that requires team agreement, I would just go with _p in contexts where the leading _ is acceptable.

1 Like

How about mark? It's shorter.

Since PhantomDatas only appear out of necessity, and since code > documentation, I've personally found that naming that field with a short description of the role of the PhantomData field to "fit" the code quite neatly: the code looks more readable, even if it's at the cost of a longer field name.

Examples

struct OnceBox<T> {
    inner: OnceNonZeroUsize,
    _owns_T: PhantomData<T>,
}
/// Uses thread-locals in some of its methods.
struct Foo {
    // …
    _thread_local: PhantomData<*mut ()>,
}
/// Like `&'a T`, but with the provenance of the initial pointer it
/// was created with (_e.g._, to cast this "back" to a reference to
/// a _bigger_ `T`).
struct RefWithProvenance<'lt, T> {
    ptr: ptr::NonNull<T>,
    _lt: PhantomData<&'lt T>,
    // or:
    _borrows: PhantomData<&'lt T>,
}
struct MyDynFn<T> {
    // …
    _contravariant: PhantomData<fn(T)>,
}
  • funnily enough, and a proof that we can never be "too explicit" about PhantomDatas, is that, had the PhantomData field been named _phantom or _p, then it is hard to tell if somebody writing:

    struct MyDynFn<T> {
        // …
        _p: PhantomData<dyn Fn(T)>,
    }
    

    has opted out of contravariance on purpose (vs. using fn(T)).


TL,DR

Let's give the PhantomData fields meaningful names :pray:

13 Likes

This is one place where I can see _ being useful as a pseudo-identifier for struct and enum variant fields, e.g.

struct Foo {
    _: PhantomData<Bar>
} 

It would also be consistent with the same feature already being available for locals and fn parameters.

However, I think it should have different semantics in that the value should not immediately be dropped, for the reason that if it were, what would be left of the containing value of type Foo is a partially-moved value.

Or we could go the radical other extreme and go with @Yandros's proposal :slightly_smiling_face:

When I'm using PhantomData for things like Send + Sync or lifetimes I'll usually call it something like __not_send so people know it's not meant to be used directly and has a very specific purpose.

1 Like

All joking aside, I agree completely. Name things directly according to what you are hoping to accomplish. More why than what.

4 Likes

I just use __ for such fields :slight_smile:

I often use _boo to spook the type system into doing what I want.

4 Likes

Perhaps as an FYI, but hopefully useful over time as Rust continues to expand its type capacity.

A phantom type does infer meaning. The classic example of a phantom type is how it can be used to define a Constant. Sketching it out in Haskell it looks something like:

newtype Const A B = Const { get_const:: A }
// B is the phantom type

The left side is what the type system sees, the right side the runtime. I have not seen this capacity play out in Rust (as of yet anyway). Being mostly new to Rust, I gather that explicit type annotations and traits are ways to influence a type without necessarily changing the value. Notwithstanding...

A classic use case is "tagging" whether a user application has been "approved". Only the function with the credentials to change the type (Form InProcess -> Form Approved) controls that process. All made possible by the compiler; runtime is guaranteed to "follow the rules" whilst only "seeing" the instantiated Form type. I have used it to track the difference between "a data request" and "the data itself"; one is a wish, the other what exists in reality (if you will).

All this to say, whether to truly call the "tag", "phantom", depends on how much what is going on in your use of the "tag" matches that of a "phantom" type... a term with meaning in a type system. The benefits of using the verbose version: you tap into the understanding of how that pattern is expected to work.

Notwithstanding all of the above,

marker: PhantomData

... from a def that one is likely to visit at least once in their lifetime; the LinkedList module...


#[stable(feature = "rust1", since = "1.0.0")]
pub fn iter(&self) -> Iter<'_, T> {
    Iter { head: self.head, tail: self.tail, len: self.len, marker: PhantomData }
}

Don't think, just copy! When it comes time to naming, as much as possible :))

- E