While reading the answer for another question, I saw this construct [1]:
struct Clipboard {
marker: PhantomData<()>,
}
While I've seen other uses of PhantomData
, all of them have been parameterized with an interesting type, like PhantomData<&'a [u8]>
or PhantomData<T>
. Why would you want to create struct that acts as if it contains an empty tuple?
[1]: A bit of poetic license as I actually wrote the other answer, but was asked to explain why I did what I did. It was too long for a comment.
In this case, the clipboard was a globally shared resource that didn't give you any token when you opened it. To try and use the clipboard without opening it first would be a Bad Thing. If you did the straightforward thing and created an empty struct, then you could forget to call the proper constructor method:
struct Clipboard;
impl Clipboard {
fn new() -> Clipboard {
println!("Clipboard opened");
Clipboard
}
fn copy(&self) -> String { "copied".into() }
}
let c = Clipboard::new(); // Correct
println!("{}", c.copy());
let c = Clipboard; // Nope, didn't open the clipboard properly
println!("{}", c.copy()); // But can still call methods!?!?!
Let's try a tuple struct with a dummy value inside:
struct ClipboardWithDummyTuple(());
impl ClipboardWithDummyTuple {
fn new() -> ClipboardWithDummyTuple {
println!("Clipboard opened");
ClipboardWithDummyTuple(())
}
fn copy(&self) -> String { "copied".into() }
}
let c = ClipboardWithDummyTuple::new(); // Correct
println!("{}", c.copy());
let c = ClipboardWithDummyTuple;
println!("{}", c.copy()); // Fails here
// But because `c` is a method, not an instance
This is better, but the error occurs later than we'd like; it only happens when we try to use the clipboard, not when we try to construct it. Let's try making a struct with named fields:
struct ClipboardWithDummyStruct {
// warning: struct field is never used
dummy: (),
}
impl ClipboardWithDummyStruct {
fn new() -> ClipboardWithDummyStruct {
println!("Clipboard opened");
ClipboardWithDummyStruct { dummy: () }
}
fn copy(&self) -> String { "copied".into() }
}
let c = ClipboardWithDummyStruct::new(); // Correct
println!("{}", c.copy());
let c = ClipboardWithDummyStruct; // Fails here
// But we have an "unused field" warning
So, we are closer, but this would generate a warning about an unused field. We could turn off that warning for the field with #[allow(dead_code)]
, or we could rename the field to _dummy
, but I believe there's a better solution — PhantomData
:
use std::marker::PhantomData;
struct ClipboardWithPhantomData {
marker: PhantomData<()>,
}
impl ClipboardWithPhantomData {
fn new() -> ClipboardWithPhantomData {
println!("Clipboard opened");
ClipboardWithPhantomData { marker: PhantomData }
}
fn copy(&self) -> String { "copied".into() }
}
let c = ClipboardWithPhantomData::new(); // Correct
println!("{}", c.copy());
let c = ClipboardWithPhantomData; // Fails here
This doesn't generate any warnings, and PhantomData
is used to indicate that something "different" is happening. I'd probably throw a little comment on the struct definition to note why we were using PhantomData
in this way.
A lot of the ideas here stem from a semi-related Rust issue about the correct type for an opaque struct.