I'd like to guarantee the benefits of both newtype wrapping and phantom types at the same time, namely to define a "typed key" as shown in this other post.
I can't just do:
pub struct Id<T>(u32);
...since Rust complains about the unused T. Fine in Haskell, but in Rustland we must add a PhantomData field:
Question: Does this undo the usual runtime advantages of a newtype? Is the second version of Id actually wrapped as a struct, not optimizing away the "pointless" PhantomData?
Newtype in Rust is not a specific language feature but a conventional name for a type which wraps another type without additional state. It's not a strictly defined terminology.
Your choice of PhantomData also influences variance. (Apparently not wrt. drop check like the documentation says any more, but also AFAIK there is no guarantee this won't come back in some form. I can try to dig up a recentish discussion if you want more details.)
#[repr(transparent)]
pub struct Id {
id: u32,
_something_not_a_zst: i32,
}
because
error[E0690]: transparent struct needs at most one non-zero-sized field, but has 2
--> src/lib.rs:3:1
|
3 | pub struct Id {
| ^^^^^^^^^^^^^ needs at most one non-zero-sized field, but has 2
4 | id: u32,
| ------- this field is non-zero-sized
5 | _something_not_a_zst: i32,
| ------------------------- this field is non-zero-sized
#[repr(transparent)]
#[serde(transparent)]
pub struct ID<T> {
id: U53,
// The `const` is a trick to tell the compiler that we don't own anything.
marker: PhantomData<*const T>,
}
Note that *const T is a poor way of doing this, because it will make your type !Send + !Sync. That will in turn annoy downstream users of the code, because a trivial ID type like that should most likely be Send + Sync.
If you are trying to express "this type does not own a T but it stands in for a T and it doesn't require dropck", use PhantomData<fn() -> T> instead. The full list of such patterns can be found in the Nomicon.
Paranoia is starting to grow... should I, in general in my day-to-day Rusting, be defensively marking things as #[repr(transparent)] on the off-chance that Rust would otherwise decide not to Do The Right Thing?
If you're doing unsafe, though, you need to be super-careful about every assumption on which you're relying. To re-use a previous post, check out #[repr(transparent)] – Why? - #6 by scottmcm
And, of course, if one needs to make assumptions about the layout of a type, #[repr(transparent)] is not enough, and one may even need #[repr(C)]. (That should be the exception, rather than the norm, though.)