Welcome toVigges Developer Community-Open, Learning,Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
384 views
in Technique[技术] by (71.8m points)

rust - Modify and return closure

I'm trying to make a function to do the following: accept a closure f of the form fn(T) -> T return a closure of the form fn(T, bool) -> T, that conditionally performs f depending on the bool parameter.

I come from a haskell-ish background, and in Haskell this would be something like this:

conditionally :: (a -> a) -> a -> Bool -> a
conditionally f x True = f x
conditionally f x False = x

Translating that to something more rust-like:

conditionally :: ((t) -> t) -> ((t, Bool) -> t)
conditionally f = (x, b) -> if b then (f x) else (x)

I tried the following in rust:

fn conditionally<T>(f: &'static (dyn Fn(T) -> T + 'static)) -> Box<dyn Fn(T, bool) -> T> {
    Box::new(&|x, b| if b { f(x) } else { x } )
}

and was helpfully told to use the move keyword to ensure the closure takes ownership of f. However, the following still doesn't work:

fn conditional<T>(f: &'static (dyn Fn(T) -> T + 'static)) -> Box<dyn Fn(T, bool) -> T> {
    Box::new(&move|x, b| if b { f(x) } else { x } )
}

I'm getting the following error (this also appeared prior to adding move):

error[E0515]: cannot return value referencing temporary value
   --> src/main.rs:216:5
    |
216 |     Box::new(&move|x, b| if b { f(x) } else { x } )
    |     ^^^^^^^^^^-----------------------------------^^
    |     |         |
    |     |         temporary value created here
    |     returns a value referencing data owned by the current function

I'm thinking that the 'data owned by the current function' is either the closure I've defined, or the f that I've moved, but I can't get my head around how it all fits together.
As a smoke check, I made sure I'm able to box simpler closures that I define in the function body, and the following compiles:

fn conditional_increment() -> Box<dyn Fn(i32, bool) -> i32> {
    Box::new(&|x, b| if b { x + 1 } else { x } )
}

What am I missing here? Is this possible in rust? I'm also wondering if there's a more specific name for what I'm trying to do than simply higher-order functions, as I'm struggling to find resources on this kind of problem.

Update: I've realised that "currying in rust" would have been a good term to search for. While this isn't an example of currying, it would use the same language features, and would have led me to the answer given by vallentin.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

You're attempting to return a boxed reference to a closure defined within the conditional function. Which you can't do, given that the closure only lives for the duration of the call. Instead you can return the closure itself, and in short just have to remove the &, i.e. turn &move |x, b| ... into move |x, b| ....

fn conditional<T>(f: &'static (dyn Fn(T) -> T + 'static)) -> Box<dyn Fn(T, bool) -> T> {
    Box::new(move |x, b| if b { f(x) } else { x })
}

However, the more idiomatic way to write what you're attempting, which is to use generics and a type parameter for the closure. Check out:

In short, you could rewrite it into this:

fn conditional<F, T>(f: F) -> Box<dyn Fn(T, bool) -> T>
where
    F: Fn(T) -> T + 'static,
    T: 'static,
{
    Box::new(move |x, b| if b { f(x) } else { x })
}

You can actually also do without the box, and use the impl Trait syntax.

fn conditional<F, T>(f: F) -> impl Fn(T, bool) -> T
where
    F: Fn(T) -> T,
{
    move |x, b| if b { f(x) } else { x }
}

You can also use the impl Trait syntax for parameters, which the links shows, but personally I find it to noisy when dealing with closuring.

Using it the boils down to something as simple as this:

let f = conditional(move |x| x * 2);
println!("{}", f(2, false)); // Prints `2`
println!("{}", f(2, true));  // Prints `4`

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to Vigges Developer Community for programmer and developer-Open, Learning and Share
...