Why would I create a struct with only a `PhantomDa

2019-04-11 14:33发布

问题:

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.

回答1:

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.



标签: rust