Implementing a trait for every function

I have a trait that has a process function:

pub trait Node {
    type Input;
    type Output;
    
    /// Process a batch of data
    fn process(&mut self, input: Vec<Self::Input>) -> Vec<Self::Output>;
}

I would like all functions with one input to automatically implement this trait, something like this:

impl <I, O, F: Fn(I) -> O> Node for F {
    type Input = I;
    type Output = O;
    fn process(&mut self, input: Vec<Self::Input>) -> Vec<Self::Output> {
        input.into_iter().map(self).collect()
    }
}

but I am receiving this error:

the type parameter `I` is not constrained by the impl trait, self type, or predicates
unconstrained type parameter

I did it with a function pointer, but that then requires ugly (f as fn(I)->O) every time I want to use process(). Is it possible to implement this trait for every function with the Fn() trait?

The type parameters can be constrained by adding them as generic parameters on either the trait or type you are implementing it on.

See: Rust Compiler Error Index (rust-lang.org)

This seems like it would work:

pub trait Node<I, O> {
    type Input;
    type Output;
    
    /// Process a batch of data
    fn process(&mut self, input: Vec<Self::Input>) -> Vec<Self::Output>;
}

impl <I, O, F: Fn(I) -> O> Node<I, O> for F {
    type Input = I;
    type Output = O;
    fn process(&mut self, input: Vec<Self::Input>) -> Vec<Self::Output> {
        input.into_iter().map(self).collect()
    }
}

Yes, I saw that. The problem is that I use Node in many places and adding generic parameters would make types explode in complexity, so that's why I'm using associated types.

Is it at all possible to not have generic types on the trait? I am using them on the type I'm implementing it on (F), but that doesn't seem to be enough.

I don't know how the parenthetical notation for the Fn trait works, but it's some magic that doesn't appear anywhere else in the language (AFAIK). Perhaps Fn(I) -> O desugars to Fn<I, Output = O>, but attempting to use that syntax won't compile because the trait's type parameters are unstable. It does seem like that would constrain the type parameters if that syntax was accepted.

That is how it works (feature unboxed_closures), but it won't directly help here as (a) the translation doesn't lose functionality as far as I know and (b) the parameters are going into a trait bound, not the type itself.

You could put the bounds on the method - but probably the OP won't like this as you'll need to carry the bounds around everywhere you want to make use of it. You could implement for concrete types, like &dyn Fn(...) and Box<Fn(...)>, etc.

1 Like

Slight correction to my last post: The desugaring is Fn<(I,), Output=O>.

This might suffice for your use cases:

impl<'a, I, O> Node for dyn FnMut(I) -> O + 'a {
    type Input = I;
    type Output = O;
    fn process(&mut self, input: Vec<Self::Input>) -> Vec<Self::Output> {
        input.into_iter().map(self).collect()
    }
}

Playground.

Assuming I understand you situation correctly you have some API that works with Node types, e.g.

fn foo<N: Node>(n: N, more_args: i32) {}

and your goal is to avoid the overhead of needing to do foo(f as fn(I)->O, ...) when calling it with a function, which is the possibility you achieved by implementing Node for function pointers. Note by the way that this approach also had another potential disadvantage: Using function pointer types always implies that you're doing dynamic function calls which can have (slight) performance overhead, though probably in the context of processing a whole Vec that's negligible anyways.

So an approach to improve the situation so you at least don't need to spell out the whole type of the function could be something like the following which would at least allow for a call like foo(mapping(f), ...). Perhaps this also makes the API more descriptive, as it describes how the function is going to be used. (Feel free to use whatever name you like instead of "mapping".)

use std::marker::PhantomData;

pub struct Mapping<F, I> {
    function: F,
    marker: PhantomData<fn(I)>,
}

pub fn mapping<I, O, F>(function: F) -> Mapping<F, I> where F: FnMut(I) -> O {
    Mapping {
        function,
        marker: PhantomData,
    }
}

impl<F, I, O> Node for Mapping<F, I> where F: FnMut(I) -> O {
    type Input = I;

    type Output = O;

    fn process(&mut self, input: Vec<Self::Input>) -> Vec<Self::Output> {
        input.into_iter().map(&mut self.function).collect()
    }
}

Then you can use this comfortably, and for concrete function types, Rust will always be able to figure out the input type for you, so to use e.g.

fn transform(x: i32) -> i32 {
    x + 1
}

you don't have to write anything more than

fn demonstration() {
    foo(mapping(transform), 42);
}

(playground)

This is basically what I'm having to do now. Do you think theres a way to do type coercion into mapping() without having to type it? I will be adding a bunch of functions to basically built a compute graph and so it really will cut down on the boilerplate to directly be able to add functions rather than use mapping() everywhere.

That works but still requires the boilerplate of let d0: &mut dyn FnMut(_) -> _ = &mut foo; so while it does support closures, it is more to write than just using the function pointer.

Thanks for the suggestions!

I was going to suggest introducing a kind-of IntoNode<I> trait, but it doesn’t work with the (current) Rust compiler completely the way I hoped it would. (But I’m hoping something like this might become better-supported in the future.)

The idea was that all your methods that expect a Node could instead expect an IntoNode<I> (with an additional type parameter I), then call the conversion function provided by that trait, and work with a Node internally. So you’d need to add extra generic arguments I to some functions, but really only to public-facing convenience-API. It’s a bit similar to Iterator vs IntoIterator.

I.e. a function like

fn foo<N: Node>(n: N, more_args: i32) { /* use n */ }

would become

fn foo<I, N: IntoNode<I>>(n: N, more_args: i32) { let n = n.into_node(); /* use n */ }

The approach I imagined was like this:

pub trait IntoNode<Input> {
    type Output;
    type Node: Node<Input = Input, Output = Self::Output>;
    fn into_node(self) -> Self::Node;
}

with a default implementations for actual Nodes

impl<N: Node> IntoNode<N::Input> for N {
    type Output = N::Output;
    type Node = N;
    fn into_node(self) -> N {
        self
    }
}

And also an implementation for functions


impl<F, I, O> IntoNode<I> for F
where
    F: FnMut(I) -> O,
{
    type Output = O;

    type Node = Mapping<F, I>;

    fn into_node(self) -> Self::Node {
        mapping(self)
    }
}

Unfortunately,

error[E0119]: conflicting implementations of trait `IntoNode<_>`
  --> src/lib.rs:53:1
   |
15 |   impl<N: Node> IntoNode<N::Input> for N {
   |   -------------------------------------- first implementation here
...
53 | / impl<F, I, O> IntoNode<I> for F
54 | | where
55 | |     F: FnMut(I) -> O,
56 | | {
...  |
63 | |     }
64 | | }
   | |_^ conflicting implementation

But I suppose not all is lost. If you commonly want to use functions, and only rarely use values that are already nodes, you could at least invert the situation, requiring no additional glue like a mapping function for functions, and instead requiring actual Nodes to be wrapped, e.g. as node(...) with a function like:

pub struct NodeIntoNode<N>(N);
fn node<N: Node>(n: N) -> NodeIntoNode<N> {
    NodeIntoNode(n)
}

impl<N: Node> IntoNode<N::Input> for NodeIntoNode<N> {
    type Output = N::Output;
    type Node = N;
    fn into_node(self) -> N {
        self.0
    }
}

use-case

fn foo<I, N: IntoNode<I>>(n: N, more_args: i32) { let n = n.into_node(); /* use n */ }

fn demonstration1(n: impl Node) {
    foo(|x: i32| x + 1, 42);
    foo(node(n), 123);
}

(playground)

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.