| ... | @@ -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#} |
| | 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 | <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> |