当函数需要引用闭包参数时,如何将函数用作闭包?

时间:2018-02-15 14:18:11

标签: reference rust closures

以下代码适用(cargo +nightly run)罚款:

fn main() {
    let res: Result<(), String> = Err(String::from("hi"));
    println!("{}", res.map_err(shout).unwrap_err())
}

fn shout(s: String) -> String {
    s.to_ascii_uppercase()
}

Clippy(cargo +nightly clippy)吐出一个(合理的)警告:

warning: this argument is passed by value, but not consumed in the function body
 --> src/main.rs:6:13
  |
6 | fn shout(s: String) -> String {
  |             ^^^^^^ help: consider changing the type to: `&str`

将代码更改为建议的版本

fn shout(s: &str) -> String {
    s.to_ascii_uppercase()
}

导致编译器错误:

error[E0631]: type mismatch in function arguments
 --> src/main.rs:3:24
  |
3 |     println!("{}", res.map_err(shout).unwrap_err())
  |                        ^^^^^^^ expected signature of `fn(std::string::String) -> _`
...
6 | fn shout(s: &str) -> String {
  | --------------------------- found signature of `for<'r> fn(&'r str) -> _`

正确的反应方式是什么?当然,我可以简单地做#![cfg_attr(feature="clippy", allow(needless_pass_by_value))],但这对我来说是错误的。有没有办法使用map_err版本shout作为参考?

1 个答案:

答案 0 :(得分:4)

你能做的最好就是使用全封闭:

res.map_err(|x| shout(&x)).unwrap_err()

您的原始表单需要两个步骤才能工作:

  1. 需要将参数带到闭包并将其转换为引用。
  2. 需要将&String转换为&str
  3. 此外,当值在范围内时,它需要同时执行这两个操作,因此它不会以悬空引用结束。这些都不是闭合处理的“短”形式的东西 - 类型必须完全匹配。

    如果确实希望避免关闭,您可以针对此特定情况:

    res.as_ref().map_err(String::as_str).map_err(shout).unwrap_err()
    //  ^~~~~~           ^~~~~~~~~~~~~~
    //  |                |
    //  |                |- Convert `&String` to `&str`
    //  |   
    //  |- Get a reference (`&String`)   
    

    我实际上是argued for the ability for your original code to work作为人体工程学计划的一部分,但它似乎没有获得牵引力。

    与编程中的许多问题一样,您可以通过添加更多抽象来“解决”这个问题。在这里,我们引入一个特征来体现“可以大喊的错误”的概念:

    fn main() {
        let e1 = Err::<(), _>(String::from("hi"));
        println!("{}", e1.map_err(ShoutyError::shout).unwrap_err());
    
        let e2 = Err::<(), _>(42);
        println!("{}", e2.map_err(ShoutyError::shout).unwrap_err());
    }
    
    trait ShoutyError {
        fn shout(self) -> String;
    }
    
    impl ShoutyError for String {
        fn shout(self) -> String {
            self.to_ascii_uppercase()
        }
    }
    
    impl ShoutyError for i32 {
        fn shout(self) -> String {
            format!("I YELL {}", self)
        }
    }
    

    如果您觉得需要它,您还可以使用包装函数来保留确切的初始代码:

    fn shout<E: ShoutyError>(e: E) -> String {
        e.shout()
    }
    
      

    我希望函数adapt使用一个函数f : &T -> U并返回一个新函数g : T -> U

    这是可能的,但只能在夜间Rust:

    #![feature(conservative_impl_trait)]
    
    fn adapt<F, T, U>(f: F) -> impl Fn(T) -> U
    where
        F: Fn(&T) -> U,
    {
        move |arg| f(&arg)
    }
    

    很遗憾,它无法解决您的问题,因为shout不接受&String,这需要strSized类型。

    更详细的解决方案涉及AsRef

    #![feature(conservative_impl_trait)]
    
    fn adapt<F, T1, T2, U>(f: F) -> impl Fn(T1) -> U
    where
        F: Fn(&T2) -> U,
        T1: AsRef<T2>,
        T2: ?Sized,
    {
        move |arg| f(arg.as_ref())
    }