zig icon indicating copy to clipboard operation
zig copied to clipboard

make std.os.exit cross-platform and related start.zig changes

Open wooster0 opened this issue 1 year ago • 3 comments

It is highly recommended you read the commit descriptions. A lot of information in these commit descriptions.

The main focus here was fixing the fact that std.os.exit is not appropriately abstracted so that it's cross-platform. std.os.exit(0) is not cross-platform. There are operating systems where you can't return an integer (Plan 9), or at least not u8 when exiting (UEFI uses usize), or anything at all (many older systems). On Plan 9 for example, you return a string as the exit status instead of an integer. Or maybe you wrote an operating system where you return a data structure as the exit status. TL;DR we cannot assume u8 as the exit status for all platforms. The current approach of taking u8 but then fixing it up to make it kind of compatible with the underlying system is not ideal either.

This means there is now std.os.ExitStatus which is the type of the value that can be returned to the operating system when exiting.

However to make it easier to write cross-platform code without having to deal with std.os.ExitStatus directly (because the type depends on the OS), the declarations std.os.exit_status_success and std.os.exit_status_failure are provided for these two most common situations: 1. exiting and communicating success and 2. exiting and communicating failure.

This abstracts the API so that std.os.exit(0) still works but, assuming 0 means success here, it's now considered unidiomatic and the recommended pattern is std.os.exit(std.os.exit_status_success). Similarly, instead of std.os.exit(1) you use std.os.exit(std.os.exit_status_failure). For more specific exit statuses you either ignore cross-platform compatibility or come up with a different exit status for that specific case for each platform that you support.


Exiting a program goes in hand with the return type of the main function, because that is what's returned, so there are also many changes to lib/std/start.zig, namely:

  • std.start no longer exists in the public std API surface.
  • void, !void, noreturn + whatever std.os.ExitStatus is are now supported as the return types for main on all platforms. Previously, on the UEFI, error unions weren't supported.

Finally, now if your program returns from main a value of a type other than void, !void, or noreturn, you might want to use std.os.ExitStatus instead of u8. Basically,

pub fn main() u8 {
    return 0;
}

still works as before but the cross-platform way of doing it would be

pub fn main() std.os.ExitStatus {
    return std.os.exit_status_success;
}

(assuming that 0 means success).

Uses of std.os always look kind of low-level though so maybe we can create aliases for these definitions in std.process (std.process.exit is also an alias of std.os.exit). Or maybe aliases in std.start?

It is highly recommended you read the commit descriptions. A lot of information in these commit descriptions.

wooster0 avatar Jun 19 '23 13:06 wooster0

Another API I just thought of would be exit(.success), exit(.failure), and exit(.{ .other = 5 }). This however will break everything but it'd be an option as well. Advantage is its much shorter.

Again, this PR as-is isn't supposed to be a breaking change for any of the major platforms.

wooster0 avatar Jun 19 '23 13:06 wooster0

I just tested it on Plan9 and it seems like it worked:

cpu% cat good.zig
const std = @import("std");
pub fn main() std.os.ExitStatus {
    return std.os.exit_status_success;
}
cpu% ./good
cpu% echo $status

cpu% cat bad.zig
const std = @import("std");
pub fn main() std.os.ExitStatus {
    return std.os.exit_status_failure;
}
cpu% ./bad
cpu% echo $status
bad 598: failure
cpu% 

g-w1 avatar Jun 19 '23 14:06 g-w1

Ah. On POSIX AFAIU the exit code is pretty much defined as u8 right? Or at least an integer. So in that case I'd have to put this somewhere else. std.process seems good then. It also addresses what I mentioned in the PR description:

Uses of std.os always look kind of low-level though so maybe we can create aliases for these definitions in std.process (std.process.exit is also an alias of std.os.exit). Or maybe aliases in std.start?

wooster0 avatar Jun 20 '23 05:06 wooster0

This is blocked on #16135 for now.

wooster0 avatar Jun 21 '23 19:06 wooster0