I'm trying to write a function that composes two functions. The initial design is pretty simple: a function that takes two functions and returns a composed function which I can then compose with other functions, since Rust doesn't have rest parameters. I've run into a wall built with frustrating non-helpful compiler errors.
My compose function:
fn compose<'a, A, B, C, G, F>(f: F, g: G) -> Box<Fn(A) -> C + 'a> where F: 'a + Fn(A) -> B + Sized, G: 'a + Fn(B) -> C + Sized, { Box::new(move |x| g(f(x))) }
How I would like to use it:
fn main() { let addAndMultiply = compose(|x| x * 2, |x| x + 2); let divideAndSubtract = compose(|x| x / 2, |x| x - 2); let finally = compose(*addAndMultiply, *divideAndSubtract); println!("Result is {}", finally(10)); }
The compiler doesn't like that, no matter what I try, the trait bounds are never satisfied. The error is:
error[E0277]: the size for values of type `dyn std::ops::Fn(_) -> _` cannot be known at compilation time --> src/main.rs:13:19 | 13 | let finally = compose(*addAndMultiply, *divideAndSubtract); | ^^^^^^^ doesn't have a size known at compile-time | = help: the trait `std::marker::Sized` is not implemented for `dyn std::ops::Fn(_) -> _` = note: to learn more, visit <https://doc.rust-lang.org/book/ch19-04-advanced-types.html#dynamically-sized-types-and-the-sized-trait> note: required by `compose` --> src/main.rs:1:1 | 1 | / fn compose<'a, A, B, C, G, F>(f: F, g: G) -> Box<Fn(A) -> C + 'a> 2 | | where 3 | | F: 'a + Fn(A) -> B + Sized, 4 | | G: 'a + Fn(B) -> C + Sized, 5 | | { 6 | | Box::new(move |x| g(f(x))) 7 | | } | |_^
It means the function never returns (usually because it unconditionally panics or otherwise ends the program, or because it contains an infinite loop that prevents a return from ever happening). The appendix describes it as: ! Always empty bottom type for diverging functions.
Functions are the building blocks of readable, maintainable, and reusable code. A function is a set of statements to perform a specific task. Functions organize the program into logical blocks of code.
Rust does have a way to write a main function that returns a value, however it is normally abstracted within stdlib.
As @ljedrz points out, to make it work you only need to reference the composed functions again:
let finally = compose(&*multiply_and_add, &*divide_and_subtract);
(Note that in Rust, convention dictates that variable names should be in snake_case)
However, we can make this better!
Since Rust 1.26, we can use abstract return types (previously featured gated as #![feature(conservative_impl_trait)]
). This can help you simplify your example greatly, as it allows you to skip the lifetimes, references, Sized
constraints and Box
es:
fn compose<A, B, C, G, F>(f: F, g: G) -> impl Fn(A) -> C where F: Fn(A) -> B, G: Fn(B) -> C, { move |x| g(f(x)) } fn main() { let multiply_and_add = compose(|x| x * 2, |x| x + 2); let divide_and_subtract = compose(|x| x / 2, |x| x - 2); let finally = compose(multiply_and_add, divide_and_subtract); println!("Result is {}", finally(10)); }
Finally, since you mention rest parameters, I suspect that what you actually want is to have a way to chain-compose as many functions as you want in a flexible manner. I wrote this macro for this purpose:
macro_rules! compose { ( $last:expr ) => { $last }; ( $head:expr, $($tail:expr), +) => { compose_two($head, compose!($($tail),+)) }; } fn compose_two<A, B, C, G, F>(f: F, g: G) -> impl Fn(A) -> C where F: Fn(A) -> B, G: Fn(B) -> C, { move |x| g(f(x)) } fn main() { let add = |x| x + 2; let multiply = |x| x * 2; let divide = |x| x / 2; let intermediate = compose!(add, multiply, divide); let subtract = |x| x - 2; let finally = compose!(intermediate, subtract); println!("Result is {}", finally(10)); }
Just add references in finally
and it will work:
fn main() { let addAndMultiply = compose(|x| x * 2, |x| x + 2); let divideAndSubtract = compose(|x| x / 2, |x| x - 2); let finally = compose(&*addAndMultiply, &*divideAndSubtract); println!("Result is {}", finally(10)); }
Dereferencing addAndMultiply
or divideAndSubtract
uncovers a trait object which is not Sized
; it needs to either be wrapped in a Box
or referenced in order for it to be passed to a function with a Sized
constraint.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With