lib: port errors.errno + os errno/strerror sys-layer (#6)

Hare-faithful port of errors::errno (ref/hare/errors/{rt,common,opaque}.ha): the 13 named common error conditions, opaque_data/opaque_ (the type-erased tail whose strerror fn-ptr defers to os.strerror), and errno(os.errno) error mapping the ~12 mapped errnos to named conditions and wrapping the unmapped tail in opaque_. The raw errno type (!i32, kernel-int width, distinct from oserror's !i64 negative raw return), the E* constants, and the strerror message table live in lib/os: ww folds Hare's sys role into os, so os is the import floor that lib/io and lib/errors build on -- documented in lib/CLAUDE.md (os never imports io or errors). errors.error is explicitly enumerated, matching Hare; the ...errors::error spread is only io.error's (blocked by #199b). Prereq for post-eFinal #5's faithful io error mapping; retires the nomem-collapse interim. Adds errnotest (mapping / opaque-tail / strerror) + test/wcc/902_errno_run. Landing required two wwstage cgen fixes (#9 struct-variant-large-union return, #11 deref-store alias narrow). Divergences cited at-site: bare-type-name return -> let+return; switch fall-through vs Hare's exhaustiveness-only default; opaque_ const dropped.
This commit is contained in:
2026-05-30 05:58:06 +09:00
parent 0eb3465919
commit fc9b486fb4
13 changed files with 905 additions and 39 deletions

View File

@@ -265,6 +265,57 @@ export fn lseek(fd: i32, off: i64, w: whence) i64 = {
// errors::errno carried inside io::error.
export type oserror = !i64;
// errno — the raw Linux errno as a positive code (ref/hare/sys/+linux/
// errno.ha:5, `errno = !int`). ww folds Hare's `sys` role into os
// (lib/CLAUDE.md), so the sys::errno machinery lands here. Spelled i32
// rather than int: Linux errnos are kernel ints (32-bit), keeping os's
// kernel-facing surface uniformly i32. Distinct from [[oserror]] (!i64,
// the syscall's *negative* raw return) — the two model different
// things, so they are not unified; the negative→positive normalization
// lives at the oserror→errors.error boundary in those callers.
export type errno = !i32;
// Mapped errno values, ref/hare/sys/+linux/errno.ha:559-682. Positive,
// matching Hare's defs (the kernel returns -N; the wrap-to-positive is
// the caller's concern). Subset: exactly the errnos [[errors.errno]]
// maps to a named condition; grow as callers surface more.
export def ENOENT: errno = 2;
export def EINTR: errno = 4;
export def EAGAIN: errno = 11;
export def EACCES: errno = 13;
export def EBUSY: errno = 16;
export def EEXIST: errno = 17;
export def EINVAL: errno = 22;
export def EOVERFLOW: errno = 75;
export def ENETUNREACH: errno = 101;
export def ETIMEDOUT: errno = 110;
export def ECONNREFUSED: errno = 111;
export def ECANCELED: errno = 125;
// strerror — human-readable text for an [[errno]] (Hare's
// sys::strerror, ref/hare/sys/+linux/errno.ha:18). FAITHFUL MINIMAL
// SUBSET: the mapped errnos above plus a generic fallback; grow the
// switch as callers surface more (lib/CLAUDE.md documented-subset, not
// a workaround). Messages verbatim from the reference. Hare's
// unknown_errno formats the numeric value; that is deferred.
export fn strerror(err: errno) str = {
switch (err) {
case ENOENT: return "No such file or directory";
case EINTR: return "Interrupted system call";
case EAGAIN: return "Resource temporarily unavailable";
case EACCES: return "Permission denied";
case EBUSY: return "Device or resource busy";
case EEXIST: return "File exists";
case EINVAL: return "Invalid argument";
case EOVERFLOW: return "Value too large for defined data type";
case ENETUNREACH: return "Network is unreachable";
case ETIMEDOUT: return "Connection timed out";
case ECONNREFUSED: return "Connection refused";
case ECANCELED: return "Operation canceled";
};
return "Unknown error";
};
// filesize — byte length of an open fd via lseek-to-end-and-back.
export fn filesize(fd: i32) (i64 | oserror) = {
let end: i64 = lseek(fd, 0i64, whence.END);

View File

@@ -265,6 +265,57 @@ export fn lseek(fd: i32, off: i64, w: whence) i64 = {
// errors::errno carried inside io::error.
export type oserror = !i64;
// errno — the raw Linux errno as a positive code (ref/hare/sys/+linux/
// errno.ha:5, `errno = !int`). ww folds Hare's `sys` role into os
// (lib/CLAUDE.md), so the sys::errno machinery lands here. Spelled i32
// rather than int: Linux errnos are kernel ints (32-bit), keeping os's
// kernel-facing surface uniformly i32. Distinct from [[oserror]] (!i64,
// the syscall's *negative* raw return) — the two model different
// things, so they are not unified; the negative→positive normalization
// lives at the oserror→errors.error boundary in those callers.
export type errno = !i32;
// Mapped errno values, ref/hare/sys/+linux/errno.ha:559-682. Positive,
// matching Hare's defs (the kernel returns -N; the wrap-to-positive is
// the caller's concern). Subset: exactly the errnos [[errors.errno]]
// maps to a named condition; grow as callers surface more.
export def ENOENT: errno = 2;
export def EINTR: errno = 4;
export def EAGAIN: errno = 11;
export def EACCES: errno = 13;
export def EBUSY: errno = 16;
export def EEXIST: errno = 17;
export def EINVAL: errno = 22;
export def EOVERFLOW: errno = 75;
export def ENETUNREACH: errno = 101;
export def ETIMEDOUT: errno = 110;
export def ECONNREFUSED: errno = 111;
export def ECANCELED: errno = 125;
// strerror — human-readable text for an [[errno]] (Hare's
// sys::strerror, ref/hare/sys/+linux/errno.ha:18). FAITHFUL MINIMAL
// SUBSET: the mapped errnos above plus a generic fallback; grow the
// switch as callers surface more (lib/CLAUDE.md documented-subset, not
// a workaround). Messages verbatim from the reference. Hare's
// unknown_errno formats the numeric value; that is deferred.
export fn strerror(err: errno) str = {
switch (err) {
case ENOENT: return "No such file or directory";
case EINTR: return "Interrupted system call";
case EAGAIN: return "Resource temporarily unavailable";
case EACCES: return "Permission denied";
case EBUSY: return "Device or resource busy";
case EEXIST: return "File exists";
case EINVAL: return "Invalid argument";
case EOVERFLOW: return "Value too large for defined data type";
case ENETUNREACH: return "Network is unreachable";
case ETIMEDOUT: return "Connection timed out";
case ECONNREFUSED: return "Connection refused";
case ECANCELED: return "Operation canceled";
};
return "Unknown error";
};
// filesize — byte length of an open fd via lseek-to-end-and-back.
export fn filesize(fd: i32) (i64 | oserror) = {
let end: i64 = lseek(fd, 0i64, whence.END);
@@ -14167,15 +14218,23 @@ export fn close(s: stream) (void | error) = {
// Named-void tagged-union variants, so `(T | errors.invalid | ...)`
// composes with every other module's error surface at the tag level.
// Per-domain errors (io.eof, io.closed, io.underread, strconv.overflow,
// ...) live in their own modules; this module ships only the generic
// conditions Hare's errors:: exports.
//
// Subset shipped today; add more from Hare's errors/common.ha as callers
// need them.
// ...) live in their own modules; this module ships the generic
// conditions Hare's errors:: exports plus the [[errno]] bridge that
// wraps a raw [[os.errno]] into a portable [[error]].
// A function was called with an invalid combination of arguments.
package errors;
import os;
// The named conditions, ordered per ref/hare/errors/common.ha.
// The requested resource is not available.
export type busy = !void;
// An attempt was made to create a resource which already exists.
export type exists = !void;
// A function was called with an invalid combination of arguments.
export type invalid = !void;
// The user does not have permission to use this resource.
@@ -14184,12 +14243,112 @@ export type noaccess = !void;
// An entry was requested which does not exist.
export type noentry = !void;
// An attempt was made to create a resource which already exists.
export type exists = !void;
// The requested operation caused a numeric overflow condition.
export type overflow = !void;
// The requested operation is not supported.
export type unsupported = !void;
// The requested operation timed out.
export type timeout = !void;
// The requested operation was cancelled.
export type cancelled = !void;
// A connection attempt was refused.
export type refused = !void;
// An operation was interrupted.
export type interrupted = !void;
// The user should attempt an operation again.
export type again = !void;
// Network unreachable
export type netunreachable = !void;
// Up to 24 bytes of arbitrary, 8-byte-aligned storage for the opaque
// error type's domain-specific data. ref/hare/errors/opaque.ha:41.
export type opaque_data = [3]u64;
// An "opaque" error wraps an implementation-specific underlying error
// behind a function that stringifies it plus a small storage area.
// ref/hare/errors/opaque.ha:32. The `strerror` field keeps Hare's `*fn`
// pointer (filled via `(&fn): *fn(...)`, mirroring io's `*reader` slot,
// lib/io/types.ww:53); only Hare's `const` is dropped (ww has none, cf
// lib/io/types.ww:57).
export type opaque_ = !struct {
strerror: *fn(op: *opaque_data) str,
data: opaque_data,
};
// A tagged union of all error types. ref/hare/errors/common.ha:43.
// Enumerated explicitly rather than spread via Hare's `...error` —
// ww's spread-flatten layout is the deferred #199b / #204 fix.
export type error = !(
busy |
exists |
invalid |
noaccess |
noentry |
overflow |
unsupported |
timeout |
cancelled |
refused |
interrupted |
again |
netunreachable |
opaque_
);
// Wraps an [[os.errno]] to produce an [[error]], which may be
// [[opaque_]]. ref/hare/errors/rt.ha:9. The mapped errnos become named
// conditions; the unmapped tail is carried in an [[opaque_]] whose
// stringifier defers to [[os.strerror]].
export fn errno(e: os.errno) error = {
// A `!void` condition is returned by instance, not by name: a bare
// `return refused;` would emit an undefined symbol reference (the
// type, not a value). cf lib/io/stream.ww:53-56. The instance
// auto-widens to the [[error]] union on return.
switch (e) {
case os.ECONNREFUSED: { let r: refused; return r; };
case os.ECANCELED: { let r: cancelled; return r; };
case os.EOVERFLOW: { let r: overflow; return r; };
case os.EACCES: { let r: noaccess; return r; };
case os.EINVAL: { let r: invalid; return r; };
case os.EEXIST: { let r: exists; return r; };
case os.ENOENT: { let r: noentry; return r; };
case os.ETIMEDOUT: { let r: timeout; return r; };
case os.EBUSY: { let r: busy; return r; };
case os.EINTR: { let r: interrupted; return r; };
case os.EAGAIN: { let r: again; return r; };
case os.ENETUNREACH: { let r: netunreachable; return r; };
};
// An unmapped errno falls through the switch into the opaque_ wrap
// below. ww switches aren't required exhaustive, so Hare's terminal
// `case => void;` (rt.ha:24, present only to satisfy exhaustiveness)
// is dropped rather than written as an explicit no-op default —
// matching the sibling [[os.strerror]] fall-through.
//
// ww has no `static assert`; Hare guards size(errno) <=
// size(opaque_data) there. The invariant holds structurally:
// opaque_data is [3]u64 (24B), errno is one machine int.
let err: opaque_;
err.strerror = (&rt_strerror): *fn(op: *opaque_data) str;
let ptr = (&err.data): *os.errno;
*ptr = e;
return err;
};
// rt_strerror — the [[opaque_]] stringifier for an errno wrapped by
// [[errno]]. ref/hare/errors/rt.ha:31.
fn rt_strerror(op: *opaque_data) str = {
let e = (op): *os.errno;
return os.strerror(*e);
};
// types — error union, mode/whence enums, reader/writer/closer
// fn-type aliases. Project #94 fold-eFinal; the fn-aliases target
// `stream` (= `*vtable`, the single io surface).

View File

@@ -265,6 +265,57 @@ export fn lseek(fd: i32, off: i64, w: whence) i64 = {
// errors::errno carried inside io::error.
export type oserror = !i64;
// errno — the raw Linux errno as a positive code (ref/hare/sys/+linux/
// errno.ha:5, `errno = !int`). ww folds Hare's `sys` role into os
// (lib/CLAUDE.md), so the sys::errno machinery lands here. Spelled i32
// rather than int: Linux errnos are kernel ints (32-bit), keeping os's
// kernel-facing surface uniformly i32. Distinct from [[oserror]] (!i64,
// the syscall's *negative* raw return) — the two model different
// things, so they are not unified; the negative→positive normalization
// lives at the oserror→errors.error boundary in those callers.
export type errno = !i32;
// Mapped errno values, ref/hare/sys/+linux/errno.ha:559-682. Positive,
// matching Hare's defs (the kernel returns -N; the wrap-to-positive is
// the caller's concern). Subset: exactly the errnos [[errors.errno]]
// maps to a named condition; grow as callers surface more.
export def ENOENT: errno = 2;
export def EINTR: errno = 4;
export def EAGAIN: errno = 11;
export def EACCES: errno = 13;
export def EBUSY: errno = 16;
export def EEXIST: errno = 17;
export def EINVAL: errno = 22;
export def EOVERFLOW: errno = 75;
export def ENETUNREACH: errno = 101;
export def ETIMEDOUT: errno = 110;
export def ECONNREFUSED: errno = 111;
export def ECANCELED: errno = 125;
// strerror — human-readable text for an [[errno]] (Hare's
// sys::strerror, ref/hare/sys/+linux/errno.ha:18). FAITHFUL MINIMAL
// SUBSET: the mapped errnos above plus a generic fallback; grow the
// switch as callers surface more (lib/CLAUDE.md documented-subset, not
// a workaround). Messages verbatim from the reference. Hare's
// unknown_errno formats the numeric value; that is deferred.
export fn strerror(err: errno) str = {
switch (err) {
case ENOENT: return "No such file or directory";
case EINTR: return "Interrupted system call";
case EAGAIN: return "Resource temporarily unavailable";
case EACCES: return "Permission denied";
case EBUSY: return "Device or resource busy";
case EEXIST: return "File exists";
case EINVAL: return "Invalid argument";
case EOVERFLOW: return "Value too large for defined data type";
case ENETUNREACH: return "Network is unreachable";
case ETIMEDOUT: return "Connection timed out";
case ECONNREFUSED: return "Connection refused";
case ECANCELED: return "Operation canceled";
};
return "Unknown error";
};
// filesize — byte length of an open fd via lseek-to-end-and-back.
export fn filesize(fd: i32) (i64 | oserror) = {
let end: i64 = lseek(fd, 0i64, whence.END);

View File

@@ -265,6 +265,57 @@ export fn lseek(fd: i32, off: i64, w: whence) i64 = {
// errors::errno carried inside io::error.
export type oserror = !i64;
// errno — the raw Linux errno as a positive code (ref/hare/sys/+linux/
// errno.ha:5, `errno = !int`). ww folds Hare's `sys` role into os
// (lib/CLAUDE.md), so the sys::errno machinery lands here. Spelled i32
// rather than int: Linux errnos are kernel ints (32-bit), keeping os's
// kernel-facing surface uniformly i32. Distinct from [[oserror]] (!i64,
// the syscall's *negative* raw return) — the two model different
// things, so they are not unified; the negative→positive normalization
// lives at the oserror→errors.error boundary in those callers.
export type errno = !i32;
// Mapped errno values, ref/hare/sys/+linux/errno.ha:559-682. Positive,
// matching Hare's defs (the kernel returns -N; the wrap-to-positive is
// the caller's concern). Subset: exactly the errnos [[errors.errno]]
// maps to a named condition; grow as callers surface more.
export def ENOENT: errno = 2;
export def EINTR: errno = 4;
export def EAGAIN: errno = 11;
export def EACCES: errno = 13;
export def EBUSY: errno = 16;
export def EEXIST: errno = 17;
export def EINVAL: errno = 22;
export def EOVERFLOW: errno = 75;
export def ENETUNREACH: errno = 101;
export def ETIMEDOUT: errno = 110;
export def ECONNREFUSED: errno = 111;
export def ECANCELED: errno = 125;
// strerror — human-readable text for an [[errno]] (Hare's
// sys::strerror, ref/hare/sys/+linux/errno.ha:18). FAITHFUL MINIMAL
// SUBSET: the mapped errnos above plus a generic fallback; grow the
// switch as callers surface more (lib/CLAUDE.md documented-subset, not
// a workaround). Messages verbatim from the reference. Hare's
// unknown_errno formats the numeric value; that is deferred.
export fn strerror(err: errno) str = {
switch (err) {
case ENOENT: return "No such file or directory";
case EINTR: return "Interrupted system call";
case EAGAIN: return "Resource temporarily unavailable";
case EACCES: return "Permission denied";
case EBUSY: return "Device or resource busy";
case EEXIST: return "File exists";
case EINVAL: return "Invalid argument";
case EOVERFLOW: return "Value too large for defined data type";
case ENETUNREACH: return "Network is unreachable";
case ETIMEDOUT: return "Connection timed out";
case ECONNREFUSED: return "Connection refused";
case ECANCELED: return "Operation canceled";
};
return "Unknown error";
};
// filesize — byte length of an open fd via lseek-to-end-and-back.
export fn filesize(fd: i32) (i64 | oserror) = {
let end: i64 = lseek(fd, 0i64, whence.END);

View File

@@ -265,6 +265,57 @@ export fn lseek(fd: i32, off: i64, w: whence) i64 = {
// errors::errno carried inside io::error.
export type oserror = !i64;
// errno — the raw Linux errno as a positive code (ref/hare/sys/+linux/
// errno.ha:5, `errno = !int`). ww folds Hare's `sys` role into os
// (lib/CLAUDE.md), so the sys::errno machinery lands here. Spelled i32
// rather than int: Linux errnos are kernel ints (32-bit), keeping os's
// kernel-facing surface uniformly i32. Distinct from [[oserror]] (!i64,
// the syscall's *negative* raw return) — the two model different
// things, so they are not unified; the negative→positive normalization
// lives at the oserror→errors.error boundary in those callers.
export type errno = !i32;
// Mapped errno values, ref/hare/sys/+linux/errno.ha:559-682. Positive,
// matching Hare's defs (the kernel returns -N; the wrap-to-positive is
// the caller's concern). Subset: exactly the errnos [[errors.errno]]
// maps to a named condition; grow as callers surface more.
export def ENOENT: errno = 2;
export def EINTR: errno = 4;
export def EAGAIN: errno = 11;
export def EACCES: errno = 13;
export def EBUSY: errno = 16;
export def EEXIST: errno = 17;
export def EINVAL: errno = 22;
export def EOVERFLOW: errno = 75;
export def ENETUNREACH: errno = 101;
export def ETIMEDOUT: errno = 110;
export def ECONNREFUSED: errno = 111;
export def ECANCELED: errno = 125;
// strerror — human-readable text for an [[errno]] (Hare's
// sys::strerror, ref/hare/sys/+linux/errno.ha:18). FAITHFUL MINIMAL
// SUBSET: the mapped errnos above plus a generic fallback; grow the
// switch as callers surface more (lib/CLAUDE.md documented-subset, not
// a workaround). Messages verbatim from the reference. Hare's
// unknown_errno formats the numeric value; that is deferred.
export fn strerror(err: errno) str = {
switch (err) {
case ENOENT: return "No such file or directory";
case EINTR: return "Interrupted system call";
case EAGAIN: return "Resource temporarily unavailable";
case EACCES: return "Permission denied";
case EBUSY: return "Device or resource busy";
case EEXIST: return "File exists";
case EINVAL: return "Invalid argument";
case EOVERFLOW: return "Value too large for defined data type";
case ENETUNREACH: return "Network is unreachable";
case ETIMEDOUT: return "Connection timed out";
case ECONNREFUSED: return "Connection refused";
case ECANCELED: return "Operation canceled";
};
return "Unknown error";
};
// filesize — byte length of an open fd via lseek-to-end-and-back.
export fn filesize(fd: i32) (i64 | oserror) = {
let end: i64 = lseek(fd, 0i64, whence.END);
@@ -14167,15 +14218,23 @@ export fn close(s: stream) (void | error) = {
// Named-void tagged-union variants, so `(T | errors.invalid | ...)`
// composes with every other module's error surface at the tag level.
// Per-domain errors (io.eof, io.closed, io.underread, strconv.overflow,
// ...) live in their own modules; this module ships only the generic
// conditions Hare's errors:: exports.
//
// Subset shipped today; add more from Hare's errors/common.ha as callers
// need them.
// ...) live in their own modules; this module ships the generic
// conditions Hare's errors:: exports plus the [[errno]] bridge that
// wraps a raw [[os.errno]] into a portable [[error]].
// A function was called with an invalid combination of arguments.
package errors;
import os;
// The named conditions, ordered per ref/hare/errors/common.ha.
// The requested resource is not available.
export type busy = !void;
// An attempt was made to create a resource which already exists.
export type exists = !void;
// A function was called with an invalid combination of arguments.
export type invalid = !void;
// The user does not have permission to use this resource.
@@ -14184,12 +14243,112 @@ export type noaccess = !void;
// An entry was requested which does not exist.
export type noentry = !void;
// An attempt was made to create a resource which already exists.
export type exists = !void;
// The requested operation caused a numeric overflow condition.
export type overflow = !void;
// The requested operation is not supported.
export type unsupported = !void;
// The requested operation timed out.
export type timeout = !void;
// The requested operation was cancelled.
export type cancelled = !void;
// A connection attempt was refused.
export type refused = !void;
// An operation was interrupted.
export type interrupted = !void;
// The user should attempt an operation again.
export type again = !void;
// Network unreachable
export type netunreachable = !void;
// Up to 24 bytes of arbitrary, 8-byte-aligned storage for the opaque
// error type's domain-specific data. ref/hare/errors/opaque.ha:41.
export type opaque_data = [3]u64;
// An "opaque" error wraps an implementation-specific underlying error
// behind a function that stringifies it plus a small storage area.
// ref/hare/errors/opaque.ha:32. The `strerror` field keeps Hare's `*fn`
// pointer (filled via `(&fn): *fn(...)`, mirroring io's `*reader` slot,
// lib/io/types.ww:53); only Hare's `const` is dropped (ww has none, cf
// lib/io/types.ww:57).
export type opaque_ = !struct {
strerror: *fn(op: *opaque_data) str,
data: opaque_data,
};
// A tagged union of all error types. ref/hare/errors/common.ha:43.
// Enumerated explicitly rather than spread via Hare's `...error` —
// ww's spread-flatten layout is the deferred #199b / #204 fix.
export type error = !(
busy |
exists |
invalid |
noaccess |
noentry |
overflow |
unsupported |
timeout |
cancelled |
refused |
interrupted |
again |
netunreachable |
opaque_
);
// Wraps an [[os.errno]] to produce an [[error]], which may be
// [[opaque_]]. ref/hare/errors/rt.ha:9. The mapped errnos become named
// conditions; the unmapped tail is carried in an [[opaque_]] whose
// stringifier defers to [[os.strerror]].
export fn errno(e: os.errno) error = {
// A `!void` condition is returned by instance, not by name: a bare
// `return refused;` would emit an undefined symbol reference (the
// type, not a value). cf lib/io/stream.ww:53-56. The instance
// auto-widens to the [[error]] union on return.
switch (e) {
case os.ECONNREFUSED: { let r: refused; return r; };
case os.ECANCELED: { let r: cancelled; return r; };
case os.EOVERFLOW: { let r: overflow; return r; };
case os.EACCES: { let r: noaccess; return r; };
case os.EINVAL: { let r: invalid; return r; };
case os.EEXIST: { let r: exists; return r; };
case os.ENOENT: { let r: noentry; return r; };
case os.ETIMEDOUT: { let r: timeout; return r; };
case os.EBUSY: { let r: busy; return r; };
case os.EINTR: { let r: interrupted; return r; };
case os.EAGAIN: { let r: again; return r; };
case os.ENETUNREACH: { let r: netunreachable; return r; };
};
// An unmapped errno falls through the switch into the opaque_ wrap
// below. ww switches aren't required exhaustive, so Hare's terminal
// `case => void;` (rt.ha:24, present only to satisfy exhaustiveness)
// is dropped rather than written as an explicit no-op default —
// matching the sibling [[os.strerror]] fall-through.
//
// ww has no `static assert`; Hare guards size(errno) <=
// size(opaque_data) there. The invariant holds structurally:
// opaque_data is [3]u64 (24B), errno is one machine int.
let err: opaque_;
err.strerror = (&rt_strerror): *fn(op: *opaque_data) str;
let ptr = (&err.data): *os.errno;
*ptr = e;
return err;
};
// rt_strerror — the [[opaque_]] stringifier for an errno wrapped by
// [[errno]]. ref/hare/errors/rt.ha:31.
fn rt_strerror(op: *opaque_data) str = {
let e = (op): *os.errno;
return os.strerror(*e);
};
// types — error union, mode/whence enums, reader/writer/closer
// fn-type aliases. Project #94 fold-eFinal; the fn-aliases target
// `stream` (= `*vtable`, the single io surface).