# `nupp.mem.span` `nupp.mem.span` gives a C array a rooted, one-based, bounds-checked view. A shared `Span` reads contiguous elements; a writable span adds exclusive access and an [affine lifetime](../../../../learn/runtime/ownership/index.html), so the pointer cannot outlive or overlap its owner. ```nupp local span = nupp.mem.span const text = "hello" const bytes = span.fromString(text) assert(#bytes == 5) ``` Use spans at checked boundaries. Direct indexing through a pointer or variable-length C array remains an [`unsafe` operation](../../../../learn/runtime/ownership/index.html#unsafe-representation-boundaries). `Span` is a sealed contract over a private implementation that keeps an element count beside a rooted pointer, so public code can neither forge one nor reach the raw pointer it holds. Indexing and slicing check that count first. ## Creating a shared span `fromCarray(source, count)` borrows a C array and records its logical element count. `fromString(source)` creates the byte-specialized `ByteSpan`. Both keep their source rooted for the lifetime of the view. ```nupp local span = nupp.mem.span local struct Point x: float y: float end const storage = carray(Point, 4) const points = span.fromCarray(storage, 4) assert(#points == 4) assert(points[1].x == 0) ``` Indexes start at one. `view[index]` raises when the index is outside `1` through `#view`. `slice(first, last)` includes both endpoints and borrows the parent; omitting `last` extends through the end. An empty slice uses `first, first - 1`. The operator surface replaces the former element methods and public count field. Each former member is a migration error, not a deprecated alternative: | Removed | Replacement | | --- | --- | | `view.count` | `#view` | | `view:get(index)` | `view[index]` | | `view:set(index, value)` | `view[index] = value` | | `view:getMut(index).field` | `view[index].field` | | `span.range(first, last, ...)` | `indexed.range(first, last, ...)` | ::: deepdive Nonescaping spans allocate nothing at -O1 When the complete use of an exact standard Span constructor is static and nonescaping, Nupp keeps its anchor, pointer, offset, count, and capability as compiler-owned values instead of allocating a wrapper. Constructor validation and bounds checks still run, and the source remains strongly rooted through the last access, so the checked meaning does not change with the optimization level. An escape or an opaque call materializes the same checked span. ::: `fromFixedCarray(source, count)` returns `FixedSpan` when the array and literal count carry the same static `N`. It satisfies `Span`, while preserving the exact count for checks and generated code. ## Writable spans `writeCarray` returns the affine alias `Writable`. Its underlying `WriteSpan` contract is what a function accepting exclusive access names: ```nupp local span = nupp.mem.span local indexed = nupp.mem.indexed local struct Point x: float y: float end local function clear(exclusive points: span.WriteSpan): nil const view = points const indexes = indexed.range(1, #view, view) for index = indexes.first, indexes.last do view[index] = new Point(0, 0) end end local storage = carray(Point, 4) const points = span.writeCarray(storage, 4) clear(points) drop points ``` Whole-element and direct field assignments write through the checked indexed place. `shared()` downgrades the writer to a shared view for the returned view's lifetime. A writable span is affine because it represents exclusive access rather than owned memory. Dropping it, explicitly or at scope exit, ends that access; it does not free or copy the source array. `writeFixedCarray` and `FixedWritable` preserve a static count in the same way as their shared counterparts. Asking one array for a shared span while a writer over it is live reports `NUPP2607`, and so does the reverse. ## Slices and partitions `WriteSpan.slice(first, last)` creates one affine child writer. The parent remains blocked until the child is dropped, preventing a write through the parent from overlapping the slice. `splitAt(mid)` partitions a writer into audited, non-overlapping left and right regions. `mid` is the number of elements in the left region, so zero gives an empty left side and `count` gives an empty right side. Both children retain the parent as their root. Use a slice for one subrange and `splitAt` when two disjoint writable regions must be live together. Unknown indexes and bounds otherwise conservatively overlap. ## Shared range for several spans `indexed.range(first, last, ...)` checks one inclusive range against every trusted Span or [SoA view](../../../../learn/runtime/data/structure-of-arrays/index.html) and answers a record whose `first` and `last` are ordinary integers: ```nupp local span = nupp.mem.span local indexed = nupp.mem.indexed local struct Value n: integer end local function dot( borrows left: span.Span, borrows right: span.Span ): integer const first = left const second = right const indexes = indexed.range(1, #first, first, second) local total: integer = 0 for index = indexes.first, indexes.last do total = total + first[index].n * second[index].n end return total end const storage = carray(Value, 4) const values = span.fromCarray(storage, 4) print(dot(values, values)) ``` At least one span is required. Empty ranges use the same `first, first - 1` convention as slices. The typed borrowed vararg passes each original span directly and allocates no container or interface wrapper. When the bounds and spans are const-bound in the same function, the successful range check proves matching indexed reads and writes non-raising inside the dominated numeric loop. This proof is part of checking at every optimization level: it is what permits those calls inside `noraise` code. See [`OPT-6`](../../../../learn/performance/index.html#opt-6-indexed-views) for how that proof is spent at `-O1`, as direct FFI element access and virtual slices that allocate no wrapper. ## Passing a span to C `ref()` returns the rooted pointer and logical count for a handwritten native wrapper. Shared spans return a `const T[?]`; writable spans require exclusive access and return `T[?]`. The returned pointer borrows the span, so it cannot escape independently. A declarative C binding can use [`countedBy(count)`](../../../../learn/runtime/c-interop/index.html#counted-pointer-adapters) instead. Its checked call surface accepts spans, verifies shared counts, and projects the physical pointer and count arguments automatically. ::: seealso - [ownership.md](../../../../learn/runtime/ownership/index.html) for what makes a writable span affine and when its access ends - [exact-affine-scopes.md](../../../../learn/runtime/ownership/exact-scopes/index.html) for the scopes that end a writable span, including the `with` block - [c-interop.md](../../../../learn/runtime/c-interop/index.html) for the boundary `ref` and `countedBy` sit on ::: ## Types ### `ByteSpan` _type_ ```nupp type span.ByteSpan = span.Span ``` The byte-specialized shared view, which is what `fromString` answers. ### `ByteWriteSpan` _type_ ```nupp type span.ByteWriteSpan = span.WriteSpan ``` The byte-specialized write range. ### `FixedSpan` _interface_ ```nupp sealed interface span.FixedSpan is span.Span metamethod __len: function(self: FixedSpan): N end ``` A shared span whose exact element count is part of its static type. It refines the dynamic contract, so APIs accepting `Span` also accept a fixed span. #### Type parameters | Name | Description | | --- | --- | | `T` | | | `N` | | #### Methods ##### `__len` ```nupp __len: function(self: FixedSpan): N ``` ###### Arguments | Name | Type | Description | | --- | --- | --- | | `self` | `FixedSpan\` | | ###### Returns | Type | Description | | --- | --- | | `N` | | ### `FixedWritable` _type_ ```nupp type span.FixedWritable = affine(span.FixedWriteSpan, span.destroyWriteSpan) ``` An affine fixed-width writable span. #### Type parameters | Name | Description | | --- | --- | | `T` | | | `N` | | ### `FixedWriteSpan` _interface_ ```nupp sealed interface span.FixedWriteSpan is span.WriteSpan metamethod __len: function(self: FixedWriteSpan): N shared: function(borrows self: FixedWriteSpan): span.FixedSpan borrows (self) end ``` A writable span whose exact element count is part of its static type. #### Type parameters | Name | Description | | --- | --- | | `T` | | | `N` | | #### Methods ##### `__len` ```nupp __len: function(self: FixedWriteSpan): N ``` ###### Arguments | Name | Type | Description | | --- | --- | --- | | `self` | `FixedWriteSpan\` | | ###### Returns | Type | Description | | --- | --- | | `N` | | ##### `shared` ```nupp shared: function(borrows self: FixedWriteSpan): span.FixedSpan borrows (self) ``` ###### Arguments | Name | Type | Description | | --- | --- | --- | | `borrows self` | `FixedWriteSpan\` | | ###### Returns | Type | Description | | --- | --- | | `span.FixedSpan\ borrows (self)` | | ### `Span` _interface_ ```nupp sealed interface span.Span metamethod __len: function(self: Span): integer metamethod __index: function(self: Span, index: integer): T slice: function(self: Span, first: integer, last: integer?): Span borrows (self) ref: function(self: Span): (const T[?] borrows (self), integer) end ``` A checked, shared view over contiguous elements. Only this module can declare an implementation, so the contract is proof that `ref()` and `count` agree. #### Type parameters | Name | Description | | --- | --- | | `T` | | #### Methods ##### `__len` ```nupp __len: function(self: Span): integer ``` ###### Arguments | Name | Type | Description | | --- | --- | --- | | `self` | `Span\` | | ###### Returns | Type | Description | | --- | --- | | `integer` | | ##### `__index` ```nupp __index: function(self: Span, index: integer): T ``` ###### Arguments | Name | Type | Description | | --- | --- | --- | | `self` | `Span\` | | | `index` | `integer` | | ###### Returns | Type | Description | | --- | --- | | `T` | | ##### `slice` ```nupp slice: function(self: Span, first: integer, last: integer?): Span borrows (self) ``` Answers a subspan, inclusive at both ends and borrowed from this one. Indexes are one-based. Omitting `last` runs through to the end, and `first, first - 1` is how an empty subspan is written. ```nupp const values = span.fromCarray(storage, 4) const middle = values:slice(2, 3) assert(#middle == 2) ``` ###### Arguments | Name | Type | Description | | --- | --- | --- | | `self` | `Span\` | | | `first` | `integer` | | | `last` | `integer?` | | ###### Returns | Type | Description | | --- | --- | | `Span\ borrows (self)` | | ##### `ref` ```nupp ref: function(self: Span): (const T[?] borrows (self), integer) ``` Answers the checked range as a const pointer and count, for a native call. The pointer borrows this span, so it cannot outlive the view that proved its bounds. This is the one supported way out to C. ###### Arguments | Name | Type | Description | | --- | --- | --- | | `self` | `Span\` | | ###### Returns | Type | Description | | --- | --- | | `const T\[?\] borrows (self)` | | | `integer` | | ### `Writable` _type_ ```nupp type span.Writable = affine(span.WriteSpan, span.destroyWriteSpan) ``` An affine dynamic writable span. #### Type parameters | Name | Description | | --- | --- | | `T` | | ### `WriteSpan` _interface_ ```nupp sealed interface span.WriteSpan is span.WriteToken metamethod __len: function(self: WriteSpan): integer metamethod __index: function(borrows self: WriteSpan, index: integer): T metamethod __newindex: function(exclusive self: WriteSpan, index: integer, value: T): nil drop: nosuspend function(takes self: WriteSpan): nil ref: function(exclusive self: WriteSpan): (T[?] borrows (self), integer) shared: function(borrows self: WriteSpan): span.Span borrows (self) slice: function( exclusive self: WriteSpan, first: integer, last: integer? ): affine(span.WriteSpan, span.destroyWriteSpan) borrows (self) @partition(left, right) splitAt: function(exclusive self: WriteSpan, mid: integer): span.WriteSplit borrows (self) end ``` An affine checked write range. Its live token keeps the source under an incompatible-borrow barrier until `drop` or scope exit consumes it. Only this module can declare an implementation. #### Type parameters | Name | Description | | --- | --- | | `T` | | #### Methods ##### `__len` ```nupp __len: function(self: WriteSpan): integer ``` ###### Arguments | Name | Type | Description | | --- | --- | --- | | `self` | `WriteSpan\` | | ###### Returns | Type | Description | | --- | --- | | `integer` | | ##### `__index` ```nupp __index: function(borrows self: WriteSpan, index: integer): T ``` ###### Arguments | Name | Type | Description | | --- | --- | --- | | `borrows self` | `WriteSpan\` | | | `index` | `integer` | | ###### Returns | Type | Description | | --- | --- | | `T` | | ##### `__newindex` ```nupp __newindex: function(exclusive self: WriteSpan, index: integer, value: T): nil ``` ###### Arguments | Name | Type | Description | | --- | --- | --- | | `exclusive self` | `WriteSpan\` | | | `index` | `integer` | | | `value` | `T` | | ###### Returns | Type | Description | | --- | --- | | `nil` | | ##### `drop` ```nupp drop: nosuspend function(takes self: WriteSpan): nil ``` Ends this write range, releasing the barrier on its source. ###### Arguments | Name | Type | Description | | --- | --- | --- | | `takes self` | `WriteSpan\` | | ###### Returns | Type | Description | | --- | --- | | `nil` | | ##### `ref` ```nupp ref: function(exclusive self: WriteSpan): (T[?] borrows (self), integer) ``` Answers the checked range as a mutable pointer and count, for a native call. The pointer borrows this range, so the barrier on the source outlives the call that was handed it. ###### Arguments | Name | Type | Description | | --- | --- | --- | | `exclusive self` | `WriteSpan\` | | ###### Returns | Type | Description | | --- | --- | | `T\[?\] borrows (self)` | | | `integer` | | ##### `shared` ```nupp shared: function(borrows self: WriteSpan): span.Span borrows (self) ``` Downgrades this writer to a shared view for the lifetime of the result. The writer is borrowed rather than spent, so it comes back when the shared view ends. ###### Arguments | Name | Type | Description | | --- | --- | --- | | `borrows self` | `WriteSpan\` | | ###### Returns | Type | Description | | --- | --- | | `span.Span\ borrows (self)` | | ##### `slice` ```nupp slice: function( exclusive self: WriteSpan, first: integer, last: integer? ): affine(span.WriteSpan, span.destroyWriteSpan) borrows (self) ``` Answers an affine writable subrange, inclusive at both ends. The child borrows this range, so the parent is unusable until the child is dropped. Two disjoint children at once is what `splitAt` is for. ###### Arguments | Name | Type | Description | | --- | --- | --- | | `exclusive self` | `WriteSpan\` | | | `first` | `integer` | | | `last` | `integer?` | | ###### Returns | Type | Description | | --- | --- | | `affine(span.WriteSpan\, span.destroyWriteSpan) borrows (self)` | | ##### `splitAt` ```nupp splitAt: function(exclusive self: WriteSpan, mid: integer): span.WriteSplit borrows (self) ``` `@partition(left, right)` Partitions this range at a zero-based boundary count. The two children are non-overlapping, so both are writable at once, which a pair of `slice` calls could not be. `mid` is how many elements go left. ```nupp do local writable = values:write() local split = writable:splitAt(2) split.left[1] = 11 as int32 split.right[1] = 22 as int32 end ``` ###### Arguments | Name | Type | Description | | --- | --- | --- | | `exclusive self` | `WriteSpan\` | | | `mid` | `integer` | | ###### Returns | Type | Description | | --- | --- | | `span.WriteSplit\ borrows (self)` | | ### `WriteSplit` _record_ ```nupp record span.WriteSplit readonly left: span.WriteSpan borrows (anchor) readonly right: span.WriteSpan borrows (anchor) end ``` Two sibling, non-overlapping writable regions borrowed from one parent writer. The representation is private so only this module can assert how the children relate to their anchor. #### Type parameters | Name | Description | | --- | --- | | `T` | | #### Fields ##### `left` ```nupp left: span.WriteSpan borrows (anchor) ``` The first `mid` elements, writable independently of `right`. ##### `right` ```nupp right: span.WriteSpan borrows (anchor) ``` Everything after them, writable independently of `left`. ### `WriteToken` _interface_ ```nupp sealed interface span.WriteToken drop: nosuspend function(takes self: WriteToken): nil end ``` The consuming operation shared by every writable span representation. #### Methods ##### `drop` ```nupp drop: nosuspend function(takes self: WriteToken): nil ``` ###### Arguments | Name | Type | Description | | --- | --- | --- | | `takes self` | `WriteToken` | | ###### Returns | Type | Description | | --- | --- | | `nil` | | ## Functions ### `span.destroyWriteSpan` _function_ ```nupp function span.destroyWriteSpan(takes writable: T): nil ``` Ends a write range, which is what every writable span's contract names. Nothing calls this by hand. It is the terminal consumer `affine` carries, so a scope boundary or an explicit `drop` reaches it. #### Type parameters | Name | Description | | --- | --- | | `T` | | #### Arguments | Name | Type | Description | | --- | --- | --- | | `takes writable` | `T` | the write range, spent by this call | #### Returns | Type | Description | | --- | --- | | `nil` | | ### `span.fromCarray` _function_ ```nupp function span.fromCarray(borrows source: T[?], count: integer): span.Span ``` Creates a checked shared span over a C array and an explicit logical count. A native raw reference uses the caller's explicit extent. Wasm references also check it against their rooted allocation. Later operations check this extent. ```nupp local struct Value n: int32 end const storage = carray(Value, 4) const values = span.fromCarray(storage, 4) assert(#values == 4) ``` #### Type parameters | Name | Description | | --- | --- | | `T` | | #### Arguments | Name | Type | Description | | --- | --- | --- | | `borrows source` | `T\[?\]` | the array the view reads, rooted for the view's lifetime | | `count` | `integer` | how many elements the array holds | #### Returns | Type | Description | | --- | --- | | `span.Span\` | the view, borrowed from the array | #### Raises - when count is negative ### `span.fromFixedCarray` _function_ ```nupp function span.fromFixedCarray(borrows source: T[N], count: N): span.FixedSpan ``` Creates a fixed shared span without a runtime length check. The literal count is both the stored count and the proof that the source has exactly `N` elements, so there is nothing left to check at run time. #### Type parameters | Name | Description | | --- | --- | | `T` | | | `N` | | #### Arguments | Name | Type | Description | | --- | --- | --- | | `borrows source` | `T\[N\]` | the array the view reads, rooted for the view's lifetime | | `count` | `N` | the literal count, which must be the source's `N` | #### Returns | Type | Description | | --- | --- | | `span.FixedSpan\` | the view, borrowed from the array | ### `span.fromString` _function_ ```nupp function span.fromString(borrows source: string): span.ByteSpan ``` Creates a byte span over a Lua string and keeps that string rooted. ```nupp const text = "hello" const bytes = span.fromString(text) assert(#bytes == 5 and bytes[1] == 104) ``` #### Arguments | Name | Type | Description | | --- | --- | --- | | `borrows source` | `string` | the string the view reads, rooted for the view's lifetime | #### Returns | Type | Description | | --- | --- | | `span.ByteSpan` | the byte view, borrowed from the string | ### `span.writeCarray` _function_ ```nupp function span.writeCarray(exclusive source: T[?], count: integer): span.Writable ``` Creates an affine write span over a C array and an explicit logical count. The source is exclusive during construction, and the returned owner keeps it under an incompatible-borrow barrier until `drop` or a scope boundary consumes the token. Nothing else may read or write the array in between. ```nupp local struct Value n: int32 end const storage = carray(Value, 4) do local writable = span.writeCarray(storage, 4) writable[1] = new Value(42) drop writable end const values = span.fromCarray(storage, 4) assert(values[1].n == 42) ``` #### Type parameters | Name | Description | | --- | --- | | `T` | | #### Arguments | Name | Type | Description | | --- | --- | --- | | `exclusive source` | `T\[?\]` | the array the view writes, held exclusively for its lifetime | | `count` | `integer` | how many elements the array holds | #### Returns | Type | Description | | --- | --- | | `span.Writable\` | the affine writer, borrowed from the array | #### Raises - when count is negative ### `span.writeFixedCarray` _function_ ```nupp function span.writeFixedCarray(exclusive source: T[N], count: N): span.FixedWritable ``` Creates a fixed affine write span without a runtime length check. The source array type and the literal count must name the same `N`, which is what stands in for the bounds check. #### Type parameters | Name | Description | | --- | --- | | `T` | | | `N` | | #### Arguments | Name | Type | Description | | --- | --- | --- | | `exclusive source` | `T\[N\]` | the array the view writes, held exclusively for its lifetime | | `count` | `N` | the literal count, which must be the source's `N` | #### Returns | Type | Description | | --- | --- | | `span.FixedWritable\` | the affine writer, borrowed from the array |