Accessing non-final local variables from a lambda expression

Peter Levart peter.levart at marand.si
Fri Feb 26 08:01:24 PST 2010


On Friday 26 February 2010 09:23:42 Lawrence Kesteloot wrote:
> > In BGGA, CfJ, and other languages with lambdas, one could write it
> > simply like this
> >
> > <T> Iterable<T> everyOther(Iterable<T> input) {
> >    boolean flag = true;
> >    return filter(input, (T t)->(flag ^= true));
> > }
> >
> > But project lambda forbids access to mutable local variables from the
> > enclosing scope.
> 
> <T> Iterable<T> everyOther(Iterable<T> input) {
>    final boolean[] flag = new boolean[] { true };
>    return filter(input, (T t)->(flag[0] ^= true));
> }
> 
> It's not beautiful, but not the end of the world either. And the
> performance is comparable, since in any case you have to create and
> dereference some heap object.
> 

The problem with this approach (from the performance perspective) is when you capture several mutable local variables. Having a separate wrapper object for each of them is an overhead which can be avoided if the compiler constructs a single frame object for you.

Regarding @Shared annotation on captured mutable local variables to prevent compiler warning, I think that it would be better to approach the problem a little differently. 

There are perfectly valid use-cases (like the one above) where accessing a mutable local variable is safe. For such cases it should not be necessary to use @Shared annotation on a variable to prevent a warning. The only way for a mutable local variable to escape the method is via capture from a closure whose reference escapes the method. So a better approach would be to annotate function type (and SAM type) variables (instance/static/local/parameters) that hold references to closures, not mutable local variables accessed from closures.

A simple approach with a single annotation @Safe (meaning: multi thread safe) could work as follows:

void test()
{
   boolean flag = true;

   // OK
   ()->boolean flipFlop = ()->(flag ^= true);

   // warning: assignment of unsafe function to @Safe variable
   @Safe ()->boolean safeFlipFlop = ()->(flag ^= true);

   // OK: compiler determines lambda is safe (doesn't access mutable local vars)
   @Safe ()->void safeHello = ()->(System.out.println("Hello!"); void);

   flipFlop = safeFlipFlop; // OK
   safeFlipFlop = flipFlop; // warning: assignment of unsafe function to @Safe variable

   int count = 0;
   Object lock = new Object();
   // OK: user suppresses the warning since she knows her code is safe
   @SuppressWarnings("unsafe")
   @Safe ()->int safeCounter = ()->(int c; synchronized(lock) { c = count++; } c);
}

All JDK methods/constructors accepting SAM types as parameters and invoking them from other threads (like new Thread, Executor.submit, ...) would have to add @Safe to their SAM parameters and any such 3rd party APIs would have to be retrofitted with annotations too. 

That's a drawback of this approach but It might still be better than @Shared on local vars. If users are forced to shut-up the compiler in all valid use-cases where it is perfectly safe to access mutable local vars, then they might develop a habit of putting @Shared annotation on every local variable accessed in lambdas without verifying that passing such lambdas to different methods (threads) is actually safe. On the other hand, when using @Safe on function/SAM variables, warnings would be less frequent, more accurate and would therefore have a better chance of being considered. 

The issue with this approach is whether to treat accesses to instance/static fields of outer classes and invocations of methods as safe or unsafe. In general it is unsafe, but it is allowed by default in inner classes so if we just want to issue warnings for the "new dangerous feature", then they should be treated as safe.

The general approach to checking the multi-thread-safe code would of course extend to methods and be orthogonal to lambdas, but If we just want to play safe with new lambda features then the simple approach described above could be enough.

Regards, Peter


More information about the lambda-dev mailing list