Standard Library, Primitives, and OS
This file is part of the Nocter language specification. The specification entry point is README.md.
OS Error Model
Adopted: Nocter uses a layered OS error model. Target-specific raw errors are converted into common standard-library records, then into the built-in error payload used by T!.
Layering:
std/os common std plus target-gated internals: Platform, OSErrorKind, OSError, syscall, SyscallResult, Errno, errno mapping
std/error Error, ErrorCode, and constructors for the built-in error payload
std/io user-facing I/O APIs
std/process user-facing process APIs
The compiler must not special-case names such as Error, ErrorCode, OSError, Errno, File, args, env, cwd, exit, or abort. These are ordinary standard-library names. The only compiler-level failure type is lowercase error.
Target Raw Errors
std/os owns the raw macOS syscall result and errno wrapper behind #target("arm64-darwin").
#target("arm64-darwin")
pub(nocter) copy struct SyscallResult {
pub value: usize
pub errno: i32
}
#target("arm64-darwin")
pub(nocter) copy struct Errno {
pub code: i32
}
Rules:
SyscallResult.errno == 0means success.SyscallResult.errno != 0means failure.SyscallResult.valueis syscall-specific.Errnois a target-specific raw error wrapper.Errnois not exposed as the common OS error type.- syscall number constants live in target-specific std internals, not in user-facing APIs.
- the exact syscall number list is a target implementation detail.
Common OS Error
Common standard library module:
std/os
Initial std-internal surface:
pub(nocter) enum Platform {
macos
linux
windows
}
pub(nocter) enum OSErrorKind {
interrupted
would_block
not_found
permission_denied
already_exists
invalid_input
broken_pipe
timed_out
unsupported
unknown
}
pub(nocter) copy struct OSError {
pub platform: Platform
pub code: i32
pub kind: OSErrorKind
}
Rules:
OSErroris the common OS error record.OSError.platformrecords the originating platform.OSError.codestores the target raw code.- On macOS and Linux,
codeis an errno value. - On Windows,
codewill be a Windows raw error code chosen by the Windows target design. OSError.kindis the portable classification used by higher-level standard-library modules.OSError,OSErrorKind, andPlatformarepub(nocter)implementation details. User-facing APIs expose the built-inerrorpayload instead.- Target-gated
Errnodeclarations instd/osare not exposed as the common OS error type.
Target-specific std internals convert raw target errors into OSError.
SyscallResult -> Errno -> OSError
Common Error Payload
The built-in failure payload is error. The standard library exposes ordinary names and constructors for it.
Initial public surface:
pub type ErrorCode = &str
pub type Error = error
pub func Error.new(code: ErrorCode, message: &str): Error
Initial standard-library implementation boundary:
pub(nocter) primitive new_error(code: &str, message: &str): error
pub func Error.new(code: ErrorCode, message: &str): Error {
return new_error(code, message)
}
Rules:
erroris the compiler built-in payload type.ErrorandErrorCodeare ordinary standard-library names.- The compiler does not know the name
ErrorCode.ErrorCodeis an open string classification alias over&str, andError.newconverts that string slice into the built-inerrorpayload's primitive code representation. new_erroris apub(nocter)standard-library implementation primitive instd/error; user project modules must callError.newinstead of importing it.- Standard-library codes use dotted names such as
"std.io.not_found","std.mem.out_of_memory","std.string.capacity_overflow","std.fmt.unsupported", and"std.process.invalid_encoding". User and package code may use their own prefixes, such as"app.config.missing_key". - Standard-library modules should fail with
error, not domain-specific fallible error type parameters. - Domain-specific detail is represented by
ErrorCode,message, and, where needed later, additional standard-library helper APIs.
String and Formatting API Boundary
Adopted: the initial owning-string and formatting boundary is split between std/string and std/fmt.
Initial std/string public surface:
pub struct String {
...
}
pub func String.empty(): String
pub func String.with_capacity(allocator: &+Allocator, capacity: usize): String!
pub func String.from_str(allocator: &+Allocator, value: &str): String!
pub func String.copy(allocator: &+Allocator, value: &str): String!
pub func empty(): String
pub func with_capacity(allocator: &+Allocator, capacity: usize): String!
pub func from_str(allocator: &+Allocator, value: &str): String!
pub func view(text: &String): &str
pub func len(text: &String): usize
pub func capacity(text: &String): usize
pub func is_empty(text: &String): bool
pub func reserve(text: &+String, additional: usize): void!
pub func clear(text: &+String): void
pub func bytes(value: &str): &[u8]
pub func push_str(text: &+String, value: &str): void!
pub func capacity_overflow(): error
impl String {
pub method &self.view(): &str
pub method &self.len(): usize
pub method &self.capacity(): usize
pub method &self.is_empty(): bool
pub method &self.bytes(): &[u8]
pub method &+self.reserve(additional: usize): void!
pub method &+self.clear(): void
pub method &+self.push_str(value: &str): void!
}
Initial std/fmt public surface:
pub func append_str(out: &+String, value: &str): void!
pub func append_string(out: &+String, value: &String): void!
pub func append_i32(out: &+String, value: i32): void!
pub func append_usize(out: &+String, value: usize): void!
pub func append_bool(out: &+String, value: bool): void!
Rules:
- These functions are ordinary standard-library APIs, not compiler built-ins.
- Fallible functions fail with the built-in
errorpayload.std/stringandstd/fmtmust not introduceStringError,FormatError, or another domain-specific fallible payload. std/string.with_capacityandstd/string.from_strtake an explicit&+Allocator.- The formatting append functions operate on an already-created
String; they do not choose an allocator. - Future lowering for string interpolation must be expressed in terms of explicit
Stringconstruction andstd/fmt.append_*calls. It must not silently choose a process-global allocator. std/fmtis not part of the initial prelude. User code imports it explicitly unless future prelude policy changes.- Module-local helpers such as formatting
unsupportederrors are not public API unless explicitly listed in this chapter.
Memory and Allocator API
Adopted: v0 exposes a small explicit allocator surface in std/mem.
Initial public surface:
pub copy struct Layout {
pub size: usize
pub align: usize
}
pub struct RawBuffer {
pub(nocter) ptr: *u8
pub(nocter) len: usize
pub(nocter) align: usize
}
pub struct Allocator {
...
}
pub func page_allocator(): Allocator
pub func alloc(allocator: &+Allocator, size: usize, align: usize): RawBuffer!
pub func free(allocator: &+Allocator, buffer: RawBuffer): void
pub func bytes(buffer: &RawBuffer): &[u8]
pub func bytes_mut(buffer: &+RawBuffer): &+[u8]
pub func prefix(buffer: &RawBuffer, len: usize): &[u8]!
pub func prefix_mut(buffer: &+RawBuffer, len: usize): &+[u8]!
pub func out_of_memory(): error
pub func invalid_argument(): error
impl Allocator {
pub method &+self.alloc(size: usize, align: usize): RawBuffer!
pub method &+self.free(buffer: RawBuffer): void
}
impl RawBuffer {
pub method &self.bytes(): &[u8]
pub method &+self.bytes_mut(): &+[u8]
pub method &self.prefix(len: usize): &[u8]!
pub method &+self.prefix_mut(len: usize): &+[u8]!
}
Rules:
- Allocation failure is recoverable and returns the built-in
errorpayload. - The initial allocator is page-backed. General allocator families are deferred.
RawBufferowns raw byte storage and is not a typed collection.RawBuffer's representation fields arepub(nocter): trusted std modules may inspect and pass the raw storage boundary onward, but user project modules must obtain views through the publicbytes,bytes_mut,prefix,prefix_mut, and method APIs.- User-facing collection APIs should be built on ordinary std types such as
Vec<T>, not by exposing unchecked raw buffer mutation. - Target-dependent allocation helpers are std-internal and target-gated.
Vector API
Adopted: std/vec provides the initial owned variable-length collection type. Vec<T> is an ordinary standard-library type, not a compiler built-in.
Initial public surface:
pub struct Vec<T> {
...
}
pub func Vec.empty<T>(): Vec<T>
pub func Vec.with_capacity<T>(allocator: &+Allocator, requested_capacity: usize): Vec<T>!
pub func Vec.from_slice<T>(allocator: &+Allocator, values: &[T]): Vec<T>!
pub func empty<T>(): Vec<T>
pub func with_capacity<T>(allocator: &+Allocator, requested_capacity: usize): Vec<T>!
pub func from_slice<T>(allocator: &+Allocator, values: &[T]): Vec<T>!
pub func len<T>(values: &Vec<T>): usize
pub func capacity<T>(values: &Vec<T>): usize
pub func is_empty<T>(values: &Vec<T>): bool
pub func view<T>(values: &Vec<T>): &[T]
pub func view_mut<T>(values: &+Vec<T>): &+[T]
pub func reserve<T>(values: &+Vec<T>, additional: usize): void!
pub func clear<T>(values: &+Vec<T>): void
pub func push<T>(values: &+Vec<T>, value: T): void!
pub func capacity_overflow(): error
impl<T> Vec<T> {
pub method &self.len(): usize
pub method &self.capacity(): usize
pub method &self.is_empty(): bool
pub method &self.view(): &[T]
pub method &+self.view_mut(): &+[T]
pub method &+self.reserve(additional: usize): void!
pub method &+self.clear(): void
pub method &+self.push(value: T): void!
drop &+self {
...
}
}
Rules:
Vec<T>is not exported bystd/prelude; user code importsstd/vec.Vecexplicitly.- The v0 runtime surface is narrow. Scalar,
&str, and explicitly promoted copy-aggregate element storage paths are supported by the current implementation. - Non-copy aggregate element storage, per-element drop glue, insertion/removal APIs, and iterator helpers are deferred.
checkmay acceptVec<T>APIs outside the runtime-supported element subset when they typecheck.buildandrunmust reject unsupportedVec<T>element storage paths during v0 buildability validation.clearresets length. Element drop behavior is deferred until per-element drop glue is designed.viewandview_mutexpose slices over initialized elements only.
I/O API
Adopted: std/io provides the initial user-facing file and text output API. The compiler must not special-case File, stdout, stderr, or print.
Initial public surface:
pub struct File {
...
}
pub func open(path: &str): File!
pub func File.open(path: &str): File!
pub func read(file: &+File, buffer: &+[u8]): usize!
pub func write(file: &+File, bytes: &[u8]): void!
pub func write_text(file: &+File, text: &str): void!
impl File {
pub method &+self.read(buffer: &+[u8]): usize!
pub method &+self.write(bytes: &[u8]): void!
pub method &+self.write_text(text: &str): void!
drop &+self {
...
}
}
pub func stdout(): File
pub func stderr(): File
pub func print(text: &str): void!
Rules:
Fileis a move-only standard-library type with private representation.open,read,write, andwrite_textare ordinary free-function wrappers around the associated function or methods.File.open(path)opens an existing file for reading in v0.- File creation, append, truncate, read-write modes, and open options are deferred.
File.open(path)maps path-related OS errors into"std.io.not_found","std.io.permission_denied", or"std.io.invalid_path"when the target can classify them.read(buffer)reads into a writable byte view and returns the number of bytes read.read(buffer)returning0means end of file for regular files.read(buffer)may return fewer bytes thanbuffer.len()without treating that as an error.write(bytes)writes all bytes in the view or fails.- Target implementations handle partial OS writes inside
write(bytes). write_text(text)writes the UTF-8 bytes of&strwithout encoding conversion.print(text)writestexttostdout()and does not append a newline.stdout()andstderr()returnFilevalues representing the process standard streams.Fileinternally distinguishes owned handles from borrowed process standard streams.- Dropping a
Filereturned byFile.open(path)closes the owned handle. - Dropping a
Filereturned bystdout()orstderr()must not close the process standard stream. - The
Filedrop member cannot fail. Close errors are ignored in v0 unless a future explicit close API is adopted. - Unexpected OS errors are converted to
"std.os.unexpected_os_error"with a message that preserves useful target context.
Physical placement:
std/iois the user-facing module path and ownsFileplus the public I/O API.- Target-dependent raw file-descriptor helpers live in
std/ioaspub(nocter)implementation details. - Target-dependent helper functions and primitive declarations use
#target("..."). - Raw helper names such as
write_text_raware not user-facing API. - Common I/O API names should remain stable across targets.
Conversion flow:
std/os.syscall3
-> SyscallResult
-> Errno
-> std/os.OSError
-> error
Not adopted in v0:
- buffered I/O
- async I/O
- seek
- directory traversal
- path object
- encoding conversion
- automatic newline printing
- compiler magic for
print printlnas a compiler feature
Process Context
Adopted: command-line arguments and environment access are standard-library APIs in std/process, not entry function parameters.
Initial public surface:
pub func args(): Vec<&str>!
pub func env(name: &str): &str?!
pub func cwd(allocator: &+Allocator): String!
pub func exit(code: i32): never
pub func abort(): never
V0 distribution status:
exit(code)andabort()are runtime-shipped.cwd(allocator)is runtime-shipped onarm64-darwinthrough the standard-library syscall boundary and returns caller-ownedStringstorage.args()is runtime-shipped onarm64-darwinand returns an ownedVec<&str>whose string views point into process context storage.env(name)reserves the future&str?!API shape, but useful runtime behavior is check-only until nested fallible/optional return lowering and process context runtime are promoted. It must not silently succeed withnonebefore environment access is implemented.- During the check-only period,
checkaccepts calls toenv(name)for source-level validation, whilebuildandrunreject them during v0 buildability validation.
Rules:
- Entry function type parameters and value parameters, such as
func main<T>(): i32!andfunc main(args: Vec<&str>): i32!, are not part of v0. - The compiler must not special-case a function named
args,env,cwd,exit, orabort. - The generated low-level entry code may receive platform process entry information such as
argc,argv, andenvp. - That platform information is connected to a
std/processprocess context inside the active target implementation. - User code reads command-line arguments with
std/process.args(). - User code reads environment values with
std/process.env(name). args()returns an ownedVec<&str>of borrowed string slices on success.- The first argument follows the host platform convention and represents the executable path or invocation name when the platform provides one.
- The
env(name)runtime behavior rules below are the contract for the future promoted implementation. During the v0 check-only period,buildandrunrejectenv(name)before lowering. env(name)has type&str?!.env(name)succeeds withnonewhen the variable is absent.env(name)succeeds with a present&strwhen the variable is present and valid UTF-8.cwd(allocator)returns an owned current-working-directory string or fails witherror.cwd(allocator)fails with"std.process.cwd_failed"if the target cannot retrieve the current working directory.- Process context string storage is valid for the whole program.
- Argument and environment views returned by
std/processare not owned by the caller and must be treated as borrowed view data. - The caller must not drop process-context storage.
- The
cwd(allocator)result is owned by the caller and must be dropped normally. - APIs that need owned strings must explicitly copy into an allocator-owned
String. - Target implementations must validate argument and environment strings before exposing them as
&str. - The v0
arm64-darwincwd(allocator)implementation treats the path returned byF_GETPATHas platform UTF-8 text; byte-to-UTF-8 validation for targets that expose arbitrary path bytes remains future std work. args()fails with"std.process.invalid_encoding"if any returned argument cannot be represented as UTF-8.env(name)fails with"std.process.invalid_encoding"if the matching environment value exists but cannot be represented as UTF-8.- Future target implementations of
cwd(allocator)must fail with"std.process.invalid_encoding"if their current-working-directory source can contain non-UTF-8 bytes and validation fails.
Example:
use std/process.args
func main(): i32! {
let argv = args()?
if argv.len() < 2 {
return 1
}
return 0
}
Physical placement:
std/processis the user-facing module path.- Process context implementation may call target-gated std internals when it depends on the target process ABI.
- Common process API names should remain stable across targets.
Process Termination
std/process's terminating functions are normal standard-library APIs.
pub func exit(code: i32): never
pub func abort(): never
Rules:
exitis not a compiler primitive.abortis not a compiler primitive.exitdoes not return an error.abortdoes not return an error.exitterminates the process with an explicit status code.abortterminates the process immediately as abnormal termination.- Neither
exitnorabortruns caller-scope Nocter cleanup. Code that needs cleanup must do it before calling them. - The target implementation uses the active target's syscall or process termination boundary.
- If the platform termination operation unexpectedly returns, the implementation calls
trap(). - The module path is
std/process; target-dependent helper or primitive calls inside it are reached through#target-gated std internals.
Pointer API
Adopted: v0 exposes only a narrow raw-pointer surface.
Initial public surface:
pub primitive addr<T>(pointer: *T): usize
pub primitive from_ref<T>(value: &T): *T
pub primitive from_ref_mut<T>(value: &+T): *T
Rules:
- These APIs are ordinary public standard-library primitive declarations under
std/ptr. - Pointer dereference and general user memory mutation through raw pointers are deferred.
- Raw construction helpers such as
from_addr, raw-parts string/slice construction, raw stores, and raw copies arepub(nocter)std-internal APIs.
Not Adopted
std/posix is not part of the initial design. macOS and Linux can share POSIX-like ideas, but Windows does not fit that layer cleanly. Shared concepts should use stable module paths such as std/os, std/io, and std/process. Their target-dependent boundaries should remain in std-internal modules with #target on type, helper, and primitive declarations.
Standard Library and Low-Level Code
The compiler must not special-case names such as print, args, env, cwd, exit, abort, or File.
Standard library functions provide these features.
use std/io.stdout
func main(): i32! {
var out = stdout()
out.write_text("Hello\n")?
return 0
}
The standard library may use typed primitive declarations to connect Nocter code to compiler-provided low-level implementations. Arbitrary inline ARM64 asm is not part of the initial language.
#target("arm64-darwin")
pub(nocter) copy struct SyscallResult {
pub value: usize
pub errno: i32
}
#target("arm64-darwin")
pub(nocter) primitive syscall3(
number: usize,
a0: usize,
a1: usize,
a2: usize,
): SyscallResult
#target("arm64-darwin")
pub(nocter) primitive trap(): never
#target("arm64-darwin")
pub(nocter) primitive unreachable(): never
trap() terminates the current process or thread with a target-defined illegal-instruction, breakpoint, or equivalent non-recoverable stop. It has type never, does not return an error, and does not unwind.
unreachable() is a standard-library marker for paths that should be impossible. Reaching it traps.
Initial policy:
primitivedeclarations are allowed only inside the active Nocter homestd/directory in the initial design.- A primitive declaration has no function body.
- A primitive declaration uses normal Nocter parameter and return types.
- Target-independent declarations do not use
#target. - Target-dependent type declarations, helper functions, and primitive declarations must be preceded by
#target("target-name"). #targetapplies only to top-level function, primitive, struct, enum, interface, and type-alias declarations in v0.targetis not a reserved keyword; it is treated as the directive name only after#.- Primitive calls follow normal visibility rules. A
pubprimitive may be called by any module that can import it. Apub(nocter)primitive may be called only inside the active Nocter home. - Primitive calls follow the Nocter ABI after visibility and trusted-boundary restrictions pass.
- The compiler validates each primitive declaration against the target-independent core primitive set or the closed primitive set for the active target.
- The compiler validates primitives by module path, name, and exact signature.
- An ordinary
funcwith the same name as a primitive has no primitive behavior. - Standard-library wrappers should expose user-facing APIs such as
exit,write,write_text,alloc, andfree. print,exit,abort, file operations, allocators,String, andBufferare not primitives in v0.- General user code cannot declare primitives in v0. The long-term default direction is that user project modules do not declare primitives.
- General user code can call only the small public primitive APIs intentionally exposed by the standard library, such as safe raw-pointer address conversion. Target syscall primitives are
pub(nocter). - Arbitrary inline
asmis not part of the initial language.
Trusted Boundary
Adopted: v0 has no unsafe keyword, no unsafe block, and no unsafe func.
Instead, the trusted boundary is the active Nocter home:
~/.nocter/std/
Rules:
- User project modules are always safe Nocter code in v0.
unsafeis not a reserved keyword in v0.trustedis not a reserved keyword in v0.- Modules inside the active Nocter home may contain primitive declarations when those declarations match the closed primitive set.
- Modules inside the active Nocter home may call
pub(nocter)primitive declarations. - Modules inside the active Nocter home may call restricted low-level APIs such as
std/ptr.from_addr. - User project modules must not declare primitives.
- User project modules must not call
pub(nocter)primitive declarations. - User project modules must not call restricted low-level APIs such as
std/ptr.from_addr. - Trusted modules still go through normal parsing, type checking, ownership checking, borrowing rules, and drop checking.
- Trusted modules should expose ordinary safe APIs to user code, using types such as
File,String,Vec<T>,Allocator,error,&str,&[T], and&+[T]. Std-internal records such asOSErrormust stay behindpub(nocter)unless the public API contract is explicitly changed. - If trusted standard-library code violates an invariant required by its public safe API, that is a standard-library or compiler bug. It is not an opt-in source-level permission granted to user code.
Initial primitive declaration syntax:
pub primitive name(params): ReturnType
pub(nocter) primitive name(params): ReturnType
#target("arm64-darwin")
pub(nocter) primitive target_dependent_name(params): ReturnType
Initial primitive declaration modules:
~/.nocter/std/error.nct
~/.nocter/std/ptr.nct
~/.nocter/std/string.nct
~/.nocter/std/io.nct
~/.nocter/std/os.nct
~/.nocter/std/process.nct
std/error.nct contains the target-independent built-in error payload construction primitive used by Error.new.
std/ptr.nct contains target-independent core pointer primitive declarations. These are required for raw pointer address conversion, borrow-to-pointer conversion, and std-internal construction and copying of string, byte, and typed views over raw storage.
std/string.nct contains the target-independent bytes_from_str primitive used to expose &str storage as a byte slice inside the standard library.
std/io.nct contains the initial arm64-darwin raw file descriptor primitives used by File.open, File.read, File.write, File.write_text, and print. These declarations are pub(nocter) and are not part of the user-facing I/O API.
std/os.nct contains common OS declarations plus target-specific declarations for arm64-darwin behind #target("arm64-darwin"). Future OS targets should add new #target("...") declarations inside stable std-internal modules instead of changing import resolution.
std/process.nct contains the initial arm64-darwin process termination primitive used by std/process.exit. It is pub(nocter) and is not part of the user-facing process API.
Initial core error primitive set:
pub(nocter) primitive new_error(code: &str, message: &str): error
Initial core pointer primitive set:
pub primitive addr<T>(pointer: *T): usize
pub primitive from_ref<T>(value: &T): *T
pub primitive from_ref_mut<T>(value: &+T): *T
pub(nocter) primitive from_addr<T>(address: usize): *T
pub(nocter) primitive pointee_size<T>(pointer: *T): usize
pub(nocter) primitive copy_str_to_ptr(destination: *u8, offset: usize, text: &str): void
pub(nocter) primitive copy_ptr_to_ptr(destination: *u8, source: *u8, byte_count: usize): void
pub(nocter) primitive store_u8_to_ptr(destination: *u8, offset: usize, value: u8): void
pub(nocter) primitive store_value_to_ptr<T>(destination: *T, offset: usize, value: T): void
pub(nocter) primitive str_from_raw_parts(pointer: *u8, len: usize): &str
pub(nocter) primitive slice_from_raw_parts(pointer: *u8, len: usize): &[u8]
pub(nocter) primitive slice_from_raw_parts_mut(pointer: *u8, len: usize): &+[u8]
pub(nocter) primitive slice_from_raw_parts_value<T>(pointer: *T, len: usize): &[T]
pub(nocter) primitive slice_from_raw_parts_value_mut<T>(pointer: *T, len: usize): &+[T]
from_addr, pointee sizing, raw-storage copy/store helpers, and raw-storage view construction helpers are pub(nocter) and therefore restricted to trusted modules inside the active Nocter home. User project modules must not call them. Calls to from_addr with an address expression statically known to be zero are rejected because v0 raw pointers are non-null.
Initial core string primitive set:
pub(nocter) primitive bytes_from_str(value: &str): &[u8]
bytes_from_str is pub(nocter) and is exposed to user code only through ordinary standard-library wrappers such as std/string.bytes and text.bytes().
Initial arm64-darwin target primitive set v0:
#target("arm64-darwin")
pub(nocter) copy struct SyscallResult {
pub value: usize
pub errno: i32
}
#target("arm64-darwin")
pub(nocter) primitive syscall0(number: usize): SyscallResult
#target("arm64-darwin")
pub(nocter) primitive syscall1(number: usize, a0: usize): SyscallResult
#target("arm64-darwin")
pub(nocter) primitive syscall2(number: usize, a0: usize, a1: usize): SyscallResult
#target("arm64-darwin")
pub(nocter) primitive syscall3(number: usize, a0: usize, a1: usize, a2: usize): SyscallResult
#target("arm64-darwin")
pub(nocter) primitive syscall4(number: usize, a0: usize, a1: usize, a2: usize, a3: usize): SyscallResult
#target("arm64-darwin")
pub(nocter) primitive syscall5(number: usize, a0: usize, a1: usize, a2: usize, a3: usize, a4: usize): SyscallResult
#target("arm64-darwin")
pub(nocter) primitive syscall6(number: usize, a0: usize, a1: usize, a2: usize, a3: usize, a4: usize, a5: usize): SyscallResult
#target("arm64-darwin")
pub(nocter) primitive trap(): never
#target("arm64-darwin")
pub(nocter) primitive unreachable(): never
#target("arm64-darwin")
pub(nocter) primitive open_read_raw(path: *u8): i32!
#target("arm64-darwin")
pub(nocter) primitive write_text_raw(fd: i32, text: &str): void!
#target("arm64-darwin")
pub(nocter) primitive write_bytes_raw(fd: i32, bytes: &[u8]): void!
#target("arm64-darwin")
pub(nocter) primitive read_bytes_raw(fd: i32, buffer: &+[u8]): usize!
#target("arm64-darwin")
pub(nocter) primitive close_fd_raw(fd: i32): void
#target("arm64-darwin")
pub(nocter) primitive exit_raw(code: i32): never
SyscallResult.errno == 0 means success. SyscallResult.errno != 0 means the syscall failed with an OS error value. The meaning of value is syscall-specific.
The compiler recognizes these declarations only at their registered module path and target:
std/os
std/io
std/process
An ordinary function named syscall3 elsewhere is not primitive.
syscall0 through syscall6 are a bootstrap boundary for the initial macOS standard library. The std/io.*_raw and std/process.exit_raw primitives are narrow bootstrap boundaries for shipped v0 I/O and process termination before all wrappers can be expressed through the ordinary SyscallResult path. Longer term, target-gated std internals may replace direct syscall exposure and these raw primitives with narrower typed wrappers. Those wrappers are standard-library APIs, not compiler primitives.
Typed Primitive Wrappers
Adopted: typed wrappers over low-level target operations are standard-library APIs, not compiler primitives.
The closed compiler primitive set stays small. For the initial arm64-darwin target, the target primitive boundary is std/os.syscall0 through std/os.syscall6, std/os.trap, std/os.unreachable, the raw std/io file descriptor primitives, and std/process.exit_raw; separately, std/ptr, std/error, and std/string own the target-independent core primitive set.
Std-internal modules may define narrower typed wrappers around those primitives:
pub(nocter) func write_fd(fd: FileDescriptor, bytes: &[u8]): void! {
...
}
User-facing modules then expose safe ordinary APIs:
pub method &+self.write(bytes: &[u8]): void! {
...
}
Rules:
- Adding a file API, process API, allocator API, string API, buffer API, or OS wrapper must not require adding a compiler primitive.
- The existing
std/io.*_rawandstd/process.exit_rawprimitives are v0 bootstrap exceptions for shipped I/O and process termination, not a precedent for adding one primitive per standard-library API. - Target-specific syscall numbers, raw OS handles, errno-like values, and calling conventions belong in target-gated std internals, not in the compiler's general language semantics.
- The compiler validates the existing primitive declarations by module path, name, and exact signature.
- An ordinary wrapper name such as
open_file_raw,write_fd_raw,mmap_raw, orexit_processhas no compiler-defined behavior. - A future compiler primitive may be added only by an explicit language and backend design update, not as the normal way to grow the standard library.
- User project modules remain outside the primitive declaration boundary. If Nocter later adds an explicit trusted or unsafe extension, it should not make arbitrary user-defined primitive declarations the default extension mechanism.
Keyword Ownership
Keyword and lexical rules are specified only in Lexical Grammar. This chapter does not maintain a separate keyword list.
Open Design Questions
No open design questions are currently listed in this chapter.