Skip to content

Commit 91a88a7

Browse files
authored
Add documentation for common mistakes in errdefer scoping
1 parent 9716a1c commit 91a88a7

File tree

1 file changed

+173
-0
lines changed

1 file changed

+173
-0
lines changed

doc/langref.html.in

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5335,6 +5335,179 @@ fn createFoo(param: i32) !Foo {
53355335
is covered. The deallocation code is always directly following the allocation code.
53365336
</p>
53375337
{#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#}
53385511
<p>
53395512
A couple of other tidbits about error handling:
53405513
</p>

0 commit comments

Comments
 (0)