Using trait type parameter as a function

I wish to use a type parameter as a function. I first tried a struct but struct type parameters must be used as members and I don't wish to actually instantiate the parameter.

I've made a minimal example to show what I'm trying to do, but this doesn't compile:

struct Thing{}

trait BestThingFinder {
    fn best(things: &[Thing]) -> Thing;
}

trait ThingNorm {
    fn norm(thing: &Thing) -> f32;
}

trait NormedBestThingFinder<Norm : ThingNorm> {}

impl BestThingFinder for NormedBestThingFinder<Norm> {
    fn best(things: &[Thing]) -> Thing {
        // Find best thing with Norm::norm() ...
    }
}

The NormedBestThingFinder should be able to take some Norm and use it to return the best thing.

It's not clear based on the code what you are actually trying to do. You are trying to implement a trait for another trait, which doesn't make sense.

Are you trying to create a blanket impl? I.e.:


impl<T, Norm> BestThingFinder for T
where
    Norm: THingNorm,
    T: NormedBestThingFinder<Norm>
{
    fn best(things: &[Thing]) -> Thing {
        ...
    }
}

Thanks for your quick response. I've tried what you're suggesting before, but that doesn't work.

13 | impl<T, Norm> BestThingFinder for T
   |         ^^^^ unconstrained type parameter

In general I would like to create a Type NormedBestThingFinder, that takes a Norm as an argument and implements BestThingFinder. This way I would be able to create multiple different NormedBestThingFinders with different norms.

Another thing that I've tried is to use a struct with a dummy member. That seems to compile but I'm not sure that's a proper solution. Since I only want Norm to be used statically, I don't want Norm to be a (member) object.

struct NormedBestThingFinder<Norm: ThingNorm> {
    _marker: std::marker::PhantomData<Norm>,
}

impl<Norm: ThingNorm> BestThingFinder for NormedBestThingFinder<Norm> {
    fn best(things: &[Thing]) -> Thing {
      //...
      Thing{}
    }
}

Using the two Finder traits was a little too much for my brain, but when I just use NormedBestThingFinder I was able to get it to compile.

I realize this may not solve your problem, but perhaps it is a step in that direction.

I made Thing Copy so it can be copied out of the array.

#[derive(Copy, Clone)]
struct Thing {}

trait ThingNorm {
    fn norm(thing: &Thing) -> f32;
}

trait NormedBestThingFinder<Norm: ThingNorm> {
    fn best(things: &[Thing]) -> Thing;
}

impl<Norm: ThingNorm, T: NormedBestThingFinder<Norm>>
    NormedBestThingFinder<Norm> for T
{
    fn best(things: &[Thing]) -> Thing {
        // Find best thing with Norm::norm() ...
        *things
            .iter()
            .max_by(|thing1, thing2| {
                f32::total_cmp(&Norm::norm(thing1), &Norm::norm(thing2))
            })
            .unwrap()
    }
}

Edit: removed NormedBestThingFinder self param.

That's a nice first step. But how would I generalize it to the nested traits?

If I try to use your code and implement the BestThingFinder with it I'm stuck with the same compilation error again:

#[derive(Copy, Clone)]
struct Thing {}

trait ThingNorm {
    fn norm(thing: &Thing) -> f32;
}

trait NormedBestThingFinder<Norm: ThingNorm> {
    fn best(things: &[Thing]) -> Thing;
}

trait BestThingFinder {
    fn best(things: &[Thing]) -> Thing;
}

impl<Norm: ThingNorm, T: NormedBestThingFinder<Norm>> NormedBestThingFinder<Norm> for T {
    fn best(things: &[Thing]) -> Thing {
        // Find best thing with Norm::norm() ...
        *things
            .iter()
            .max_by(|thing1, thing2| f32::total_cmp(&Norm::norm(thing1), &Norm::norm(thing2)))
            .unwrap()
    }
}

impl<Norm, T> BestThingFinder for T
where
    Norm: ThingNorm,
    T: NormedBestThingFinder<Norm>,
{
    fn best(things: &[Thing]) -> Thing {
        T::best(things)
    }
}
26 | impl<Norm, T> BestThingFinder for T
   |      ^^^^ unconstrained type parameter

Then why are you making a trait and not a type, if what you need is a type?

struct NormedBestThingFinder<Norm>(Norm);

