PhantomData is a unique idea to rust. It is basically a marker to tell the compiler that the struct you are working with can represent the T inside the PhandomData<T>.

Below, we show how you can implement a generic tuple that holds a unique id as well as a marker type.

We then setup a trait that can be applied to all types allowing for easy generation of unique ids that are marked with their respective struct types.

We set Uuid equal to i32 just to minimize dependencies for this example

use std::marker::PhantomData;
type Uuid = i32;

#[derive(PartialEq, Debug)]
struct UniqueID<D>(Uuid, PhantomData<D>);
impl<D> UniqueID<D> {
    fn new(uuid: Uuid) -> Self {
        Self(uuid, PhantomData)
    }
}

trait Uid<D> {
    fn uuid(uuid: Uuid) -> UniqueID<D>;
}
impl<D> Uid<D> for D {
    fn uuid(uuid: Uuid) -> UniqueID<D> {
        UniqueID::<D>::new(uuid)
    }
}

You can use this struct to construct UniqueIDs of any Type and the compiler will ensure you never use the wrong ID type for a function.

#[derive(PartialEq, Debug)]
struct PlayerID;
#[derive(PartialEq, Debug)]
struct ShipID;
#[derive(PartialEq, Debug)]
struct ItemID;

fn main() {
    let player_id = PlayerID::uuid(123);
    let ship_id = ShipID::uuid(345);
    let item_id = ItemID::uuid(567);
    //This function will only accept a PlayerID UniqueID.
    //Anything else will throw a compiler error
    check_player_id(player_id);
}

fn check_player_id(player_id: UniqueID<PlayerID>) {
    assert_eq!(player_id.0, 123);
}