In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-03-30 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >
Share
Shulou(Shulou.com)06/02 Report--
This article mainly introduces "how to understand the wormhole shuttle of the Rust closure". In the daily operation, I believe that many people have doubts about how to understand the wormhole shuttle of the Rust closure. The editor consulted all kinds of data and sorted out a simple and useful method of operation. I hope it will be helpful to answer the doubt of "how to understand the wormhole shuttle of the Rust closure". Next, please follow the editor to study!
1. What is a closure?
The concept of Closure has been around for a long time. Regardless of the language, the concept of closures is constrained by the following features:
Anonymous function (non-unique, function pointer can also be used)
You can call closures and explicitly pass parameters (non-exclusive, function pointers can also be used)
Exists as a variable and can be passed back and forth (non-exclusive, function pointers can also be used)
The value that defines the scope can be captured and used directly in the closure (unique)
The magic is the last point, it is also more awkward to understand, just get used to it.
To illustrate the above features, look at an example of Rust.
Fn display (age: U32, print_info: t) where T: Fn (U32) {print_info (age);} fn main () {let name = String::from ("Ethan"); let print_info_closure = | age | {println! ("name is {}", name); println! ("age is {}", age);}; let age = 18; display (age, print_info_closure);}
Run the code:
Name is Ethan age is 18
First, the closure is stored in the print_info_closure stack variable as an anonymous function, then it is passed to the function display as an argument, the closure is called inside display, and the parameter age is passed. Finally, something amazing happened: the closure called in the function display actually printed out the variable name in the scope of the function main.
The essence of closure is that it involves two scopes at the same time, which is like opening a "wormhole" for variables of different scopes to travel through.
Let x_closure = | | {}
This secret is hidden in a single line of code:
On the left side of the assignment = is the variable that stores the closure, which is in a scope, that is, the environment context where we call the closure definition.
The right side of the assignment =, in the pair of curly braces {}, is also a scope, which is dynamically generated where the closure is called.
Both the left and right sides define the properties of the closure, which naturally connects the two scopes.
For closures, this is true for Rust, as well as for other languages. However, Rust also has such things as ownership and life cycle, so it can be analyzed in depth.
2. How the Rust closure captures the context
How does the Rust closure capture the context?
To put it another way, how does the variable name in the scope of main enter the scope of the closure (section 1 example)? Transfer or loan?
It Depends, depending on the situation.
Rust defines three kinds of trait in std:
FnOnce: there is a transfer operation to the external variable in the closure, which makes the external variable unavailable (so you can only call once)
FnMut: use external variables directly in closures and modify them
Fn: external variables are directly used in closures without modification
What the latter can do, the former can certainly do. On the contrary, it is not. Therefore, when the compiler deduces the closure signature:
To achieve FnMut, but also to achieve FnOnce
To implement Fn, FnMut and FnOnce are also implemented.
The example in section 1, changing the generic parameter of display from Fn to FnMut, can also be passed without warning.
Fn display (age: U32, mut print_info: t) where T: FnMut (U32) {print_info (age);}
Closures that capture environment variables require additional space support to store environment variables.
3. Closure signature as a parameter
The above code display function definition, to accept a closure as a parameter, reveals how to explicitly describe the signature of the closure: add a trait constraint to the generic parameter, such as T: FnMut (U32), where (U32) explicitly indicates the type of the input parameter. Although it is a generic parameter constraint, the description of the function signature (except that there is no function name) is very accurate.
By the way, Rust generics really do a lot of things. In addition to what generics are supposed to do, they can also add trait constraints and describe the life cycle.
Describing a signature is one thing, but who defines the signature of a closure? At the closure definition, we don't see any type constraints, so we can call it directly.
The answer is: the compiler does all the signature of the closure, which binds the type of parameters and return values passed in by the first invocation of the closure to the signature of the closure. This means that once the closure has been called once, the parameter type passed in when the closure is called again must be the same as the first time.
The incoming parameters are bound to the return value type, but you will inevitably feel a little sad: what about the generic parameters that describe the life cycle?
The Rust compiler can handle it.
Fn main () {let lifttime_closure = | a, b | {println! ("{}", a); println! ("{}", b); b}; let a = String::from ("abc"); let c; {let b = String::from ("xyz"); c = lifttime_closure (& a, & b);} println! ("{}", c) }
The above code failed to compile, and the drape reference was successfully detected:
Error [E0597]: b does not live long enough
Obviously, for closures, the compile time can check the lifecycle of the reference to ensure that the reference is always valid.
In this example, instead of explaining the difference between closures and functions, it is better to explain the difference between anonymous functions and named functions:
The named function is signed first, and for the compiler, the caller and the internal implementation of the function, as long as they follow the signing convention, respectively.
The signature of the anonymous function is inferred, and the compiler needs to see through the actual input of the caller and the actual return inside the function, and the check is naturally done by the way.
4. Function returns closure
In the example in section 1, we pass in a closure as a function parameter, so according to the nature of the closure, it should be able to be the return value of the function. The answer is yes.
Based on the Fn trait described earlier, we define a function that returns the closure as follows:
Fn closure_return ()-> Fn ()-> () {| | {}}
However, the compilation failed:
Error [E0746]: return type cannot have an unboxed trait object doesn't have a size known at compile-time
The failure message shows that the compiler cannot determine the size of the function's return value. How big is a closure? It doesn't matter.
To get to the point, the general solution is to box the stack memory variables into heap memory once in order to return the closure, so that no matter how large the closure is, the function return value is a pointer that determines the size. In the following code, you can use Box::new to complete the packing.
Fn closure_inside ()-> Box () > {let mut age = 1; let mut name = String::from ("Ethan"); let age_closure = move | | {name.push_str ("Yuan"); age + = 1; println! ("name is {}", name); println! ("age is {}", age);} Box::new (age_closure)} fn main () {let mut age_closure = closure_inside (); age_closure (); age_closure ();}
The running results are as follows:
Name is Ethan Yuan
Age is 2
Name is Ethan Yuan Yuan
Age is 3
The above code, in addition to making the function return the closure successfully, has another purpose. We want the closure to capture the value in the internal environment of the function, but this time is a little different:
Section 1 code example, we put the outer environment context by passing the closure into the inner function, which is not difficult to understand, because the life cycle of the outer variable is longer, and the outer variable is still alive when the inner function is accessed.
What the code in this section does is pass the environment variables of the inner function to the outer environment through the closure.
After the inner function call is completed, the inner environment variables will be destroyed, so how do you do that? Fortunately, Rust has a transfer of ownership. As long as it can facilitate the transfer of ownership from the environment variables of the inner function to the closure, this operation makes sense.
Because Rust has the concept of ownership transfer and the mechanism of returning closures (capturing environment variables at the same time), Rust's explanation is simpler and clearer than any explanation with garbage collection languages (JavaScript, Java, C #). The latter is always a bit unsettling: the internal function calls are over and the local variables are still alive.
The ownership transfer in the code, where the keyword move is used, which forces the ownership of the captured variable to be transferred to a special store inside the closure when building the closure. It is important to note that using move does not affect the trait of the closure, and in this case you can see that the closure is FnMut, not FnOnce.
At this point, the study on "how to understand wormhole shuttling of Rust closure" is over. I hope to be able to solve your doubts. The collocation of theory and practice can better help you learn, go and try it! If you want to continue to learn more related knowledge, please continue to follow the website, the editor will continue to work hard to bring you more practical articles!
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 225
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.