Zig C Error Handling
C error handling is the pattern of checking a C call's return value and reading its error number when the call fails. The error number names the failure, and a message function turns it into words.
A failed open
Opening a missing file fails and returns null. The program reads the error number and prints it.
const std = @import("std");
const c = @cImport({
@cInclude("stdio.h");
@cInclude("errno.h");
});
pub fn main() !void {
const f = c.fopen("no-such-file-xyz.txt", "r");
const out = std.io.getStdOut().writer();
if (f == null) {
try out.print("open failed errno={d}\n", .{c.__errno_location().*});
} else {
try out.print("opened\n", .{});
}
}
Zero is not always failure
Converting non-numeric text returns zero without failing. The error number stays zero, showing no failure happened.
const std = @import("std");
const c = @cImport({
@cInclude("stdlib.h");
@cInclude("errno.h");
});
pub fn main() !void {
const v = c.strtol("abc", null, 10);
const out = std.io.getStdOut().writer();
try out.print("value={d} errno={d}\n", .{ v, c.__errno_location().* });
}
A failed remove
Removing a missing file also fails. The program checks the return value and prints the error number.
const std = @import("std");
const c = @cImport({
@cInclude("stdio.h");
@cInclude("errno.h");
});
pub fn main() !void {
const r = c.remove("no-such-file-xyz.txt");
const out = std.io.getStdOut().writer();
if (r != 0) {
try out.print("remove failed errno={d}\n", .{c.__errno_location().*});
} else {
try out.print("removed\n", .{});
}
}
Example
const std = @import("std");
const c = @cImport({
@cInclude("stdio.h");
@cInclude("string.h");
@cInclude("errno.h");
});
pub fn main() !void {
const f = c.fopen("no-such-file-xyz.txt", "r");
const out = std.io.getStdOut().writer();
if (f == null) {
const e = c.__errno_location().*;
try out.print("open failed errno={d} message={s}\n", .{ e, c.strerror(e) });
} else {
try out.print("opened\n", .{});
}
}
Key points
- Errno: errno is a number set by a C library call when that call fails.
- Return value: a return value is the result a function hands back, and a special value signals failure.
- A failing open returns a null pointer, so the program must test for null before reading the error number.
- The error number is only meaningful right after a failed call, so the program reads it before any other C call.
- A message function turns an error number into a readable sentence for the user.
- A return of zero is not always a failure, so the program must know what failure looks like for each function.
The example calls open on a missing file and tests the result for null. It reads the error number at once, before any other C call. It turns the number into words and prints the number and the message together.
Try it Yourself
Edit the code, then press Run.
Running is simulated
Exercise
When should a program read the error number after a C call fails?