How to Implement Copy for newtype of Fn?

I am new to Rust, and am working on a project as a learning experience. In the project I want to be able to do the following:

fn main() {
    let a = FnType::new(|x| x + 1);
    let b = FnType::new(|x| x + 10);
    let c = a + b;
    let d = c + a + a + c;
    print!("Result: {}", c.call(100));
    print!("Result: {}", d.call(100));
}

I want to be able to compose functions using +. Here is my likely naive attempt at implementing this:

pub struct FnType(&'static dyn Fn(usize) -> usize);

impl FnType {
    pub fn new(f: impl Fn(usize) -> usize + 'static) -> FnType {
        FnType(&f)
    }

    pub fn call(&self, u: usize) -> usize {
        (self.0)(u)
    }
}

impl Copy for FnType {}

impl Clone for FnType {
    fn clone(&self) -> Self {
        Self(self.0.clone())
    }
}

impl Add for FnType {
    fn add(self, other: FnType) -> FnType {
        FnType::new(|x| (other.0)((self.0)(x)))
    }

    type Output = FnType;
}

The main thing is to be able to implement Copy for FnType, as this is what would allow the clean API, but this is not possible unless I have a reference to Fn as Copy cannot be implemented for Fn.

In the code above the FnType::new function does not work because it says that f does not live long enough.

What do I need to do so that I can implement this with a clean API, being able to utilize Copy on a newtype for Fn?

You can't implement Copy manually. It is always implemented by [derive(Copy)]. In most cases, you want to derive both Clone and Copy.

No, Copy can be implemented manually, however not for arbitrary types, but only ones where every field is Copy, too.

4 Likes

Thanks for the correction, I don't know where I got this from. The docs explain it really well.

Anyway, to solve your real problem, I think it's best to replace &static Fn(usize) -> usize with a function pointer fn(usize) -> usize, which are Copy.

A function pointer will not accept closures. Fn will take closures and function pointers.

As the add implementation involves a capturing closure, it’s hard to propose anything “better” than Box<dyn Fn…> for the inner type. Any form of Boxing does hover automatically preclude Copy. Perhaps you’re fine with only having Clone though. For trait objects, this can be achieved e.g. via the crate dyn_clone - Rust.

Here’s some example code

/*
[dependencies]
dyn-clone = "1"
*/

use std::ops::Add;

use dyn_clone::{DynClone, clone_trait_object};

trait FnTypeTrait: Fn(usize) -> usize + DynClone {}
impl<T: Fn(usize) -> usize + Clone> FnTypeTrait for T {}
clone_trait_object!(FnTypeTrait);

fn main() {
    let a = &FnType::new(|x| x + 1);
    let b = &FnType::new(|x| x + 10);
    let c = &(a + b);
    let d = &(c + a + a + c);
    println!("Result: {}", c.call(100));
    println!("Result: {}", d.call(100));
}

#[derive(Clone)]
pub struct FnType(Box<dyn FnTypeTrait>);

impl FnType {
    pub fn new(f: impl Fn(usize) -> usize + 'static + Clone) -> FnType {
        FnType(Box::new(f))
    }

    pub fn call(&self, u: usize) -> usize {
        (self.0)(u)
    }
}

impl Add for FnType {
    fn add(self, other: FnType) -> FnType {
        FnType::new(move |x| (other.0)((self.0)(x)))
    }

    type Output = FnType;
}
impl Add<&FnType> for FnType {
    fn add(self, other: &FnType) -> FnType {
        let other = other.clone();
        FnType::new(move |x| (other.0)((self.0)(x)))
    }

    type Output = FnType;
}
impl Add<FnType> for &FnType {
    fn add(self, other: FnType) -> FnType {
        let this = self.clone();
        FnType::new(move |x| (other.0)((this.0)(x)))
    }

    type Output = FnType;
}
impl Add<&FnType> for &FnType {
    fn add(self, other: &FnType) -> FnType {
        let this = self.clone();
        let other = other.clone();
        FnType::new(move |x| (other.0)((this.0)(x)))
    }

    type Output = FnType;
}

Rust Explorer

2 Likes

It will, as long as they don't capture anything. That rules out the Add implementation in this case, though.


One option is to store Rc<dyn Fn...>. This won't let you implement Copy, but Clone will always work:

use std::rc::Rc;

#[derive(Clone)]
pub struct FnType(Rc<dyn Fn(usize) -> usize>);

impl FnType {
    pub fn new(f: impl 'static + Fn(usize) -> usize) -> FnType {
        FnType(Rc::new(f))
    }

    pub fn call(&self, u: usize) -> usize {
        (self.0)(u)
    }
}

impl std::ops::Add for FnType {
    fn add(self, other: FnType) -> FnType {
        FnType::new(move |x| other.call(self.call(x)))
    }

    type Output = FnType;
}

impl<'a> std::ops::Add<&'a FnType> for FnType {
    type Output = FnType;
    fn add(self, other: &'a FnType)->FnType {
        self + other.clone()
    }
}

impl std::ops::Add<FnType> for &'_ FnType {
    type Output = FnType;
    fn add(self, other: FnType)->FnType {
        self.clone() + other
    }
}

impl<'a> std::ops::Add<&'a FnType> for &'_ FnType {
    type Output = FnType;
    fn add(self, other: &'a FnType)->FnType {
        self.clone() + other.clone()
    }
}


fn main() {
    let a = FnType::new(|x| x + 1);
    let b = FnType::new(|x| x + 10);
    let c = &a + &b;
    let d = &c + &a + &a + &c;
    print!("Result: {}", c.call(100));
    print!("Result: {}", d.call(100));
}
3 Likes

Ah, yes, Rc is an even better idea than dyn_clone.

This is perfect! I had the same idea of making the operands to + references, but I could not figure out how to implement Add in a way in which they would chain.

Can you explain what the lifetime '_ does?

Not much; it's just an anonymous lifetime that you can use in some situations where you don't need to refer to it somewhere else. This:

impl<'a> std::ops::Add<&'a FnType> for &'_ FnType {
    type Output = FnType;
    fn add(self, other: &'a FnType)->FnType {
        self.clone() + other.clone()
    }
}

Is equivalent to this:

impl<'a,'b> std::ops::Add<&'a FnType> for &'b FnType {
    type Output = FnType;
    fn add(self, other: &'a FnType)->FnType {
        self.clone() + other.clone()
    }
}

Got it. Thank you!

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.