Hi,
Let me first say that the only reason I write this is because I really like
Rust. It seems to get a lot of things really right, and I'd like it to
become successful! With that said, here's one issue. I have more, and I may
write more about that later, but I figured I start with this one because it
actually ends with a suggestion!
IMO implicit capture of the environment for closures is crucial, and punches
above its weight because it allows the user to write his own control flow,
as well as scope-based resource control (without RAII). This can only happen
if the lambda looks very much like any other scope (which requires extremely
light weight syntax - possibly influencing all other syntax to make this
happen, but it also requires that we implicitly get access to the
environment, just like any other scope).
For just one example of why we need lambdas for scope-based resource control
(and not just the kludge of RAII), you can imagine some kind of optimistic
concurrency for a transactional store doing something like (pseudo code,
don't know Rust well enough yet to write anything with it):
void with_transaction( db, Func<Transaction,Void> body )
{
// try running the body in a transaction a few times
for( int i = 0; i < NUM_RETRIES; ++i )
{
try{
Transaction t = db.startTransaction();
body(t);
return; // all is well, return early
}
catch( TransactionFailed ex )
{
continue; // benign error, let's just try again...
}
finally{
t.close();
}
}
// couldn't co-exist with others
// so take a global lock instead
try {
Transaction t = db.takeBigLock();
body(t);
}
finally{
t.close();
}
}
And then the client code could use this like so:
with_transaction( thedb, (auto t){
auto x = t.getVar("x");
t.putVar( "x", foo(x) );
}
Now, a few things to notice here. First, the code actually implementing the
resource management is a single function, so it's easy to write and maintain
- it's not arbitrarily split up into a constructor/destructor, with any data
flowing between the two having to be stored away somewhere in the instance
data of some dummy data structure. Second, it has first-class access to the
body of the code actually using the resource, which in this case means it
can retry the body several times, and do all sorts of other logic you can
imagine, something which wouldn't be possible with C++ style RAII. Third,
the user code doesn't need to create some (named) "dummy" object which
doesn't actually have any real value, but is only used as a means of
hijacking the constructor/destructor mechanism for RAII. Fourth, the client
code makes it extremely clear that a scope isn't just any old scope, it's
explicitly and very clearly attached to the resource-introducing function
"with_transaction", whereas with C++ style RAII this is a bit more
hidden/implicit because RAII objects just look like any other object, so who
can really say that one of those objects takes a lock, for example, and its
scope must therefore be minimized? Closure-based scope control is easier to
write and maintain, more flexible and powerful, and clearer at the use
site.
So that's a partial argument for why lambdas and higher order functions are
superior to constructors/destructors for scope-based resource control (at
least for some things), but that's only *one* example of why lambdas with
environment capture are good stuff. All sorts of other things can be done
much simpler (e.g. control flow constructs like for_each, events systems,
etc.). In short, the usefulness of lambdas require environment capture,
without it they offer very little over simple function pointer and become
severely crippled.
Now, here's the question/proposal (I would prefer if the whole thing was
just changed so it worked like normal closures, but since that probably
won't happen...). Could we not do some kind of compromise where lambdas that
are passed by aliases implicitly capture their environment by aliasing? In
other words, for things like with_transaction above, where we use the lambda
immediately, rather than storing it away, we can use it as if it was any
other scope, and therefore write a lot of simple resource management and
flow control code. All the usual alias restrictions would apply. The idea is
that you wouldn't need to worry about actually storing anything in a
closure, since you don't have to worry about the receiver of the lambda
actually storing the lambda anywhere (it can only access it within the scope
of the function call, since it's an alias).
Would this work? Seems like some (perhaps even the majority?) of the cases
where "real" lambdas are needed could still be satisfied, without having to
worry about most of the issues listed in the FAQ since the lambda can't
survive the function call it's passed to.
--
Sebastian Sylvan
_______________________________________________
Rust-dev mailing list
[email protected]
https://mail.mozilla.org/listinfo/rust-dev