Using PhantomData to deal with unused type parameter


#1

Consider the following trait, that is(as the name suggest) intended to represent types that contain a continuous sequence of T’s:

pub trait Array<T> {
	fn as_slice(&self) -> &[T];
	fn as_mut_slice(&mut self) -> &mut [T];
}

The best way I could come up with to make a struct that contains data of a type that implements this trait, while also being generic about the generic type T of the trait is:

struct Foo<T, A: Array<T>> {
	_marker: PhantomData<T>,
	data: A
}

Is there a better way? From what I’ve read about PhantomData, it’s intended to deal with situations where data is stored in a custom way(like with raw pointers), but in this case, all data is stored in a normal, safe-rust way.


#2

If you intend for an impl to only impl Array for a single type, then you can use associated types:

trait Array { type Item; // rest of your functions }
struct Foo<T, A: Array<Item=T>> { a: A }

#3

Hmm, that works, but then you can’t say that it has to implement another trait that requires the type T:

pub trait Array<T> : Deref<Target=[T]> {
    //...
}

#4

Indeed, this won’t work (and saying : Deref<Target=[Self::Item]> creates circularity). See my subsequent reply.

So the original formulation you had was fine as well (and if you wanted a given type to impl Array for different Ts, the associated type formulation wouldn’t work either). PhantomData is used whenever generic types (or lifetime parameters) don’t appear in the fields of a struct; one case, albeit common, is when fields aren’t using the type directly. But another usage, a bit less common probably, is for type level concepts only (i.e. no runtime storage of it). Some examples of that are “session types” where a generic parameter captures the state of a type but otherwise has no “physical” representation. Another example is something like https://github.com/paholg/typenum.

So, I wouldn’t necessarily avoid PhantomData - it’s just a matter of whether you want to represent something only at the type level or not.


#5

I take it back - you can say : Deref<Target=<Self as Array>::Item> and use the associated type. Although I must say I don’t quite get why this syntax works but not the Self::Item one.


#6

Ok thanks! That was very insightfull, the type system can get very confusing at times. It just seemed like I was missing something, but I guess not.


#7

Yeah, it can. In this case, it didn’t seem right that using an associated type should preclude using the generic (associated) type to require another generic trait to be implemented. I am, however, curious why Deref<Target=[Self::Item]> doesn’t work but Deref<Target=[<Self as Array>::Item]> does, as mentioned. If nobody replies here, I’ll try to follow up with a github issue and hopefully get someone to enlighten over there.


#8

It’s been reported several times (and I’ve also personally ran into it multiple times :P) https://github.com/rust-lang/rust/issues/35237


#9

Great, thanks!