How do you understand this Rust function that returns another function?

While reading about Rust, I came across an example function that takes a number and returns a function that adds that number to another number.

fn higher_order_fn_return<'a>(step_value: &'a i32) -> Box<Fn(i32) -> i32 + 'a> {
    Box::new(move |x: i32| x + step_value)
}

      

There are so many rust specific mechanisms I can't figure out here. I'm sure some of them are related to lifecycle management, but the reasons why it should be written this way elude me. A few questions:

  • Why step_value

    is it passed as a link?
  • Why does the function return to box?
  • How to interpret the unconventional way of writing a function type (how Fn(i32) -> i32 + 'a

    )?
  • Why is it 'a

    written as generic ( <'a>

    ), but "added" to the return type ( + 'a

    )?
  • What is the point move

    and what is moving here?
+3


source to share


2 answers


There's a ban on asking more than one question, but since it all falls under "what does this piece of code mean" I won't complain. It also happens to compress quite a bit of weirdness into one relatively small rather than horribly unusual piece.

Why step_value

is it passed as a link?

I do not know. It's simple. It can be passed by value without significantly changing the semantics of the code. But it is transmitted by reference and that is the cause of all other life-related problems.

Why does the function return to box?

It doesn't return a function. Functions are defined fn

. It returns a closure. The problem is that each closure is actually an instance of an anonymous type (sometimes called "Voldemort's Type") for performance reasons. Anonymous types are a problem because you cannot name them, but you must specify your return type.

Thus, a tag object should be returned instead. In this case, it returns a fn

. There is also FnMut

and FnOnce

. He returns it to the box, because it objects to open the object can not be passed by value, so the objects are objects should always be behind some pointer (either Box

, &

, Rc

etc.).

They cannot be passed by value because the compiler cannot decide how big it will be, which makes them nearly impossible. After that, the train of logic is translated directly into "how the compiler is implemented", which is somewhat out of scope here.

How to interpret the unconventional way of writing a function type (how Fn(i32) -> i32 + 'a

)?

There is nothing unconventional there. Anyway, not for rust, but since it's in Rust, it's different, as other languages ​​do.

Let it ignore it for a moment + 'a

, since in fact it is something else. Fn(i32) -> i32

- an important part. Every "callable" thing in Rust implements one or more traits fn

, FnMut

and FnOnce

that is, how Rust expresses the idea that something can be called. The things inside parens are arguments, what after ->

is the return type, just like functions.

You can learn more about these traits in the question "When is Fn, FnMut and FnOnce Closing Implemented?" ...



Why is it 'a

written as generic ( <'a>

) but "added" to the return type ( + 'a

)?

First, because lifetimes are part of the type system. Therefore, they are included in the general list of parameters (the thing inside <...>

).

Second, because the compiler has to figure out how long the traits object inside Box

will be valid. If you have Box<SomeTrait>

, how long does the compiler allow this value to exist? Usually this information will be part of the type, but if you use a trait, the compiler doesn't know which type is being used. Remember, you can do Box<SomeTrait>

from anywhere Box<T>

that T

implements SomeTrait

.

In this case, the closure will be dependent on the borrowing step_value

, which means that it should not survive the lifetime of that borrowing (which is 'a

). But if the type was simple Box<Fn(i32) -> i32>

, the compiler would not have this information. Thus, there is a syntax for indicating that regardless of the type hiding behind the feature object, it cannot survive a given lifetime.

That which says + 'a

, "This is the value in the box that realizes the trait Fn(i32) -> i32

and cannot survive the lifetime 'a

."

What is the meaning of movement and what is moving here?

Usually the compiler tries to guess what it should do to make the closure work, but it may not always get it right. Where possible, it tries to borrow things captured by the closure. So when you use step_value

inside a closure, the compiler usually just borrows it.

This won't be a problem, except that you are returning a function closure. This automatic borrowing will only last for the lifetime of the function, which is not long enough. To fix this, instead of borrowing, step_value

you can move it to the closure.

Bonus thing you might think of.

If you don't write + 'a

to Box<Trait + 'a>

, what usually happens?

Actually, the compiler has a heuristic. By default, every object object has a lifetime attached. It inherits from the pointer that wraps it. So, &'a Trait

really &'a (Trait + 'a)

. Box

has no lifetime value of its own, so it gets 'static

(i.e. Box<Trait>

is Box<Trait + 'static>

), which means that, by default, objects with boxing traits cannot contain any non-t238> borrows.

+6


source


Why step_value

is it passed as a link?

There is no good reason for this. Passing it by value makes it much easier. However, the example in question may have done this because you cannot do this for every type, only those that are Copy

.

Why does the function return to box?

The lambda type cannot be named and therefore cannot be returned from a function. So you have to return a trait object ( Fn

is a trait) and for that you need a field. ( impl Trait

You don't need a window anymore.)

How to interpret the unconventional way of writing a function type (how Fn(i32) -> i32 + 'a)

?



Fn

has a little syntactic sugar, where syntax Fn(arg1, arg2) -> ret

is shorthand for (I think) Fn<(arg1, arg2), Output=ret>

. +

above has a lower priority than error and is not part of the constraint Fn

; instead, it is a combination of constraints, meaning that the type in Box

must be both Fn(i32) -> i32

and has a lifetime 'a

.

Why is it 'a

written as generic ( <'a>

), but "added" to the return type ( + 'a

)?

Lifecycle parameters must be declared in the general parameters section of the function (or type), thus <'a>

. This then happens in the reference type of the argument ( & 'a i32

) and finally as an additional constraint in the Box

.

What is the point move

and what is moving here?

This makes the closure a move closure, which means that the things it grabs are moved to the closure rather than being captured by a link. In this example, however, note that the float value step_value

, which is itself a link!

+2


source







All Articles