ADR 0007 — M8: hybrid core library, raw pointers, unsafe contexts
Inherited. ADR 0007 was accepted before CeeSafe's fork (Decision 0006), when the language had its earlier name. In this record, "the reference" is that language and project and "the reference compiler" its compiler: read them as CeeSafe and
ceesafec. Links to documents CeeSafe did not keep are given as text.
- Status: Accepted (project owner, 2026-10-24, when starting M8: "Hybrid" for the core library). Decisions 2–5 are the agent's defaults under that instruction, following the pattern of ADR 0004; the owner may revise them.
- Scope: how M8 provides the core library, raw pointers and unsafe code (
milestone-8.md). FFI, profiles and MMIO details are specified with their increments (8b–8d).
Context
Master prompt Milestone 8 asks for a core library, freestanding compilation, a C FFI, raw-pointer unsafe operations, custom entry points and target capabilities. Every library type today (Option, Result, Box, Vector, Console, File, …) is built into the compiler and the C runtime (ADR 0003, 0005); the reference has no library source files. The grammar reserves unsafe and extern, and ownership.md U1–U3 sketch the rules, but nothing is specified or implemented.
Decision
Core library: hybrid (owner). The existing built-in types stay built in. The "split" is a per-profile capability table (hosted-only names such as
Console,FileandSystemHeapare rejected in freestanding code) plus the runtime split into a freestanding core and a hosted part. New systems types that unsafe the reference can express (Volatile<T>,MmioPtr<T>,PhysAddr, memory helpers) are written in the reference, in alib/corebundled with the compiler (8c, 8d). Rejected: everything built in (lib/corewould stay empty, and safe wrappers over unsafe code would be exercised only by tests); porting the M3–M6 built-ins to the reference (a large rewrite with regression risk, not needed for C4).Raw pointers are
*Tand*mut T, Copy, with no loans (U3). Making and converting them is safe; using them is unsafe. Conversions useas, so no new operator is needed; the operations are methods (p.read(),p.write(v),p.add(n)) andPtr.*/Slice.fromRawbuilt-ins. There is no*pdereference operator, since the reference has no prefix dereference at all (references and boxes auto-dereference). Rejected: a*pplace operator (new syntax, and*is already multiplication);nullas a pointer literal (nullis the optional's empty value, §22.1); the null pointer is0 as *T.Pointer reads and writes need a Copy pointee in v0. Moving a value out of or into raw memory would need the drop elaborator to track raw places. It can come later, with the allocator interface.
Unsafe contexts are
unsafe { }blocks,unsafe { e }expressions (Ex17:let n = unsafe { write(…) };, since the reference has no block expressions) andunsafe fnbodies. TheSafety:doc comment (U2) is an error when missing, not a warning: the reference has no warnings, and the rule is cheap to satisfy.Generated C has no undefined pointer arithmetic: raw pointers are plain
T *(noconst), and arithmetic and casts go throughuintptr_t(unsafe.md§4).FFI (8b):
- Extern declarations name C types exactly, through the mapping of
type-system.md§28.3 (usizeissize_t,*Tisconst T *, and the pointee-onlyCCharischar), and calls go through generated wrappers. Calling a C function through an incompatible prototype is undefined in C11, and clang and GCC reject redeclared library functions with other types. @exportalone makes a function C-callable. Ex17'spublic extern "C" fnspelling is not used: the attribute already says everything, and anexterndefinition would read like a declaration.@repr(C)structs keep their CeeSafe names in C, so C code usingceesafe_exports.hhas the same struct type. Arrays and other non-FFI-safe types are not allowed in them yet.- The interpreter models a few C library functions and faults on any other C call.
- Function pointers move to 8d: a C callback needs a C-typed wrapper, which exports provide.
- Extern declarations name C types exactly, through the mapping of
Systems capabilities (8d):
lib/coreis CeeSafe source embedded inceesafecand part of every package; unused parts are not emitted, so programs that do not use it compile to the same C as before.Volatile<T>is a cell written throughborrow mut this, not an interior-mutable cell: writing a register needs exclusive access to the register block, whichMmioPtr<T>.getMut()gives.- Only C function pointers exist (
extern "C" fn(…)); their values are extern and exported functions, whose C addresses already exist. Calling one is unsafe. Target.isX86_64()and friends are decided by the C compiler, because one generated C file serves every LP64 target; there is no conditional compilation in the reference yet.
Consequences
ownership.mdU3'sborrowFromRaw(p)is spelledPtr.toBorrow(p)/Ptr.toBorrowMut(p)(borrowis a keyword, soPtr.borrowwould not parse).- The interpreter becomes the dynamic oracle for unsafe code too (null, dead, out-of-bounds, misaligned, uninitialised accesses), but it does not track loans.
- M5's rule 1c (address exposed to unsafe) is met conservatively: a function that turns references into pointers keeps its boxes on the heap.
- Pointee types excluded type parameters in 8a. 8d lifts this (
lib/core'sVolatile<T>needs*T); a scoped type argument for such aTis N0274, as for boxes.