如何在Rust中编写函数?


76

我正在尝试编写一个包含两个功能的功能。初始设计非常简单:一个函数需要两个函数并返回一个组合函数,然后我可以将其与其他函数组合起来,因为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  | | }
   | |_^

1
至于主要目标,您可能正在寻找这样的东西:stackoverflow.com/q/36284637/1233251
E_net4无需评论的投票者

不适用于我的情况。
Seun LanLege

Answers:


121

正如@ljedrz指出的那样,要使其工作,您只需再次引用组成的函数:

let finally = compose(&*multiply_and_add, &*divide_and_subtract);

(请注意,在Rust中,约定规定变量名称应在snake_case中)


但是,我们可以做得更好!

从Rust 1.26开始,我们可以使用抽象返回类型(以前的特性为gate #![feature(conservative_impl_trait)])。这可以帮助您大大简化示例,因为它可以跳过生存期,引用,Sized约束和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));
}

最后,既然您提到了rest参数,我怀疑您真正想要的是有一种方法可以灵活地链式组合任意数量的函数。我为此编写了此宏:

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

7
根据口味,可能需要impl Trait在参数位置中使用以进一步简化事情。
gnzlbg

1
compose_two并非绝对必要。内联宏函数的工作原理,但是当类型不匹配时可能会产生可怕的编译错误:( $head:expr, $($tail:expr), +) => { |x| compose!($($tail),+)($head(x)) }
Victor Savu

1
听起来是徒劳的,但是熟悉编程的人会发现有点反直觉,按照上面的实现,add_and_multiply实际上应称为multiple_and_add。不错的答案。
Orco

这对具有多个arg的函数有用吗?虽然我猜一个人可以通过元组。Rust没有Haskell那样好的功能,其中多个参数等效于这些参数的元组。
Raskell

12

只需在其中添加引用,finally它将起作用:

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

取消引用addAndMultiplydivideAndSubtract发现不是的特征对象Sized;它需要包装在Box或引用中,以便将其传递给具有Sized约束的函数。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.