authorgravatar for 68005704+Exonorid@users.noreply.github.comExonorid <68005704+Exonorid@users.noreply.github.com> 2022-02-23 05:33:51-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2022-02-23 14:33:51+02:00
log91a88a789ffa80ebb57c77ae0fe37594276e3707
tree37d8ac25ce4ede5793775f7f3d24ae88ccbfbc3e
parent9716a1c3ab3f56dcef7a70aac597f186a69714fc
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Add documentation for common mistakes in errdefer scoping


1 files changed, 173 insertions(+), 0 deletions(-)

doc/langref.html.in+173
...@@ -5335,6 +5335,179 @@ fn createFoo(param: i32) !Foo {...@@ -5335,6 +5335,179 @@ fn createFoo(param: i32) !Foo {
5335 is covered. The deallocation code is always directly following the allocation code.5335 is covered. The deallocation code is always directly following the allocation code.
5336 </p>5336 </p>
5337 {#header_close#}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#}
5344const std = @import("std");
5345const Allocator = std.mem.Allocator;
5346
5347const Foo = struct {
5348 data: u32,
5349};
5350
5351fn tryToAllocateFoo(allocator: Allocator) !*Foo {
5352 return allocator.create(Foo);
5353}
5354
5355fn deallocateFoo(allocator: Allocator, foo: *Foo) void {
5356 allocator.destroy(foo);
5357}
5358
5359fn getFooData() !u32 {
5360 return 666;
5361}
5362
5363fn 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
5381test "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#}
5389const std = @import("std");
5390const Allocator = std.mem.Allocator;
5391
5392const Foo = struct {
5393 data: u32,
5394};
5395
5396fn tryToAllocateFoo(allocator: Allocator) !*Foo {
5397 return allocator.create(Foo);
5398}
5399
5400fn deallocateFoo(allocator: Allocator, foo: *Foo) void {
5401 allocator.destroy(foo);
5402}
5403
5404fn getFooData() !u32 {
5405 return 666;
5406}
5407
5408fn 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
5426test "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#}
5435const std = @import("std");
5436const Allocator = std.mem.Allocator;
5437
5438const Foo = struct {
5439 data: *u32
5440};
5441
5442fn getData() !u32 {
5443 return 666;
5444}
5445
5446fn 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
5464test "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#}
5473const std = @import("std");
5474const Allocator = std.mem.Allocator;
5475
5476const Foo = struct {
5477 data: *u32
5478};
5479
5480fn getData() !u32 {
5481 return 666;
5482}
5483
5484fn 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
5506test "genFoos" {
5507 try std.testing.expectError(error.TooManyFoos, genFoos(std.testing.allocator, 5));
5508}
5509 {#code_end#}
5510 {#header_close#}
5338 <p>5511 <p>
5339 A couple of other tidbits about error handling:5512 A couple of other tidbits about error handling:
5340 </p>5513 </p>