スクリプト言語間における「lexical closure」の違い – Blocksの場合

元ネタ:

スクリプト言語でもclosureでもないけど、一応やっておかないとね。

Objective-C with Blocks

#import <Foundation/Foundation.h>
#import <objc/runtime.h>

// 注: スタックの持ち方に問題のある書き方
int
main(int argc, const char * argv[])
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    
    NSMutableArray *closures = [NSMutableArray array];
    for (int i = 0; i < 5; i++) {
        NSString *localvar = [NSString stringWithFormat:@"foo%d", i];
        [closures addObject:^{return localvar;}];
    }
    for (int i = 0, c = [closures count]; i < c; i++) {
        NSLog(@"%@", ((id (^)())[closures objectAtIndex:i])());
    }

    [pool drain];
    return 0;
}
2010-11-04 03:37:37.636 BlocksSample[49954:a0f] foo4
2010-11-04 03:37:37.638 BlocksSample[49954:a0f] foo4
2010-11-04 03:37:37.639 BlocksSample[49954:a0f] foo4
2010-11-04 03:37:37.639 BlocksSample[49954:a0f] foo4
2010-11-04 03:37:37.640 BlocksSample[49954:a0f] foo4

がっかりだ……。これはlocalvarが使い回されるせいではなく、Blocksがスタック上の同じアドレスに作られるせいだと思われる。
次のようにコピーすれば解消する。

#import <Foundation/Foundation.h>
#import <objc/runtime.h>

// 修正版
int
main(int argc, const char * argv[])
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    
    NSMutableArray *closures = [NSMutableArray array];
    for (int i = 0; i < 5; i++) {
        NSString *localvar = [NSString stringWithFormat:@"foo%d", i];
        [closures addObject:[[^{return localvar;} copy] autorelease]];
    }
    for (int i = 0, c = [closures count]; i < c; i++) {
        NSLog(@"%@", ((id (^)())[closures objectAtIndex:i])());
    }

    [pool drain];
    return 0;
}
2010-11-04 03:49:22.292 BlocksSample[50207:a0f] foo0
2010-11-04 03:49:22.299 BlocksSample[50207:a0f] foo1
2010-11-04 03:49:22.304 BlocksSample[50207:a0f] foo2
2010-11-04 03:49:22.305 BlocksSample[50207:a0f] foo3
2010-11-04 03:49:22.307 BlocksSample[50207:a0f] foo4

というかスタックへの参照をうかつに残すとクラッシュするので、コピーするのが正解のようではある。
だったらもう少し使い易いシンタックスシュガーが欲しい気もする。