Is it possible to have a generic function on a trait?
Asked Answered
R

2

7

I have:

struct Plumbus<'a> {
    grumbo: &'a dyn Grumbo,
}

trait Grumbo {
    fn dinglebop<T>(&self, x: &mut T) -> bool { false }
}

but I get:

error[E0038]: the trait `Grumbo` cannot be made into an object
 --> plumbus.rs:4:5
  |
4 |     grumbo: &'a Grumbo,
  |     ^^^^^^^^^^^^^^^^^^ the trait `Grumbo` cannot be made into an object
  |
  = note: method `dinglebop` has generic type parameters

I want to have dinglebop do nothing by default, but depending on the Grumbo and the T, possibly fill x with a T if it makes sense for that particular Grumbo implementation.

In C++ this could probably be accomplished with partial specialization. I am not sure what to aim for with Rust.

  • Is it possible to have generic functions like this on a trait?
  • How do I accomplish my goal of having a dinglebop() for arbitrary T without specializing my Plumbus for a particular T?
Reber answered 21/4, 2018 at 5:18 Comment(1)
Read this. Is this a possible duplicate?Washing
E
7

Is it possible to have a generic function on a trait?

Yes. But you are then trying to use the trait as an object. If a trait has a generic method then you can't use it as an object, but you can still use it as a bound for a type parameter.

That is, instead of using &'a Gumbo, use a T: Gumbo:

struct Plumbus<'a, T: Gumbo> { 
    grumbo: &'a T,
}

With the trait object, the implementation is only known at runtime. And its generic parameter is part of the implementation type, so the compiler couldn't know how to call it. With T: Gumbo you are putting a constraint on what T can be, but T will always be known by the compiler at the point of use, which includes any parameters of its own.

See also:

Edveh answered 21/4, 2018 at 6:10 Comment(0)
B
0

Many problems in computer science can be solved with one level of indirection, I was told. Coming across the same issue, here is a solution that does not requires to put any new type parameter on Plumbus.

Because dynamic dispatch has only one vtable, and with your function signature you actually need two vtables, the solution is to have your initial function return another object so that the call is performed in two steps, like this:

pub struct Plumbus<'a> { // No extra type parameters
    pub grumbo: &'a dyn Grumbo,
}

pub struct GrumboDinglebo<'a> { // Internal structure
    pub this: &'a dyn Grumbo
}
impl <'a> GrumboDinglebo<'a> {
  pub fn apply<T>(&self, _x: &mut T) -> bool {
      false
  }
}

pub trait Grumbo {
    // Replace fn dinglebop<T>(&self, x: &mut T) -> bool { false } with this:
    fn dinglebop(&self) -> &GrumboDinglebo;
}

At call site, replace anything you'd have written like

grumbo.dinglebox::<X>(self, xmutT)

which does not work, by

grumbo.dinglebox(self).apply::<X>(xmutT)

which works as expected.

Note, if you wanted your method dinglebox to also be abstract, I don't have yet a solution for that

Billings answered 12/6 at 15:9 Comment(1)
This is the same as can be demonstrated by a free function dinglebop<T>(g: &dyn Grumbo, t: &mut T) where it is more clear that it is limited to only access via &dyn Grumbo and not a concrete implementation. It can be the solution if the implementation is uniform across &dyn Grumbos or if special cases can be determined through that interface.Bornu

© 2022 - 2024 — McMap. All rights reserved.