4
votes

In Rust, I want to use a phantom type to properly type a simple id:

struct Id<T> {
    val: u32,
    _type: PhantomData<T>,
}

In a first draft version, I used concrete structs as T, all was well. Then in a more elaborate version using differents data sources, those structs became traits. Let's say:

trait MyArticle {
    fn get_id() -> Id<MyArticle>;
}

But using traits as phantom types brings problems:

  • The compiler makes me declare T: ?Sized, as if the size of T was potentielly needed. I could live with that, but as the purpose of PhantomData<T> is to tell that T won't be used, I wonder if there is some other way?
  • I get the warning: "trait objects without an explicit 'dyn' are deprecated". I can get rid of it with global #![allow(bare_trait_objects)], but this warning is otherwise useful and I don't want to do that. Is there a way to allow bare_trait_object only "when used as the type parameter for Id<T>"?

My current solution is to duplicate name types between an empty struct and a trait:

struct MyArticle_ {};

trait MyArticle {
    fn get_id() -> Id<MyArticle_>;
}

This is awkward but I could not find better.

2
I suspect you want fn get_id() -> Id<Self>;Jmb
Good question, but actually no. The typing of Ids is quite everywhere in the code using the traits; there are several kinds of "articles" that each is a trait, but the relationships beween those articles are well-known. It would be tedious to let everything get a generic or associated type only to be able to use it as a phantom type.Shadok

2 Answers

0
votes

The problem with your sample lies in understanding what the trait is. And in fact it is not a type (that's why compiler asks for T: ?Sized), but a requirement for a type. Thus the solution is fairly simple: come up with a "real" type. You got it right with a struct declaration, it can be one option. But usually it's much more convenient to use associative type for that:

trait MyArticle {
    type T;

    fn get_id() -> Id<Self::T>
    where
        Self::T: MyArticle;
}

// so given impls
struct X;

impl MyArticle for X {
    type T = u32;
    fn get_id() -> Id<u32> {
        todo!()
    }
}

impl MyArticle for u32 {
    type T = u32;
    fn get_id() -> Id<u32> {
        todo!()
    }
}

So finally, you're able to call X::get_id(), or a fully-qualified version: <X as MyArticle>::get_id()

Also, you may read there why fn get_id() -> Id<Box<dyn MyArticle>> doesn't work.

0
votes

The problem here is that traits are not themselves types, although dyn Traits are. So when you write Id<MyArticle>, it actually means Id<dyn MyArticle> (hence the warning), and it wouldn't compile if MyArticle is not object safe.

In this particular case you can make MyArticle object-safe:

use std::marker::PhantomData;

struct Id<T: ?Sized> {
    val: u32,
    _type: PhantomData<T>,
}

trait MyArticle {
    fn get_id() -> Id<dyn MyArticle> where Self: Sized;
}

If you couldn't or don't want to make the trait object-safe, then I think your empty struct solution is the way to go. Note that if you just need an empty struct you can use just write struct MyArticle_;.