impl<Norm: ThingNorm> NormedBestThingFinder<Norm> {
    fn best(things: &[Thing]) -> Thing {
        ...
    }
}
2 Likes

I don't have a solution for that.

Can you give an example of calling the trait method with a fully concrete type? It would help to think about it and experiment. I find it strange that you have no self params.

This works, but as far as I understand this means NormedBestThingFinder holds an instance of a Norm trait object. That's what I originally wanted to avoid. I want my Type to take Norm as a type parameter and not as an object. The best workaround I found is using PhantomData, but I thought there might be a nicer solution using traits.

struct NormedBestThingFinder;

impl NormedBestThingFinder {
    fn best<Norm: ThingNorm>(things: &[Thing]) -> Thing {
        ...
    }
}
1 Like

Would be nice, but it doesn't implement BestThingFinder. I think NormedBestThingFinder needs to take a Type argument.

I think I haven't been quite clear about what I wanted to achieve. I've found a solution using PhantomData similar to @paramagnetic's suggestion, but preferably I would have a solution without any unused placeholder struct members.

Here's a more complete example of how the trait can be used:

#[derive(Clone, Debug)]
struct Thing {
    size: f32,
}

trait BestThingFinder {
    fn best(&self, things: &[Thing]) -> Thing;
}

trait ThingNorm {
    fn norm(thing: &Thing) -> f32;
}

#[derive(Default)]
struct NormedBestThingFinder<Norm: ThingNorm> {
    _marker: std::marker::PhantomData<Norm>,
}

impl<Norm: ThingNorm> BestThingFinder for NormedBestThingFinder<Norm> {
    fn best(&self, things: &[Thing]) -> Thing {
        things
            .iter()
            .max_by(|thing1, thing2| f32::total_cmp(&Norm::norm(thing1), &Norm::norm(thing2)))
            .unwrap()
            .clone()
    }
}

// Example usage:

// Example 1:

#[derive(Default)]
struct FirstThingFinder {}

impl BestThingFinder for FirstThingFinder {
    fn best(&self, things: &[Thing]) -> Thing {
        things[0].clone()
    }
}

// Example 2:

#[derive(Default)]
struct SmallestNorm {}

impl ThingNorm for SmallestNorm {
    fn norm(thing: &Thing) -> f32 {
        -thing.size
    }
}

type SmallestThingFinder = NormedBestThingFinder<SmallestNorm>;

// Example 3:

#[derive(Default)]
struct BiggestNorm {}

impl ThingNorm for BiggestNorm {
    fn norm(thing: &Thing) -> f32 {
        thing.size
    }
}

type BiggestThingFinder = NormedBestThingFinder<BiggestNorm>;

// Combined usage:

fn main() {
    let things = vec![
        Thing { size: 2.0 },
        Thing { size: 1.0 },
        Thing { size: 3.0 },
    ];
    let mut thing_finder: Box<dyn BestThingFinder> = Box::new(SmallestThingFinder {
        ..Default::default()
    });
    println!("SmallestThingFinder {:?}", thing_finder.best(&things));
    thing_finder = Box::new(BiggestThingFinder {
        ..Default::default()
    });
    println!("BiggestThingFinder {:?}", thing_finder.best(&things));
    thing_finder = Box::new(FirstThingFinder {
        ..Default::default()
    });
    println!("FirstThingFinder {:?}", thing_finder.best(&things));
}

Using a PhantomData field is quite common and has no significant drawbacks, so I suggest becoming accustomed to using it. It is zero-sized so it is only used by the type system -- it doesn't exist at runtime. You would need a self parameter anyway, since trait objects require one.

PhantomData is an unnecessary annoyance if you have a zero-sized marker type that you can just make up instances of. That's exactly why I didn't use a PhantomData. Just make the marker type Default or whatever, and instantiate it, it's trivial.

I unfortunately failed to understand what's wrong with storing an instance. If you are using the norm as a purely type-level marker, you shouldn't make it anything but a zero-sized type (likely a unit struct) that you can instantiate without any additional requirements.

That's a good point, in my effort to do away with a marker type I completely missed that it's zero-sized either way. So just using the trait object itself as suggested by @paramagnetic is the cleaner solution. Thanks for pointing that out!

There is no trait object. It's all generic code.

Ok I'm not sure what's the proper terminology. I meant the marker type.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.