reef-lang Feedback — Surfaced While Planning repoman v0.1
Source: Spec'ing repoman (a real Linux CLI tool: Incus container management + rsync/NFS backup) against reef-lang 0.5.9 stdlib. Items below are gaps and quirks discovered while mapping the spec onto actual stdlib APIs. None are blockers — repoman v0.1 will work around them — but each is a friction point a future user will hit when writing a real production reef program.
1. Encoders missing for structured formats — highest impact
Every config-driven program eventually needs to write its config, not just read it. Today:
| Module | Reads | Writes |
|---|---|---|
encoding.toml |
yes (toml_parse, toml_get*, toml_array_*) |
no |
encoding.json |
yes (json_parse, json_get*) |
no |
encoding.yaml |
yes | only line-at-a-time yaml_encode_string/int/bool — no nested tables, no lists |
encoding.ini |
yes (line helpers) | no |
encoding.csv |
TBD | TBD |
What repoman needs: a toml_serialize(...) (or equivalent for one of the structured formats) that handles strings, ints, bools, string arrays, tables, and table-arrays ([[project]]).
Suggested API shape (TOML; same idea for JSON):
fn toml_serialize(keys: [string], values: [string], count: int): string
// or a higher-level builder:
type TomlBuilder
fn toml_builder(): TomlBuilder
proc toml_set_string(b: TomlBuilder, key: string, val: string)
proc toml_set_int(b: TomlBuilder, key: string, val: int)
proc toml_set_bool(b: TomlBuilder, key: string, val: bool)
proc toml_set_string_array(b: TomlBuilder, key: string, vals: [string])
proc toml_array_append_table(b: TomlBuilder, name: string) // for [[project]]
fn toml_render(b: TomlBuilder): string
Parser/serializer asymmetry is the single biggest gap stdlib has for "write a real config-driven program."
2. io.file is missing rename and fsync
io.file exposes readFile, writeFile, appendFile, deleteFile, fileExists, fileSize, plus binary versions. Missing:
rename(old, new)—fs.ops.renameexists, so this is just a discoverability issue (people will look inio.filefirst).fsync(path)— there is no way to force a file's contents to disk before a rename. Important for the standard atomic-write recipe (write tmp → fsync → rename). Today repoman has to skip the fsync and accept a brief power-loss inconsistency window.
Suggested: add fn fsync(path: string): bool (or expose a file-handle-based API, then add flush_to_disk(handle)).
3. No generic Result[T, E]
core.result only exports concrete ResultInt, ResultStr, ResultBool, ResultFloat. For functions that return structs (e.g., load_registry() : Result<Registry, string>), every caller has to define a local enum:
type RegistryLoad = enum
Ok(Registry)
Err(string)
end RegistryLoad
core.option_generic already provides Option[T] — the same pattern would be a clean fit for Result[T, E]. The existing concrete ResultInt/ResultStr/etc. could be deprecated aliases or stay as ergonomic shortcuts.
Suggested: add core.result_generic with Result[T, E], is_ok[T,E], is_err[T,E], unwrap_ok[T,E], unwrap_err[T,E].
4. io.path is missing expand_home
~/foo expansion is one of the most frequent path operations in any user-facing CLI. Today io.path has join_path, dirname, basename, extension, normalize, is_absolute — but no expand_home or expand_user. Every caller writes (corrected from v1: str.substring_from does not exist publicly; the real call is str.substring(s, start, len)):
fn expand_home(p: string): string
if str.starts_with(p, "~/")
let len = str.length(p)
return path.join_path(env.get_env_or("HOME", ""), str.substring(p, 2, len - 2))
end if
return p
end expand_home
Suggested: fn expand_home(path: string): string (and maybe expand_user(path) for ~user/...) in io.path.
5. sys.flag doesn't support subcommand dispatch
sys.flag is a V-style flat flag parser. It calls sys.args directly inside parse() — there's no way to feed it a subcommand-sliced argv. The common CLI shape tool <subcommand> [subcommand-flags] (git, kubectl, incus, podman, repoman) requires a hand-rolled outer parser to extract the subcommand, then either a hand-rolled per-subcommand parser or a way to give sys.flag a sub-slice of argv.
Suggested: either
- (a)
flag_parser_from(args: [string]): FlagParserso callers can pass a sliced argv, or - (b) a
subcommand_parserAPI at the same level asflag_parserthat handles the dispatch.
Even a doc note ("for subcommand tools, hand-roll using sys.args.get(i)") would help — today the example in sys/flag.reef makes it look like the answer.
6. test.framework thin assertion surface
TestRunner (an active object — nice) already has assert_eq_int/bool/string/float, and as of v0.5.9 also has assert_true, assert_false, and assert_not_eq_int/bool/string (filer error in v1 of this report — caught reading only the first 100 lines of a 235-line file). Genuinely missing:
assert_contains(haystack, needle, message)for substring/array membership.assert_ok_*/assert_err_*for Result types (uses pattern-match plus a diagnostic that surfaces the wrapped error/value on failure).
7. TOML parser quirks
- Hard 1024-entry cap in
toml_alloc_keys(). Adequate for repoman (~127 projects max), but the cap is invisible —toml_parsereturns the entry count it processed; if the file had more, callers don't know. Suggest a return value indicating "truncated" (or accept a max-entries arg and error if exceeded — the_sizedvariants already do the latter, so making them the recommended path would help). - Parallel-array repr (
[string] keys,[string] values,int count) forces every caller to thread three values around. A smallTomlDocstruct (or[Pair]array) would be friendlier — the runtime cost is the same but the API surface is much smaller.
8. Naming nit
io.path.join_path(a, b) — the _path suffix is redundant since the function is in io.path. Most languages call this path.join. Minor, but discoverability matters when learning a new stdlib.
Summary table (ordered by impact on real-world reef programs)
| # | Item | Severity | Type |
|---|---|---|---|
| 1 | TOML/JSON encoders missing | High | Feature gap |
| 2 | io.file no rename/fsync |
High | Feature gap |
| 3 | No generic Result[T,E] |
Medium | API design |
| 4 | io.path.expand_home missing |
Medium | Feature gap |
| 5 | sys.flag no subcommand support |
Medium | API design |
| 6 | test.framework thin assertions |
Medium | Feature gap |
| 7 | TOML parser caps & repr | Low | API design |
| 8 | join_path naming |
Low | Naming |
repoman v0.1 works around all of these and ships. But every one of them costs the project some hand-rolled glue code that should be in stdlib.