nx_result.nx source
↩ module page · 112 lines · 4323 B
1// nx_result.nx -- proper Result type for substrate-side error handling.
2//
3// Per user 2026-05-14: "if we dont have capabilities and adding them
4// doesnt add bullshit like nulls where nulls shouldnt be go deep and
5// build up". This replaces the `return -1` / `return null` sentinel
6// pattern with a Result struct the caller MUST inspect before reading
7// the value.
8//
9// Discipline:
10// result := nx_result_ok(value) or nx_result_err(code)
11// caller calls nx_result_is_ok(result)
12// only if 1 does caller call nx_result_unwrap(result)
13// on 0 caller calls nx_result_err_code(result) to dispatch
14//
15// No null-pointer returns. No -1 sentinels. Every error has a
16// named code from the NX_ERR_* sealed enum.
17
18// nx_safety_envelope:
19// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
20// sil_target: SIL1
21// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
22// verdict: NOT_YET_EVALUATED
23
24import "nx_syscalls.nx"
25import "nx_runtime.nx"
26import "nx_tier.nx"
27
28// ===== sealed error codes ===========================================
29const NX_ERR_NONE: nx_int = 0
30const NX_ERR_OUT_OF_RANGE: nx_int = 1
31const NX_ERR_INVALID_INPUT: nx_int = 2
32const NX_ERR_FILE_NOT_FOUND: nx_int = 3
33const NX_ERR_PARSE_FAILED: nx_int = 4
34const NX_ERR_TAG_MISMATCH: nx_int = 5
35const NX_ERR_NOT_FOUND: nx_int = 6
36const NX_ERR_ALLOC_FAILED: nx_int = 7
37const NX_ERR_INVALID_STATE: nx_int = 8
38const NX_ERR_DIVIDE_BY_ZERO: nx_int = 9
39const NX_ERR_OVERFLOW: nx_int = 10
40const NX_ERR_UNKNOWN_CODE: nx_int = 11
41
42func nx_err_name(code: nx_int) -> *u8 {
43 if code == NX_ERR_NONE { return "NONE" as *u8 }
44 if code == NX_ERR_OUT_OF_RANGE { return "OUT_OF_RANGE" as *u8 }
45 if code == NX_ERR_INVALID_INPUT { return "INVALID_INPUT" as *u8 }
46 if code == NX_ERR_FILE_NOT_FOUND { return "FILE_NOT_FOUND" as *u8 }
47 if code == NX_ERR_PARSE_FAILED { return "PARSE_FAILED" as *u8 }
48 if code == NX_ERR_TAG_MISMATCH { return "TAG_MISMATCH" as *u8 }
49 if code == NX_ERR_NOT_FOUND { return "NOT_FOUND" as *u8 }
50 if code == NX_ERR_ALLOC_FAILED { return "ALLOC_FAILED" as *u8 }
51 if code == NX_ERR_INVALID_STATE { return "INVALID_STATE" as *u8 }
52 if code == NX_ERR_DIVIDE_BY_ZERO { return "DIVIDE_BY_ZERO" as *u8 }
53 if code == NX_ERR_OVERFLOW { return "OVERFLOW" as *u8 }
54 return "UNKNOWN_CODE" as *u8
55}
56
57// ===== Result struct ================================================
58// Status field is sealed: 1 = OK, 0 = ERR. Never any other value.
59// If status == OK, the value field holds the result.
60// If status == ERR, the err_code field holds the NX_ERR_* code.
61// Allocated via nx_result_ok / nx_result_err -- never raw mmap.
62struct NxResult {
63 status: nx_int,
64 value: nx_int,
65 err_code: nx_int,
66}
67
68const NX_RESULT_BYTES: nx_int = 24
69
70func nx_result_ok(value: nx_int) -> *NxResult {
71 let r: *NxResult = (sys_mmap(NX_RESULT_BYTES as i64)) as *NxResult
72 r.status = 1
73 r.value = value
74 r.err_code = NX_ERR_NONE
75 return r
76}
77
78func nx_result_err(code: nx_int) -> *NxResult {
79 let r: *NxResult = (sys_mmap(NX_RESULT_BYTES as i64)) as *NxResult
80 r.status = 0
81 r.value = 0
82 r.err_code = code
83 return r
84}
85
86// Predicates -- always safe to call.
87func nx_result_is_ok(r: *NxResult) -> nx_int { return r.status }
88func nx_result_is_err(r: *NxResult) -> nx_int {
89 if r.status == 1 { return 0 }
90 return 1
91}
92
93// Unwrap. Convention: caller checked is_ok first; if not, returns
94// the err_code (still not a sentinel-into-value-space because err_code
95// is a sealed enum, not a magic value). For strict callers,
96// nx_result_must_unwrap halts on error.
97func nx_result_unwrap(r: *NxResult) -> nx_int {
98 if r.status == 1 { return r.value }
99 return r.err_code
100}
101
102func nx_result_err_code(r: *NxResult) -> nx_int { return r.err_code }
103
104// Strict unwrap: prints the error name + exits the process if err.
105// Use for "this MUST succeed" contracts.
106func nx_result_must_unwrap(r: *NxResult, ctx: *u8) -> nx_int {
107 if r.status == 1 { return r.value }
108 print("FATAL: " as *u8); print(ctx); print(": " as *u8); print(nx_err_name(r.err_code))
109 println("" as *u8)
110 sys_exit(1)
111 return 0 // unreachable; satisfies type checker
112}