如何在 Rust 中编写函数?

我尝试写一个由两个函数组成的函数。最初的设计非常简单: 一个函数接受两个函数并返回一个组合函数,然后我可以与其他函数组合,因为 Rust 没有静止参数。我遇到了一个无用的编译器错误。

我的作曲功能:

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)))
}

我想如何使用它:

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));
}

编译器不喜欢这样,不管我怎么尝试,特性界限永远不会满足。错误是:

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  | | }
| |_^
16602 次浏览

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.

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 Boxes:

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));
}
macro_rules! comp {
($f: expr) => {
move |g: fn(_) -> _| move |x: _| $f(g(x))
};
}


fn main() {
let add1 = |x| x + 1;
let add2 = |x| x + 2;
let add3 = comp!(add1)(add2);
println!("{}", add3(3));
}

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=1c6915d94f7e1e35cf93fb21daceb9ef