There have been some discussions on how to make closure captures more ergonomic. For a background on motivation and a discusson on what I perceive to be the current way forward I recommend reading this blog post.
In various Reddit discussions the thought on explicit captures came up, and a blog post discussing some aspects of this was published here.
In this post I want to explore how C++ handles this same issue, and see if this could be solved in a similar manner in Rust.
Rusts closures are pretty much all or nothing, you either capture everything by reference, or you capture everything by move. You cannot grant access to values, you effectively have access to everything in the outside scope.
If you want to capture some things by reference and some things by move you need to set this up outside of the closure. If you want to clone things into the closure you can only do so by first clone, and then move into the closure.
If you need to clone a lot of things into the closure you are left with in a very unergonomic situation
And given that there is no way to control what values a closure can make use of, it is easy to accidentally refer to the wrong values from inside a closure.
Requirements
So these issues gives us these requirements that we must fulfill:
- It shall be possible to easily clone values into a closure.
- It shall be possible to restrict the set of outside values that a closure can make use of.
I also think that the following requirement should make it to the list:
- It shall be possible to switch between full control, and ergonomic cloning, in different parts in the same source file.
The last requirement is based on the fact that context matters. In some part of the program it might be ok to clone a Vec or a String or whatever else big datastructure you have. In other parts this must never be done.
Incidentally this is one area where I think C++ got things exactly right.
Lets look at various scenarios and see how C++ and Rust compares.
| let mul_2 = |x| x*2; | auto always_2 = [](int x) { return x*2; }; | |
| let mut y = 3i32; let mut add_one = || y += 1; | int y = 3; auto add_one = [&y] { y += 1; }; | int y = 3; auto add_one = [&] { y += 1; }; | 
| let mut y = 3i32; let mut add_one = move || y += 1; | int y = 3; auto add_one = [y] mutable { y += 1; }; | int y = 3; auto add_one = [=] mutable { y += 1; }; | 
| let some_arc = Arc::new(something); let _some_arc = some_arc.clone(); let do_something = move || do_something_impl(_some_arc); | auto some_data = std::make_shared(); auto do_something = [some_data] { do_something_impl(some_data); }; | auto some_data = std::make_shared(); auto do_something = [=] { do_something_impl(some_data); }; | 
We can see that C++ gives both full control, and shorthands. Essentially in this case C++ allows for the best of both worlds.
In terms of our requirements we tick both boxes….almost.
This is weird
In C++, the this pointer makes things more awkward.
In the above example the implicit this pointer gets captured by value, but since it is a pointer, x is actually referred to by reference. That is why d.x gets modified within clos(), even though we captured everything by value.
Now C++ has deprecated implicit capture of this when using the [=] capture form, but it still makes this slightly weird.
To solve this in C++ one has to do the following:
So could we do the same approach in Rust as C++? Lets play with the thought and see where it brings us.
This would be an empty closure that cannot refer to variables in the outer scope. Doing so would become a compiler error:
Here we can either explicitly control the captures, or capture whatever we refer to by ref:
Again, we can either explicitly control the captures, or capture whatever we refer to by move:
Same for clone:
We can even mix and match:
Self is wild
The same problem that C++ has with this, Rust will have with self.
Consider a struct like this:
To refer to self.some_a we need to decide how to capture self and then further on how to capture some_a.
For now I think my suggestion lands on allowing wildcards in the capture clause:
We set out to solve the following:
- It shall be possible to easily clone values into a closure.
- It shall be possible to restrict the set of outside values that a closure can make use of, and how they are captured by the closure.
Number 2 is definately fulfilled. We can clearly restrict what values a closure can make use of and we can control how they are captured.
Lets look at the two examples in the issue chapter and see how they would be solved using this more refined capture control:
Now the second one is more dramatic, so lets look at the original first:
This gets reduced to
I don’t think this can be made much simpler to be honest. So requirement number 1 is also fulfilled.
Now this is all bikeshed syntax, but I think this can give some ideas and directions where to go.
.png)
 8 hours ago
                                1
                        8 hours ago
                                1
                     
  ![The Geometry of Schemes [pdf]](https://news.najib.digital/site/assets/img/broken.gif)

