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 Box
ing 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()
}
}