std.flags
std.flags is a command line flag parser, added in 1.8.1. It is built as register then parse: a program declares its flags up front with flag_bool, flag_str, and flag_int, then hands the whole argv to flags_parse, which fills the flag values and collects the leftover positional words in order. There are no short flags and no grouping; a flag is always its long name matched as --name. The module lives at lib/std/flags.dusk and is written in Dusk.
@import std.flagsThe design keeps a hard line between two kinds of wrong. Bad input on the command line, an unknown flag or a missing value, is an ordinary error value that flags_parse hands back for you to handle, so the library never prints and never exits on a user’s typo. A misuse of the library itself, a duplicate registration or a getter on the wrong kind, is a program bug that aborts, the same contract a bounds fault follows. Those two paths are spelled out under Errors below, and knowing which is which is most of knowing how to use the module.
The register then parse model
Section titled “The register then parse model”You build one Flags on the heap, register every flag into it, parse once, then read the values back through typed getters. The parser holds the registered flags and the collected positionals in one value:
export struct Flags { specs: *Vector<FlagSpec>, pos: *Vector<string>, prog: string, about: string,}specs holds the registered flags in registration order, which is also the order flags_usage prints them; pos holds the positional words in the order they appeared. prog and about are the program name and the one line summary the usage text prints. Because both vectors grow as you register and parse, you pass the Flags by pointer, the same shape std.vector and StringBuilder use: build one with alloc(flags_new(...)) and release it in two steps once you are done, flags_free for the vectors and then free for the allocation itself.
Each registered flag is a FlagSpec, and you rarely touch one directly, since the getters look it up for you by name:
export struct FlagSpec { name: string, help: string, kind: int64, sval: string, ival: int64, bval: bool, seen: bool,}The name is stored without the leading dashes, since parsing matches -- plus the name and the getters look it up by that same bare name. Only the field for the flag’s kind carries a value, bval for a bool, sval for a str, ival for an int, and seen records whether the flag ever appeared on the command line. Every string a spec holds, its name, its help text, and a str flag’s default, is a borrow of your own literal, never a copy the module owns; the one owned string a spec can carry is the value copy a str flag makes when it is set during parsing, which flags_free releases.
Functions
Section titled “Functions”| Function | Description |
|---|---|
flags_new(prog: string, about: string) -> Flags | A fresh parser with no flags registered, knowing the program name and a one line about text for its usage output. |
flag_bool(f: *Flags, name: string, help: string) -> void | Register a boolean flag under name; the bare --name sets it true. |
flag_str(f: *Flags, name: string, def: string, help: string) -> void | Register a string flag under name with the default def. |
flag_int(f: *Flags, name: string, def: int64, help: string) -> void | Register an integer flag under name with the default def. |
flags_parse(f: *Flags, argv: string[], start: int64) -> error | Parse argv from index start, filling the flags and collecting positionals; returns an error naming the first bad token, or a clean error. |
flag_get_bool(f: *Flags, name: string) -> bool | The value of the bool flag name, false when it never appeared. |
flag_get_str(f: *Flags, name: string) -> string | The value of the string flag name, its default when it never appeared. |
flag_get_int(f: *Flags, name: string) -> int64 | The value of the integer flag name, its default when it never appeared. |
flag_seen(f: *Flags, name: string) -> bool | Whether the flag name appeared on the command line at least once, across any kind. |
flags_pos_len(f: *Flags) -> int64 | The number of positional words collected during parsing. |
flags_pos_at(f: *Flags, i: int64) -> string | The positional word at index i, in the order the words appeared. |
flags_usage(f: *Flags) -> string | A fresh heap usage string you own: the usage line, the about line, then one line per registered flag. |
flags_free(f: *Flags) -> void | Free the two vectors the parser owns, and the value copy of each string flag that was set. |
The three registrars all take the name without its leading dashes, since parsing adds the -- for you and the getters look the flag up by the bare name. Register a flag once: a second registration under a name already taken is a program bug and aborts, so the duplicate is caught at build-out rather than silently overwriting the first spec. flag_str and flag_int each carry a default that the matching getter returns when the flag never appears on the line, so a getter always has something to hand back and you never branch on presence unless you want to, which is what flag_seen is for.
The three getters and flag_seen look a flag up by name and read the field for its kind. Reading through the wrong getter, flag_get_int on a name you registered as a bool, is a program bug and aborts the same way an unregistered name does. A getter never returns a zero value to paper over the mismatch.
The parsing grammar
Section titled “The parsing grammar”flags_parse walks argv from start to the end and sorts each token into a flag or a positional. The start index is the first token to read, commonly 1 so the program name at index 0 is skipped; a self-contained literal argv with no program name in front parses from 0.
A token that begins with -- names a flag, and there are two ways to give it a value. --name value binds the following token, and --name=value binds inline; both forms bind the same flag to the same value, so which you write is a matter of taste. A bool flag is the exception: it takes no value, so the bare --name sets it true and --name=x on a bool is an error. An integer flag parses its value as a base ten signed integer, and a value that is not an integer is an error rather than a silent zero.
A lone -- ends flag parsing. Every token after it is a positional, even one that begins with a dash, which is how you pass a positional word that would otherwise read as a flag. Short of that terminator, any token that does not begin with -- is a positional and keeps its order, and that deliberately includes a single-dash word like -x and a negative number like -5: neither begins with two dashes, so both land in the positional list untouched. The positionals come back in the exact order they appeared, read with flags_pos_len and flags_pos_at.
A repeated flag is last-wins. --count 1 --count 2 leaves the flag at 2, with flag_seen staying true across the repeat; a str flag frees the value it held before storing the next, so a repeat never leaks.
Errors
Section titled “Errors”The module draws a hard line between a user’s mistake and a programmer’s, and it handles the two in opposite ways.
Command line input, returned as a value
Section titled “Command line input, returned as a value”A bad command line is an ordinary error that flags_parse returns, naming the first bad token. The library never prints it and never exits; that is your call, which is what lets a program print its own usage and choose its own exit code. Because flags_parse returns an error, you must handle the result, resolving it with exists, check, or ignore before it goes out of scope, the same must-handle rule every std error carries. See Error handling.
There are four such messages, each a fresh heap string the returned error owns:
unknown flag '--frob'flag '--timeout' needs a valueflag '--timeout' needs an integer, got 'abc'flag '--verbose' takes no valueThe first fires on a --name that was never registered, the second on a str or int flag given no value with nothing following it, the third on an int flag whose value does not parse, and the fourth on a bool flag written as --name=x. Parsing stops at the first of them and hands it straight back.
Library misuse, which aborts
Section titled “Library misuse, which aborts”A misuse of the library itself is a program bug, not input, so it prints a line with a fatal: flags: prefix and aborts rather than returning. Three cases reach it:
fatal: flags: flag '--verbose' already registeredfatal: flags: no int flag named '--verbose'fatal: flags: positional index out of boundsThe first is a duplicate registration, caught the moment the second flag_* call runs. The second is a getter fault: reading a flag through a getter for the wrong kind, or through any getter under a name never registered, aborts with the wanted kind named, bool, string, or int for the three typed getters and registered for flag_seen. The third is flags_pos_at called with an index below zero or at or past flags_pos_len, the same bounds contract vec_get follows. None of the three can come from a user’s typo; each is a bug in the program’s own wiring, so it stops the program with a clear cause instead of limping on.
Usage text
Section titled “Usage text”flags_usage returns a fresh heap string you own and free: the usage line, the about line, then one line per registered flag in registration order. A str flag is marked <str> and an int flag <int> after its name, and each prints its default; a bool flag prints neither. The output is deterministic, a pure function of the registered flags, so it can be pinned byte for byte in a test. For the greeting parser built in the example below it reads:
usage: greet [options]greet someone a few times --verbose print more --name <str> who to greet (default: world) --count <int> how many times (default: 1)The string is yours: free it once you have printed it. It is the natural thing to print alongside a parse error before exiting, which the example does.
Example
Section titled “Example”This program registers the three flag kinds, parses an argv, reads the values, walks the positionals, and cleans up. The argv here is a literal array so the run is self-contained and deterministic; a real program takes main(argc: int32, argv: string[]) and parses from 1 to skip the program name. The while loops and mut require the procedural paradigm; see /reference/paradigm-system/.
@paradigm procedural
@import std.flags
func main() -> int32 { // A real program takes main(argc: int32, argv: string[]) and parses from 1 // to skip the program name; this literal argv keeps the run self-contained. argv: string[] = ["--verbose", "--name=alice", "--count", "3", "gift", "--", "--not-a-flag"]
f: *Flags = alloc(flags_new("greet", "greet someone a few times")) flag_bool(f, "verbose", "print more") flag_str(f, "name", "world", "who to greet") flag_int(f, "count", 1, "how many times")
e: error = flags_parse(f, argv, 0) if e.exists() { // A bad command line is an error value, so print it, print usage, and // exit yourself; the library never prints and never exits on a typo. println(e.message) u: string = flags_usage(f) print(u) free(u) flags_free(f) free(f) return 1 }
verbose: bool = flag_get_bool(f, "verbose") name: string = flag_get_str(f, "name") count: int64 = flag_get_int(f, "count")
mut i: int64 = 0 while i < count { print("hello, ") println(name) // hello, alice, three times i = i + 1 } if verbose { println(flag_seen(f, "count")) // 1, count was set on the line }
// The positional words kept their order; "--not-a-flag" is one only because // it followed the "--" terminator. mut p: int64 = 0 while p < flags_pos_len(f) { println(flags_pos_at(f, p)) // gift then --not-a-flag p = p + 1 }
flags_free(f) // frees the two vectors free(f) // frees the Flags allocation return 0}--verbose sets its bool with no value, --name=alice binds inline, and --count 3 binds the following token. The -- then ends flag parsing, so --not-a-flag rides through as a positional next to gift rather than raising unknown flag. The run prints hello, alice three times, then 1 for flag_seen, then the two positionals in order.
The must-handle rule on the parse result is not optional. Dropping the error on the floor, calling flags_parse without binding or resolving what it returns, is rejected at check time with this expression's error result is ignored, so a user’s typo can never slip past unhandled.
The four input errors each come back as a value you print and recover from, never a fault:
@paradigm procedural
@import std.flags
func try_parse(argv: string[]) -> void { f: *Flags = alloc(flags_new("greet", "a greeting demo")) flag_bool(f, "verbose", "print more") flag_str(f, "name", "world", "who to greet") flag_int(f, "count", 1, "how many times") e: error = flags_parse(f, argv, 0) if e.exists() { println(e.message) } else { println("(ok)") } flags_free(f) free(f)}
func main() -> int32 { try_parse(["--frob"]) // unknown flag '--frob' try_parse(["--name"]) // flag '--name' needs a value try_parse(["--count", "abc"]) // flag '--count' needs an integer, got 'abc' try_parse(["--verbose=x"]) // flag '--verbose' takes no value return 0}Each call parses into a fresh flag set so one error never colors the next, and the program prints all four in order and keeps running, since none of them is a fault.
Ownership summary
Section titled “Ownership summary”- A
Flagsbuilt withalloc(flags_new(...))is released in two steps:flags_free(f)frees the two vectors the parser owns, its specs and its positionals, and thenfree(f)releases the allocation itself. Flagsstores only borrows, your own name, help, and default literals and theargvstrings, all of process lifetime, soflags_freefrees those two vectors and nothing else. The one exception is a str flag’s value copy made during parsing, whichflags_freealso releases; a str flag that was never set still holds its borrowed default and that is left alone.- The string from
flags_usageis a fresh heap allocation you own;freeit once you have printed it. - A flag name passed to a registrar or a getter is given without its leading dashes; parsing adds the
--and the getters look it up by the bare name.
For the rest of the standard library, see /stdlib/overview/, and for driving a program from the command line, the CLI.