Repository navigation
juicy main #24510
Description
What if you could receive a bunch of goodies in main simply by asking for them?
pub fn main(init: std.process.Init, args: Args) anyerror!void { ... }The first arg:
pub const Init = struct {
/// Permanent storage for the entire process, cleaned automatically on exit.
arena: *std.heap.ArenaAllocator,
/// A default-selected general purpose allocator for temporary heap allocations.
/// Debug mode will set up leak checking.
gpa: std.mem.Allocator,
/// An appropriate default Io implementation based on the target configuration.
/// Debug mode will set up leak checking.
io: std.Io,
/// Environment variables.
environ: std.process.Environ,
/// Os-specific stuff.
os: union {
linux: Aux,
windows: Peb,
// ...
},
};The second arg is user-defined is does that CLI pattern where you provide a type that contains already parsed CLI args, and then the start code uses type reflection to parse the args before calling main:
const Args = struct {
named: struct {
example: u32,
},
/// Memory allocated into Init.arena
positional: []const []const u8,
};This would be a minimal, opinionated parser that users would opt out of the moment they want to do something remotely sophisticated, by accepting []const []const u8 instead of a struct.
If you think about it we already have a little bit of this with the return type - if it returns an error, start code does some opinionated stuff when an error is returned, such as print an error return trace (debug) or the error name (release) and exit(1).
Users who want full control over everything can continue to use pub fn main() noreturn {} and have the start code do almost nothing (beyond setting up Thread Local Storage, signal handling, PIE, etc., as it already does).
Activity
MasonRemaley commentedon Jul 19, 2025
Thinking out loud--
Returning errors from main is useful because it allows you to use "try" in main. Without this feature, people would either recreate it manually or just write worse code, so it makes sense that we support it.
I think my argument in favor of something like juicyMain is similar, though it's not as strong.
Take argument parsing as an example since it's one of the things juicyMain would provide. Argument parsing isn't hard, but it's still work to get right.
If basic argument parsing is provided out of the box, the quality of the average Zig executable likely increases because, well, why not add a nice CLI to your exe if it's easy?
This argument isn't as strong as my argument for returning errors from main though, since there are plenty of other ways to make argument parsing easy without juicyMain (a std api, a third party package, etc.)
Still, I think the convenience of this proposal probably outweighs the minimal increase in complexity.
MasonRemaley commentedon Jul 19, 2025
Side note--I've been using an argument parser that works similarly to what's described here in all my Zig projects.
I don't want to derail this thread w/ argument parsing details, but if/when it's time to sort that part of things out I can share some insights or help design it.
InKryption commentedon Jul 19, 2025
Question: would the bare main be in position to capture argv and other on-startup data?
Also, will untyped argv be allocated by the arena as well?
BeetMacol commentedon Jul 20, 2025
Would other combinations be allowed? Simple logic could allow anything, including:
Also, are the names
initandargsrequired, or can they be freely changed?nickelca commentedon Jul 20, 2025
Zig does not have reflection on parameter names, so no, the names aren't required
castholm commentedon Jul 20, 2025
Resource management-wise, how do you envision that command-line arguments and environment variables will be handled on operating systems like Windows, where they are passed to the process as WTF-16LE strings and need to be parsed and converted WTF-8?
Will the startup code parse and allocate converted copies of them using the default allocator before calling the user's main function, accepting that precise control over memory management is taken away from the user?
Or will you try to find a design for this hypothetical
std.process.InitAPI that takes these platform differences into consideration, by e.g. exposing the args through member functions likeinit.allocArgs(allocator)so that the user retains full control over the memory management strategy?paperdev-code commentedon Jul 20, 2025
I think it's great to finally have an argument parser provided by the
stdnamespace.Alternatively, we could have a system similar to
clap, where the parameters are parsed at comptime from the help message. Throughstd_options.init_params? I think a way to print a help message is required.Also, some way to integrate this with zig build's
Step.Runwould be cool. If we have a standard way to provide arguments, then perhaps we could provide them as a struct from inside the build script as well.scottredig commentedon Jul 20, 2025
2¢: I think this would make more sense as a template (
zig init) than as the standardmainfunctionality. I don't think this is likely to give the optimal implementation for significant programs, so this creates a larger jump where you either do things sub-optimally or you have to copy a significant portion of the functionality anyways. With a template it's easy to trim what you don't need (eg, delete the gpa code if you don't need it), and modify what isn't quite right (eg, argument parsing is likely to need specific work to display good errors to a program's user).mrjbq7 commentedon Jul 21, 2025
I think you could get there with some useful defaults modules:
matklad commentedon Jul 21, 2025
I am slightly apprehensive about
arenain particular, because usually arena is scoped to something (frame/request), or you use append-only arena instead your GPA. There's more than one reasonable semantics for arena, and blessing one of them disadvantages others.The rest look fine to me. Maybe this also needs
log: Log? Not sure how logging works in post-iopocalypse world. Similarly, could have stdin/stdout/stderr writers/readers set-up. Would solve the problem of tests liking to written stderr, if they don't have stderr.TigerBeetle flags.zig module might be an inspiration for
Argsparser, it's opinionated, small, and optimizes for low-friction when defining flags.silversquirl commentedon Jul 21, 2025
@matklad
arenaholds args and env vars. Since it's needed bystd.start, might as well letmainuse it too!Strictly speaking, args and envs could be allocated in the gpa, but since it's a lot of small allocations, an arena is better.
Arenas are also very useful for short-running programs, or for allocating things that are needed for the entire duration of the program run (eg. a config file). I think it's useful enough to be exposed, especially considering the alternative would often result in both
std.startand user code immediately creating their own arena.29 remaining items
KilianHanich commentedon Jul 31, 2025
Yes, just that this is being kept in mind: being able to use a Debugger on functions which were treated with Release optimisations.
MarvinHannott commentedon Aug 30, 2025
Maybe I'm just thick, but this sounds like quite an unnecessary feature that doesn't really add anything but confusion, just to avoid 3 lines of code.
ityonemo commentedon Oct 12, 2025
This is so good that at some point in the past I had hallucinated in an exchange with @kristoff-it that something similar (more simple, only args iirc) already existed. He corrected me. Happy to see it happening.
eamonburns commentedon Oct 22, 2025
It looks like #24510 (comment) is a pretty popular solution, but there hasn't been a whole lot of discussion around it.
Is there any reason why having the "magic" arguments to
mainis better than explicitly initializing a struct that contains all the things you need?I think:
initinstead of I/O, an allocator, an arena, the environment, etc.const init: std.Init = .initDefault();const init: std.Init = .initConfig(.{ thread_cound = 5 });.initDefault()would probably be exactly equivalent to.initConfig(.{}), and it might not even be needed(This third point is mostly geared toward new users, but I think it can apply to more experienced users as well. Less difference between the "easy" and "advanced" code)
castholm commentedon Oct 24, 2025
On platforms like Linux, command-line arguments, environment variables, auxiliary vectors and similar process arguments are only available as arguments passed to the entry point and can't be accessed any other way, so they need to be saved away to global variables or passed forward to parts of the program that need them. This is unlike e.g. Windows where process arguments are always available via the PEB and can be accessed by any parts of the process at any point in time.
#4524 identified that global mutable variables like
std.os.argv,std.os.environ, etc. is a huge footgun because they rely on startup code fromstd.startto initialize them. If you're building a library (static or dynamic) or otherwise not letting Zig be responsible for the entry point, these variables will remain undefined and APIs likestd.process.argsAlloc()will happily crash your program if you're unaware of these details.So even in the basic "non-juicy" form, without any argument parsing or allocator/IO setup,
main()still needs at least one argument:I don't have any strong opinions on whether the "juicy" setup/teardown of allocators, interfaces, arguments should be automatically handled by
std.startor explicitly by the user with something like.initDefault(). The thing that's important that it should still be possible to define amain()function that receives only the basic entry point arguments, without any unnecessary setup or memory allocation.bradcypert commentedon Nov 18, 2025
I don't have many thoughts on implementation details for this, but I do think having this feature would help allow users to delegate some commonly used boilerplate (arg parsing for example) to Zig itself and ultimately help with adoption. That being said, does this brush up against Zig's ethos of explicit-over-implicit?
KilianHanich commentedon Nov 18, 2025
About as much as using any other library.
So yes, you lose some explicitness, but that also goes for every kind of abstraction. But abstraction (if done right) helps with Zig's "communicate intent precisely" ethos.
std.heap.DebugAllocator: never detect TTY config
andrewrk commentedon Jan 1, 2026
https://codeberg.org/ziglang/zig/pulls/30644
`std.process.argsWithAllocator` is removed at zig master version (#25)