Is this kind of polymorphism supported in Rust


#1

Good day!

Warning: just starting to learn Rust, a beginner, although like it a lot so far.

I faced a problem with generic parametrisation and not sure if it’s possible to express it Rust. Below comes very simplified example showing the problem.

#[test]
fn it_works() {
    assert_eq!(239, generic_apply(f, I32(239)));
}

pub trait ToI32 {
    fn to_i32(&self) -> i32;
}

pub struct I32(i32);

pub struct I32B(i32);

impl ToI32 for I32 {
    fn to_i32(&self) -> i32 {
        self.0
    }
}

impl ToI32 for I32B {
    fn to_i32(&self) -> i32 {
        self.0
    }
}

pub fn convert<Arg: ToI32>(a: Arg) -> I32B {
    I32B(a.to_i32())
}

#[cfg(feature = "dynamic")]
pub fn generic_apply<F, Arg>(f: F, a: Arg) -> i32
    where Arg: ToI32,
          F: Fn(&ToI32) -> i32
{
    f(&convert(a) as &ToI32)
}

#[cfg(feature = "dynamic")]
pub fn f(a: &ToI32) -> i32 {
    a.to_i32()
}

#[cfg(feature = "generic")]
pub fn generic_apply<F, Arg>(f: F, a: Arg) -> i32
    where Arg: ToI32,
          F: Fn(/* What to put here??? */) -> i32,
{
    f(convert(a))
}

#[cfg(feature = "generic")]
pub fn f<Arg: ToI32>(a: Arg) -> i32 {
    a.to_i32()
}

dynamic case is fine, but imagine that I want to optimise dynamic invocation via trait object. To achieve that I try to pass generic function (see case of generic) which can operate on any I32 object. And here comes my problem: I don’t want to fix the actual type of the only argument passed to f—it may change due to implementation change, for example, if I remove convert in generic_apply body (in my real case I want to pass something which implements Iterator into f, but as I can change my chain of iterators, I don’t want to change the signature due to it.) So, I want roughly Fn(* : I32) meaning some unspecified type as f is generic anyway.

My immediate feeling it is not possible in Rust and may, for example, render separate compilation impossible. But maybe I miss something obvious.

Thank you very much in advance!
cheers,
anton.


#2

You want higher kinded types (HKT), a feature that doesn’t currently exist in Rust but which is fairly heavily desired.

There are, however, hacky ways to encode this sort of thing into the type system:


#3

Thank you very much!


#4

Just to close the loop. Here comes updated variant which apparently provides reasonable solution, and I really hope there is no dynamic dispatch now, but would appreciate if told how to double check. Note that I slightly modified body of generic_apply to better illustrate problem: now it models f(convert(a)) + f(a).

pub fn generic_apply<F, Arg>(ff: F, a: Arg) -> i32
    where Arg: ToI32,
          F: ToI32Able,
{
    ff.call(convert(&a)) + ff.call(a)
}

pub trait ToI32Able {
    fn call<Arg: ToI32>(&self, a: Arg) -> i32;
}

#[allow(non_camel_case_types)]
pub struct f;

impl ToI32Able for f {
    fn call<Arg: ToI32>(&self, a: Arg) -> i32 {
        a.to_i32()
    }
}

#5

I struggled with this for at least 20 minutes before I came to the conclusion that it shouldn’t be possible, and that’s a good thing. Statically resolved functions are distinct copies in memory. All “holes” have been filled by the time the body must be executed, so there’s no way to reinstantiate it differently.

You can fake it, though, by passing in the function twice and letting it get instantiated differently for each parameter. Your generic function then becomes “overly” generic, which is slightly bothersome, but preserves the semantics of Rust and its template-like instantiation. If you want to obscure the fact that you’re instantiating something twice, you can even write a macro for it. Code here: http://is.gd/C2EZly

struct A;
struct B;

trait Tr { 
    fn convert(&self) -> B {
        B
    }
}

impl Tr for A { }
impl Tr for B { }

fn f<A: Tr>(_: A) { }

fn poly<F, G, T: Tr>(f: F, g: G, a: T) -> () 
    where F: Fn(T) -> (),
          G: Fn(B) -> ()
{
    g(a.convert());
    f(a);
}

macro_rules! poly {
    ($f: ident, $a: expr) => {poly($f, $f, $a)}
}

fn main() { 
    let (a1, a2) = (A, A);
    poly(f, f, a1); // By duplicating
    poly!(f, a2);   // By macro
}

Notice that the final line, poly!(f, a2, is precisely what you wanted to write. So I lied at the beginning of this post. This is definitely possible, although I wouldn’t call copy-and-paste preprocessing “polymorphism”. More like “fool the compiler”. What would be nice would be to have first class templates, where I can pass in f (which is really just a function template), and instantiate it in many different ways internally.

If you’re going to use this trick, tip for refactoring: since changing the implementation of the polymorphic function involves changing its generic parameters, program to the macro instead of the function.