Consider the following reverseArray function. It mutates the array in-place, and returns the result. Of course, that’s a rather redundant thing to do. But it represents a general class of functions that has side effects and a return value.
fn reverseArray(arr: []u32) []u32 { ... }
Now, we want to test it. The following code doesn’t work, because the inline slice &[_]u32{ 1, 2, 3 } is read-only.
test "reverseArray: works" {
try std.testing.expectEqualSlices(
u32,
&[_]u32{ 3, 2, 1 },
reverseArray(&[_]u32{ 1, 2, 3 }), // Fails
);
}
We need to make the input slice mutable. The next one works. This is the “stack approach”.
test "reverseArray: works" {
var mock_arr = [_]u32{ 1, 2, 3 };
try std.testing.expectEqualSlices(
u32,
&[_]u32{ 3, 2, 1 },
reverseArray(&mock_arr),
);
}
However, var mock_arr = [_]u32{ 1, 2, 3 } makes stack allocation. And that might not be favorable in some cases.
var massive_mock_array = [_]u32{0} ** 10_000_000, which will lead to a stack overflow.So, here comes the “heap approach” with the testing allocator.
test "reverseArray: works" {
const mock_arr = try std.testing.allocator.dupe(u32, &[_]u32{ 1, 2, 3 });
defer std.testing.allocator.free(mock_arr);
try std.testing.expectEqualSlices(
u32,
&[_]u32{ 3, 2, 1 },
reverseArray(mock_arr),
);
}
In any case, having a function do both side effects and return something is not a good idea. It’s better if a function does exactly one thing.