@@ -5335,6 +5335,179 @@ fn createFoo(param: i32) !Foo {
5335
5335
is covered. The deallocation code is always directly following the allocation code.
5336
5336
</p>
5337
5337
{#header_close#}
5338
+ {#header_open|Common errdefer Slip-Ups#}
5339
+ <p>
5340
+ It should be noted that {#syntax#}errdefer{#endsyntax#} statements only last until the end of the block
5341
+ they are written in, and therefore are not run if an error is returned outside of that block:
5342
+ </p>
5343
+ {#code_begin|test_err|1 tests leaked memory#}
5344
+ const std = @import("std");
5345
+ const Allocator = std.mem.Allocator;
5346
+
5347
+ const Foo = struct {
5348
+ data: u32,
5349
+ };
5350
+
5351
+ fn tryToAllocateFoo(allocator: Allocator) !*Foo {
5352
+ return allocator.create(Foo);
5353
+ }
5354
+
5355
+ fn deallocateFoo(allocator: Allocator, foo: *Foo) void {
5356
+ allocator.destroy(foo);
5357
+ }
5358
+
5359
+ fn getFooData() !u32 {
5360
+ return 666;
5361
+ }
5362
+
5363
+ fn createFoo(allocator: Allocator, param: i32) !*Foo {
5364
+ const foo = getFoo: {
5365
+ var foo = try tryToAllocateFoo(allocator);
5366
+ errdefer deallocateFoo(allocator, foo); // Only lasts until the end of getFoo
5367
+
5368
+ // Calls deallocateFoo on error
5369
+ foo.data = try getFooData();
5370
+
5371
+ break :getFoo foo;
5372
+ };
5373
+
5374
+ // Outside of the scope of the errdefer, so
5375
+ // deallocateFoo will not be called here
5376
+ if (param > 1337) return error.InvalidParam;
5377
+
5378
+ return foo;
5379
+ }
5380
+
5381
+ test "createFoo" {
5382
+ try std.testing.expectError(error.InvalidParam, createFoo(std.testing.allocator, 2468));
5383
+ }
5384
+ {#code_end#}
5385
+ <p>
5386
+ To ensure that {#syntax#}deallocateFoo{#endsyntax#} is properly called
5387
+ when returning an error, you must add an {#syntax#}errdefer{#endsyntax#} outside of the block:
5388
+ {#code_begin|test|test_errdefer_block#}
5389
+ const std = @import("std");
5390
+ const Allocator = std.mem.Allocator;
5391
+
5392
+ const Foo = struct {
5393
+ data: u32,
5394
+ };
5395
+
5396
+ fn tryToAllocateFoo(allocator: Allocator) !*Foo {
5397
+ return allocator.create(Foo);
5398
+ }
5399
+
5400
+ fn deallocateFoo(allocator: Allocator, foo: *Foo) void {
5401
+ allocator.destroy(foo);
5402
+ }
5403
+
5404
+ fn getFooData() !u32 {
5405
+ return 666;
5406
+ }
5407
+
5408
+ fn createFoo(allocator: Allocator, param: i32) !*Foo {
5409
+ const foo = getFoo: {
5410
+ var foo = try tryToAllocateFoo(allocator);
5411
+ errdefer deallocateFoo(allocator, foo);
5412
+
5413
+ foo.data = try getFooData();
5414
+
5415
+ break :getFoo foo;
5416
+ };
5417
+ // This lasts for the rest of the function
5418
+ errdefer deallocateFoo(allocator, foo);
5419
+
5420
+ // Error is now properly handled by errdefer
5421
+ if (param > 1337) return error.InvalidParam;
5422
+
5423
+ return foo;
5424
+ }
5425
+
5426
+ test "createFoo" {
5427
+ try std.testing.expectError(error.InvalidParam, createFoo(std.testing.allocator, 2468));
5428
+ }
5429
+ {#code_end#}
5430
+ <p>
5431
+ The fact that errdefers only last for the block they are declared in is
5432
+ especially important when using loops:
5433
+ </p>
5434
+ {#code_begin|test_err|3 errors were logged#}
5435
+ const std = @import("std");
5436
+ const Allocator = std.mem.Allocator;
5437
+
5438
+ const Foo = struct {
5439
+ data: *u32
5440
+ };
5441
+
5442
+ fn getData() !u32 {
5443
+ return 666;
5444
+ }
5445
+
5446
+ fn genFoos(allocator: Allocator, num: usize) ![]Foo {
5447
+ var foos = try allocator.alloc(Foo, num);
5448
+ errdefer allocator.free(foos);
5449
+
5450
+ for(foos) |*foo, i| {
5451
+ foo.data = try allocator.create(u32);
5452
+ // This errdefer does not last between iterations
5453
+ errdefer allocator.destroy(foo.data);
5454
+
5455
+ // The data for the first 3 foos will be leaked
5456
+ if(i >= 3) return error.TooManyFoos;
5457
+
5458
+ foo.data.* = try getData();
5459
+ }
5460
+
5461
+ return foos;
5462
+ }
5463
+
5464
+ test "genFoos" {
5465
+ try std.testing.expectError(error.TooManyFoos, genFoos(std.testing.allocator, 5));
5466
+ }
5467
+ {#code_end#}
5468
+ <p>
5469
+ Special care must be taken with code that allocates in a loop
5470
+ to make sure that no memory is leaked when returning an error:
5471
+ </p>
5472
+ {#code_begin|test|test_errdefer_loop#}
5473
+ const std = @import("std");
5474
+ const Allocator = std.mem.Allocator;
5475
+
5476
+ const Foo = struct {
5477
+ data: *u32
5478
+ };
5479
+
5480
+ fn getData() !u32 {
5481
+ return 666;
5482
+ }
5483
+
5484
+ fn genFoos(allocator: Allocator, num: usize) ![]Foo {
5485
+ var foos = try allocator.alloc(Foo, num);
5486
+ errdefer allocator.free(foos);
5487
+
5488
+ // Used to track how many foos have been initialized
5489
+ // (including their data being allocated)
5490
+ var num_allocated: usize = 0;
5491
+ errdefer for(foos[0..num_allocated]) |foo| {
5492
+ allocator.destroy(foo.data);
5493
+ };
5494
+ for(foos) |*foo, i| {
5495
+ foo.data = try allocator.create(u32);
5496
+ num_allocated += 1;
5497
+
5498
+ if(i >= 3) return error.TooManyFoos;
5499
+
5500
+ foo.data.* = try getData();
5501
+ }
5502
+
5503
+ return foos;
5504
+ }
5505
+
5506
+ test "genFoos" {
5507
+ try std.testing.expectError(error.TooManyFoos, genFoos(std.testing.allocator, 5));
5508
+ }
5509
+ {#code_end#}
5510
+ {#header_close#}
5338
5511
<p>
5339
5512
A couple of other tidbits about error handling:
5340
5513
</p>
0 commit comments