Skip to content

feat: let init, update and view return error unions - #132

Merged
meszmate merged 1 commit into
mainfrom
feat/fallible-model-callbacks
Aug 14, 2026
Merged

meszmate merged 1 commit into
mainfrom
feat/fallible-model-callbacks

Conversation

@meszmate

Copy link
Copy Markdown
Owner

Not tied to an issue — this came out of reviewing the codebase for things that would simplify the library.

The problem

view returns []const u8, so it cannot fail. Rendering allocates on nearly every line, which means every allocation needs its own fallback. The examples currently carry 245 of them:

101  catch "";
 38  catch "Error";
 14  catch "error";
 13  catch "?";
 ...

The quick-start snippet in src/root.zig taught the pattern too. Each one silently turns an allocation failure into a rendering artefact that is indistinguishable from real output — "?" where a number should be, a title that quietly loses its styling.

The change

init, update and view may now each return !T instead of T. The runtime detects that per function at compile time and propagates the error out of tick(), where the caller already handles failure.

// before
pub fn view(self: *const Model, ctx: *const zz.Context) []const u8 {
    const title = title_style.render(ctx.allocator, "Report") catch "Report";
    const body = std.fmt.allocPrint(ctx.allocator, "{d} rows", .{self.rows}) catch "?";
    return std.fmt.allocPrint(ctx.allocator, "{s}\n{s}", .{ title, body }) catch "Error";
}

// after
pub fn view(self: *const Model, ctx: *const zz.Context) ![]const u8 {
    const title = try title_style.render(ctx.allocator, "Report");
    const body = try std.fmt.allocPrint(ctx.allocator, "{d} rows", .{self.rows});
    return std.fmt.allocPrint(ctx.allocator, "{s}\n{s}", .{ title, body });
}

Fully backwards compatible — both forms compile, mixed freely (a fallible view alongside a plain update is fine). Nothing existing has to change.

Also

src/core/model.zig collects the Model contract in one place: the @hasDecl checks that were duplicated between Program and SubProgram (now with a message naming the type and the missing declaration), plus the two comptime helpers.

SubProgram mirrors its child's fallibility, so a fallible child model gives a SubProgram whose view is fallible too — otherwise composing one would be a compile error with no way out.

Tests

refAllDecls on Program(FallibleModel) and Program(DummyModel) forces both instantiations to be fully analysed, so a try that does not line up is a build failure rather than something that only shows up in a user's app. Verified the test actually bites by removing a try and watching it fail. Same for SubProgram in both shapes, plus unit tests for the comptime helpers.

counter and hello_world are converted as worked examples. The remaining 42 still compile unchanged and can move over gradually.

zig build test and zig build clean on 0.16.0.

`view` returns `[]const u8`, so every allocation inside it needs its own
fallback. The examples carry 245 of them -- `catch "Error"`, `catch ""`,
`catch "?"` -- and the quick-start snippet in the README taught the
pattern. Each one turns an allocation failure into a rendering artefact
that is indistinguishable from real output.

`init`, `update` and `view` may now each return `!T` instead of `T`. The
runtime detects that per function at compile time and propagates the
error out of `tick()`, where the caller already handles failure. Both
forms compile, so nothing existing has to change.

`src/core/model.zig` holds the Model contract in one place: the
`@hasDecl` checks that were duplicated in `Program` and `SubProgram`,
plus the two comptime helpers. `SubProgram` mirrors its child, so a
fallible child model gives a `SubProgram` whose `view` is fallible too.

`counter` and `hello_world` are converted as worked examples; the rest
still compile unchanged and can move over gradually.
@meszmate
meszmate force-pushed the feat/fallible-model-callbacks branch from 1fb58a9 to 7d94d85 Compare August 14, 2026 04:37
@meszmate
meszmate merged commit 79ee060 into main Aug 14, 2026
12 checks passed
@meszmate
meszmate deleted the feat/fallible-model-callbacks branch August 28, 2026 12:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant