Policy-based design and phantoms

Hi,

I would like to do some policy-based design in Rust (à la Alexandrescu).

The following code works:

use std::fmt::Display;
use std::marker::PhantomData;

trait SomeTrait<T> {
    fn do_something(&self, value: T);
}

struct ConcretePolicy<T> {
    field: PhantomData<T>
}

impl<T: Display> SomeTrait<T> for ConcretePolicy<T> {
    fn do_something(&self, value: T) {
        println!("Hello, {}!", value);
    }
} 

struct MyStruct<T, Policy: SomeTrait<T>> {
    politics: Policy,
    my_type: PhantomData<T>, // why is this required?
}

impl<T, Policy: SomeTrait<T>> MyStruct<T, Policy> {
    fn do_something(&self, value: T) {
        self.politics.do_something(value);
    }
}

fn main() {
    let my_struct = MyStruct {
        politics: ConcretePolicy::<&str>{
            field: PhantomData
        },
        my_type: PhantomData
    };
    my_struct.do_something("world");
}

If I remove the phantom field in MyStruct, the compiler complains that parameter T is never used. But I think it is used in SomeTrait<T>.

Am I missing anything? Are nested generics possible? Is there a simpler way to do policy-based design?

Thanks.

1 Like

I have no idea about the topic other than your example, but perhaps something like this works?

use std::fmt::Display;
use std::marker::PhantomData;
trait HasTarget {
    type Target;
}
trait SomeTrait: HasTarget {
    fn do_something(&self, value: Self::Target);
}

pub struct ConcretePolicy<T> {
    field: PhantomData<*const T>
}
impl<T> ConcretePolicy<T> {
    pub fn new() -> ConcretePolicy<T> {
        ConcretePolicy { field: PhantomData }
    }
}
impl<T> HasTarget for ConcretePolicy<T> {
    type Target = T;
}

impl<T: Display> SomeTrait for ConcretePolicy<T> {
    fn do_something(&self, value: T) {
        println!("Hello, {}!", value);
    }
}


struct MyStruct<Policy: SomeTrait> {
    politics: Policy,
}

impl<Policy: SomeTrait> MyStruct<Policy> {
    fn do_something(&self, value: Policy::Target) {
        self.politics.do_something(value);
    }
}

fn main() {
    let my_struct = MyStruct {
        politics: ConcretePolicy::<&str>::new(),
    };
    my_struct.do_something("world");
}

This question looks like it's better suited for https://users.rust-lang.org/ (which is for general help using Rust). This forum is focused on developing Rust itself (both the language and the core tools like the compiler).

3 Likes

The reason for this error is that generic types have a variance in each of their parameters, which is inferred from how each parameter is used in the type's definition: https://doc.rust-lang.org/nomicon/subtyping.html

Variance matters because it determines how MyStruct<T> and MyStruct<U> are related, via subtyping. For example, if you have two lifetimes 'a and 'b where 'a outlives 'b, then you may want to be able to pass a MyStruct<&'a i32> where a MyStruct<&'b i32> is expected.

This is fine if MyStruct<T> uses T directly, as in field: T. But if MyStruct<T> instead contains a field: fn(T), this relationship must be reversed, and you would instead want to pass a MyStruct<&'b i32> where a MyStruct<&'a i32> is expected.

Because variance is inferred from how parameters are used, for a use to "count" it must force a particular kind of variance. PhantomData pretends, for the purpose of inferring variance, to be its parameter- which is how you specify variance for parameters that aren't otherwise used in that way.

3 Likes

Thanks for your answers. The solution of steffahn with associated types is what I was looking for.