To make this more concrete:
let s = String::from("s");
let closure = || {
println!("s is: {}", s);
};
closure();
If you wanted to capture s in a different way: let s = String::from("s");
let s1 = &s;
let closure = || {
println!("s1 is: {}", s1);
};
closure();
No capture list needed, same control.Strictly speaking, Rust probably could have gotten away with having neither the `move` modifier nor capture clauses at all, but it would have had wide-ranging implications on the ergonomics and capabilty of closures.
auto on_heap = std::make_unique<MyType>(...);
function_that_accepts_lambda([obj = std::move(on_heap)]() {
obj->bar(...);
}) let on_heap = Box::new(...);
function_that_accepts_lambda(move || {
on_heap.bar();
});
This is sort of what kibwen was mentioning: move is a single annotation that overrides everything to capture by value rather than have it inferred.e.g.
auto shared = std::make_shared<MySharedType>(...);
auto unique = std::make_unique<MyOwnedType>(...);
function_that_accepts_lambda([shared, u = std::move(unique)] {
shared->foo(...); u->bar(...);
});
// Outer scope can still use shared.
shared->foo(....); let on_heap = Box::new(...);
let shared = Arc::new(...);
let s = shared.clone();
function_that_accepts_lambda(move || {
on_heap.bar();
s.foo();
});
We have to make the extra s binding.Also, my sibling is correct that Copy types will just be copied, not moved.
In C#, there is no such thing, but there is a part of me that wishes we had such a thing. I like the ability explicitly state what variables are being captured.
Why? You state what variables are being captured by just using them in the lambda body.
Wow, have you never spent a week debugging a JavaScript memory leak?
I think the primary need for manual declaration is because in C++ you need to differentiate between pass by copy semantics and pass by reference semantics.
That's not actually a need, C++ includes [=] and [&] (capture everything by value or by reference). You can get a mix by creating references outside the body then capturing the environment by value (capturing the references by value and thus getting references).
On the one hand it has a bit more syntactic overhead (you have to take and declare a bunch of references before creating the closure), on the other hand there's less irregularity to the language, and bindings mean the same thing in and out of the closure.
FWIW that's what Rust does[0], though it may help that Rust's blocks are "statement expressions", some constructs would probably be unwieldy without that.
[0] the default corresponds to C++'s [&] (capture by ref), and a "move closure" switches to [=] instead
I mean I'm not really complaining; I don't see better syntax to fit short anonymous functions into the existing syntax, without defeating the whole purpose of it either.
I suspect it's just a matter of getting used to this extra meaning for angular brackets.
If I have learned anything over the years, it's that removing the braces anywhere is the introduction of a bug during a rewrite waiting to happen.
Statemends like :
list.Where(e => e.Property == ExpectedValue).Select(e => e.Property)
is much cleaner than something like : list.Where([](auto e) { return e.Property == ExpectedValue; }).Select([](auto e) { return e.Property; });
Braces and capture declaration adds 0 value here and it's ~80% of the use cases I see for lambdas. int b = 0;
^(int a) {
return a*b;
}
The declaration for lambda variables is almost identical to function pointers, just with a ^ instead of a *, so there's nothing to learn (or unlearn, like C++ forces you to). The ^ looks like a lambda, and historically the lambda of lambda calculus actually was a caret accent over the variable. The argument list can be elided.http://researchblogs.cs.bham.ac.uk/thelablunch/2016/05/why-i...
Something like:
lambda(arguments):capture list {...}
Just seems way more clear. Looks more like a function or a class (and a lambda is sort of an in-between kind of thing anyway)