Shaped Types

Sec provides first-class shaped values for numerical and multidimensional data. The core families are vector, matrix, tensor, and the borrowed tensor_view form.

A shaped value is more than a flat collection of elements. Its rank, shape, logical element order and storage contract are part of its semantics. The compiler can therefore reason about dimensions, views, layout, borrowing, algebra and storage movement without reducing the program to untyped pointer arithmetic.

let position: vector[float32, 3]
let transform: matrix[float32, 4, 4]
let image: tensor[float32, 3, 224, 224]

Chapter Scope

This chapter covers:

  • vector[T, N];
  • matrix[T, Rows, Columns];
  • statically shaped and runtime-shaped tensor values;
  • tensor_view[T, Rank];
  • Shape, Strides, layouts and axes;
  • indexing, slicing, reshaping, permutation and broadcasting;
  • elementwise arithmetic, vector algebra, matrix multiplication and contraction;
  • memory-space and storage-placement rules for shaped data.

Ordinary arrays, slices, lists, maps and sets are covered by the collection chapters. Shaped types have separate semantics because their dimensions and multidimensional mapping are meaningful parts of the value.

Shaped Type Families

Type Ownership Rank Extents
vector[T, N] Owning 1 Compile-time
matrix[T, R, C] Owning 2 Compile-time
tensor[T, D0, D1, ...] Owning Compile-time Compile-time
tensor[T, Shape[Rank]] Owning Compile-time Runtime
ref tensor_view[T, Rank] Borrowed Compile-time Runtime or derived

vector[T, N]

A vector is an owning rank-one shaped value with a compile-time-known extent.

let direction: vector[float32, 3]

For vector[T, N]:

Rank  = 1
Shape = [N]
Len   = N

A vector is not a growable collection. Its extent belongs to the shaped type.

matrix[T, Rows, Columns]

A matrix is an owning rank-two shaped value with compile-time-known row and column extents.

let transform: matrix[float32, 4, 4]

For matrix[T, Rows, Columns]:

Rank  = 2
Shape = [Rows, Columns]
Len   = Rows * Columns

Statically Shaped tensor

A statically shaped tensor has a compile-time-known rank and compile-time-known extents.

let image: tensor[float32, 3, 224, 224]

This type has:

Rank  = 3
Shape = [3, 224, 224]
Len   = 150528

The dimension list is a compiler-known shaped-type form. It does not introduce general value-generic parameter packs for ordinary user-defined generic types.

Runtime-Shaped Owning tensor

Sec also supports owning tensors whose rank is known at compile time while the individual extents are runtime values.

let image: tensor[float32, Shape[3]]

Here:

Rank
    compile-time-known as 3

Shape
    runtime Shape[3]

Len
    runtime product of Shape

This remains an owning tensor. It is not reinterpreted as a slice, dynamic array or view merely because its extents are runtime values.

tensor_view[T, Rank]

tensor_view[T, Rank] is Sec's non-owning affine-strided shaped view. Safe source-level access uses a reference to the view:

ref tensor_view[T, Rank]
ref mut tensor_view[T, Rank]

Mutability comes from ref or ref mut. There is no separate mutable view type.

fn Inspect(image: ref tensor_view[float32, 3]) void {
    // read-only access
}

fn Modify(image: ref mut tensor_view[float32, 3]) void {
    // mutable access when the mapping is safely non-overlapping
}

A view remains tied to the lifetime, identity, validity and memory-space contract of its backing storage. A view never becomes an owner merely because it has a shape and strides.

Shape[Rank]

Shape[Rank] is a compiler-known immutable nominal value containing one non-negative extent for every axis. Rank is part of its type.

let shape: Shape[3] := Shape[3] { 3, 224, 224 }

A Shape[3] is not interchangeable with a list or a three-element array merely because a representation could look similar.

Zero extents are valid. If any extent is zero, the shaped value has Len == 0. Negative extents are invalid, and extent products are checked for overflow when they cannot be proven safe at compile time.

Shared Read-Only Properties

Shaped values and views expose a common semantic surface:

value.Rank
value.Shape
value.Len
value.Strides
value.Layout
value.MemorySpace
value.IsContiguous
value.Ptr
value.SizeOf

The core result meanings are:

Property Meaning
RankNumber of logical axes.
ShapeOne extent per axis as Shape[Rank].
LenTotal logical scalar element count.
StridesElement stride for every affine axis.
LayoutPublic storage-layout classification.
MemorySpaceStorage access and transfer domain.
IsContiguousWhether logical traversal is one forward dense range.
PtrUnsafe raw pointer to the logical origin when addressable.
SizeOfLogical represented payload size in bytes.

Len is always the product of all extents. It never means only the first dimension.

let v: vector[float32, 4]
let m: matrix[float32, 3, 4]
let t: tensor[float32, 2, 3, 4]

v.Len // 4
m.Len // 12
t.Len // 24

When a complete fact belongs to the static type, it is also available as a type-level property:

matrix[float32, 3, 4].Rank
matrix[float32, 3, 4].Shape
matrix[float32, 3, 4].Len

tensor_view[float32, 3].Rank

A runtime-shaped tensor or view type knows its rank but does not have one complete type-level runtime Shape or Len value.

Strides

Strides[Rank] is an immutable nominal value containing one element stride per axis. Strides are measured in elements, not bytes.

A stride may be:

  • positive for forward traversal;
  • negative for reverse traversal;
  • zero for a broadcast axis.

The canonical ordinary owning layout is dense row-major. Therefore a matrix[T, Rows, Columns] normally has element strides:

[Columns, 1]

A view may have very different strides without changing its logical element order.

Tensor Layout

TensorLayout[Rank] describes the public shaped storage-layout category. The canonical affine-strided categories are:

DenseRowMajor
DenseColumnMajor
Strided

Ordinary owning shaped values use dense row-major layout unless an explicit storage request requires another supported layout.

Layout is read-only. Changing representation is an explicit operation such as Relayout, not assignment to .Layout.

Contiguity

IsContiguous is true when logical traversal corresponds to one forward, dense, non-overlapping storage range in logical traversal order.

MappingTypical value
Canonical dense owning valuetrue
Row of a row-major matrixtrue
Column of a row-major matrixfalse
Transpose viewfalse
Step greater than 1false
Negative-stride reversalfalse
Broadcast with zero stridefalse

This strict meaning is useful when deciding whether a shaped value can safely be presented as one ordinary contiguous foreign buffer.

Memory Space

MemorySpace describes the semantic access and transfer domain of shaped storage. It is separate from where storage was allocated or which object owns its lifetime.

A view inherits the memory space of its backing storage. Reading .MemorySpace does not force materialization, and memory space cannot be changed by assigning to a property.

Moving data between incompatible memory spaces is always explicit.

Ptr and SizeOf

Addressable shaped values expose an unsafe read-only Ptr property that refers to the logical origin of the current addressable representation.

For a view, Ptr does not mean that later logical elements are adjacent. Code using the raw pointer must also respect shape, strides, layout, contiguity and the relevant unsafe or foreign contract.

SizeOf is the logical represented payload byte count:

value.SizeOf = value.Len * element payload size

For a non-contiguous or broadcast view, this is not the descriptor size, physical address span or count of unique backing bytes touched.

Ptr plus SizeOf describes one directly usable contiguous payload buffer only when the relevant requirements are satisfied and IsContiguous is true.

Storage Requests

Storage placement is explicit when the program needs requirements beyond ordinary canonical construction.

A general request can constrain:

  • memory space;
  • minimum alignment.

A shaped request additionally constrains layout.

StorageRequest
    MemorySpace: Option[MemorySpace]
    MinAlignment: Option[uint]

ShapedStorageRequest[Rank]
    Storage: StorageRequest
    Layout: Option[TensorLayout[Rank]]

Supplied values are requirements, not optimization hints. An omitted requirement means that the operation may use its canonical valid choice; it does not mean that the compiler must pretend a specific request was written.

Allocation authority, such as an arena or target provider, is separate from the requested storage properties.

Construction and Explicit Create

Ordinary shaped construction uses the type's canonical storage contract when storage is required:

let matrix: matrix[float32, 4, 4]

Sec does not model ordinary construction as a meaningless parameterless Create() call.

Explicit Create(...) is reserved for active storage control, such as a non-default storage request or explicit allocation authority:

let value := try matrix[float32, 4096, 4096].Create(request)

let value := try matrix[float32, 4096, 4096].Create(
    ref mut arena,
    request,
)

If storage acquisition can fail, that failure remains explicit at the storage-producing boundary.

Views and Ownership

vector, matrix and tensor are owning shaped families. tensor_view is non-owning.

A view does not destroy backing elements. Its lifetime cannot exceed the backing storage from which it was derived.

Copyability of an owning shaped value follows ordinary Sec copy and move rules. The compiler never invents an implicit deep copy merely because a shaped value owns backing storage.

A move transfers ownership. Every initialized owned element is destroyed exactly once when its final owner ends.

Indexing and Slicing

A multidimensional shaped index supplies exactly one selector for every source axis.

value[i, j, k]
value[i, .., k]
value[.., .., ..]

Sec does not silently append trailing .. selectors and does not add an ellipsis syntax for shaped indexing.

Scalar selectors

A scalar selector removes one axis. When every selector is scalar, the result is the selected element Place[T], not a rank-zero tensor.

let element := value[i, j, k]

Range selectors

A range selector preserves its axis:

matrix[2, ..]
matrix[.., 2]
tensor[1, .., 2]
tensor[1..<3, .., 4..<10]

If at least one axis remains, the result is a borrowed tensor_view[T, ResultRank]. Slicing does not allocate or copy. It keeps the backing identity and derives new shape, strides and contiguity facts.

Shaped slicing uses the normal Sec range forms:

start..end
start..

Step and Reversed Views

Range selectors reuse Sec's ordinary step syntax:

value[.. step 2]
value[10..<90 step 2]
value[.. step -1]

The step must be non-zero. Positive values produce forward strided views; negative values produce reverse strided views. step 0 is invalid.

A negative stride does not inherently make a view unsafe or immutable. Mutable access remains possible when the mapping is non-overlapping and normal borrowing rules permit it.

Reshape, ToShape and Materialize

These operations deliberately have different ownership and storage meaning.

Operation Backing Result Allocation / Copy
Reshape Same Borrowed view None
ToShape Same New owner; source consumed None
Materialize New New owner May allocate and copies/materializes

Reshape

let view := try value.Reshape(newShape)

Reshape creates a view over the same backing storage. It requires equal element count and reshape-compatible contiguous logical storage. It never hides a copy to make an incompatible input work.

ToShape

let image: tensor[float32, Shape[3]]

let flat := try image.ToShape(
    Shape[1] { image.Len },
)
// image has been consumed

ToShape consumes the source owner, reuses the same backing storage and changes the owning shaped metadata. It performs no element copy and no allocation.

Materialize

let owned := try view.Materialize()
let placed := try view.Materialize(request)

Materialize creates new owning storage from the view's logical contents. It is the operation to use when a non-contiguous, reversed, transposed or broadcast view must become an independent owning representation.

Axes and AxisList

Axes[Rank] describes a complete axis permutation. It contains every axis in 0..<Rank exactly once.

Axes[3] { 2, 0, 1 }

AxisList[Count] is an ordered list of distinct selected axes for an operation that uses only part of the rank.

AxisList[2] { 1, 3 }

Axis order is semantically significant. Duplicate or out-of-range axes are invalid.

Permute and Transpose

Permute creates a non-owning view with reordered axes:

let view := value.Permute(
    Axes[3] { 2, 0, 1 },
)

It keeps the same backing storage and memory space, while permuting shape and strides and recomputing layout and contiguity.

For rank two, Transpose() is the convenience form:

let transposed := matrix.Transpose()

equivalent to:

matrix.Permute(Axes[2] { 1, 0 })

Transpose is a view operation. It does not physically reorder the backing data. Use explicit materialization when a new packed representation is required:

let packed := try matrix.Transpose().Materialize()

Broadcasting

Scalar-to-shaped arithmetic is direct and does not require an explicit broadcast view:

let scaled := image * 0.5
let shifted := image + biasValue

Shaped-to-shaped broadcasting is explicit. Sec does not silently change dimensions merely to make two shaped operands compatible.

let expanded := right.BroadcastTo(left.Shape)
let result := left + expanded

BroadcastTo uses right-aligned matching. Matching extents are preserved; a source extent of 1 may expand using stride 0; missing leading axes behave as extent 1.

source:       [   3, 1]
destination:  [2, 3, 4]

Broadcasting allocates nothing and copies no elements. A real zero-stride expansion is overlapping and therefore produces shared view authority even when the source was mutable.

Elementwise Arithmetic

Ordinary arithmetic operators are elementwise when both operands are shaped and the element algebra supports the operator.

left + right
left - right
left * right
left / right

Two shaped operands require compatible shapes. Sec does not perform implicit shaped broadcasting.

let a: matrix[float32, 3, 4]
let b: matrix[float32, 1, 4]

let expanded := b.BroadcastTo(a.Shape)
let c := a + expanded

Scalar forms remain direct:

matrix * scalar
scalar * matrix
matrix / scalar
matrix + scalar
scalar + matrix
matrix - scalar

The result element type follows ordinary Sec scalar operator and unit algebra.

Shaped arithmetic first produces a logical shaped value. It does not become fallible merely because a later storage boundary may need allocation. The compiler may fuse or eliminate intermediate representations as long as source semantics are preserved.

Matrix Multiplication with x

Sec uses contextual infix x for canonical algebraic multiplication. * remains elementwise or scalar multiplication.

let elementwise := left * right
let product := left x right

The operator has multiplicative precedence and is left-associative. Outside operator position, x remains a valid ordinary identifier.

Matrix by matrix

matrix[L, Rows, Inner] x matrix[R, Inner, Columns]

produces a matrix shaped as:

matrix[ProductElement[L, R], Rows, Columns]

Matrix by vector

A plain vector is treated as a column vector for this operation:

matrix[L, Rows, Columns] x vector[R, Columns]

produces:

vector[ProductElement[L, R], Rows]

x does not guess arbitrary tensor contraction axes. Use Dot, Outer or Contract when those operations express the intended algebra.

Vector Algebra

Dot

let result := left.Dot(right)

Compatible equal-length vectors produce a scalar by multiplying corresponding elements and accumulating the products. The result is a scalar, not a rank-zero tensor.

Outer

let result := left.Outer(right)

vector[L, N] with vector[R, M] produces a logical matrix[ProductElement[L, R], N, M].

Magnitude

let length := value.Magnitude()

Magnitude is available when the element type supplies the required multiplication, addition and square-root semantics. Units participate in normal unit algebra.

Normalize

let normalized := value.Normalize()

Normalization returns Option[vector[...]]. A zero-magnitude vector returns None; this is a normal domain case, not an error.

Cross

let result := left.Cross(right)

Cross product is defined for compatible extent-three vectors. Sec does not silently generalize it to arbitrary dimensions.

General Tensor Contraction

General contraction is explicit:

let result := left.Contract(
    right,
    leftAxes,
    rightAxes,
)

Both axis lists have the same Count, and axes pair by position.

let result := left.Contract(
    right,
    AxisList[2] { 1, 3 },
    AxisList[2] { 0, 2 },
)

This pairs:

left axis 1 with right axis 0
left axis 3 with right axis 2

Paired extents must match exactly. Contraction never performs implicit broadcasting.

Result axes are ordered as:

all uncontracted left axes in original order
followed by
all uncontracted right axes in original order

The result rank is:

left.Rank + right.Rank - 2 * Count

Rank zero produces a scalar. Rank one or two does not cause general Contract to switch dynamically into the vector or matrix families; specialized operations remain Dot, Outer and x.

Iteration

Shaped iteration follows logical index order rather than accidental physical address order. The canonical traversal is lexicographic by axis, with the last axis varying fastest.

A transposed, reversed or otherwise strided view still iterates according to its logical shape and axis order. A broadcast view may therefore yield the same backing element at several logical indexes.

Mutation during iteration remains subject to ordinary borrowing, aliasing, overlap and storage-stability rules.

Storage-Producing Operations

Operations that intentionally create or change physical backing remain visible in source code.

Materialize

Produces new owning storage from a view. It may allocate and copy or pack logical elements.

TransferTo

let deviceValue := try value.TransferTo(request)

Produces a new owner satisfying a different storage request. The source owner remains valid. The implementation may copy, map, synchronize or use a target transfer mechanism, but it may not silently substitute an incompatible destination contract.

Relayout

let columnMajor := try value.Relayout(
    TensorLayout[2].DenseColumnMajor,
)

Produces a new owning representation with the requested layout. It may allocate and physically reorder elements while leaving the source owner valid.

Synchronous TransferTo is complete when it returns successfully. A real asynchronous transfer requires a separate API that represents outstanding lifetime and completion obligations.

Errors and try

Sec applies static proof before inserting runtime checks.

KnowledgeBehavior
Provably validOperation is infallible for that condition; no try.
Provably invalidCompile-time diagnostic.
Runtime-dependentChecked operation; try when it returns Result.

This rule applies consistently to runtime-shaped arithmetic, runtime matrix multiplication, reshape operations, broadcasting, contraction and runtime indexing or slicing checks.

let a: tensor[float32, Shape[3]]
let b: tensor[float32, Shape[3]]

let c := try a + b

Here try is required because runtime shape compatibility must be checked, not because the expression might eventually need storage.

ShapeError

Runtime-dependent shaped validity uses the ShapeError family, including:

RankMismatch
DimensionMismatch
ElementCountOverflow
InvalidReshape
InvalidBroadcast
InvalidContraction
InvalidAxis
DuplicateAxis
InvalidStep

Runtime scalar indexing uses IndexError where required, while runtime slicing/range validity uses RangeError.

StorageError

Explicit storage-producing operations use StorageError when destination requirements or physical storage work can fail. The public error family covers at least allocation failure, unsupported memory space or layout, unsatisfied alignment, transfer failure and size overflow.

No Hidden Heap

Shaped types do not assume a hidden global heap. If a storage-producing operation requires allocation, it must have valid allocation authority such as an arena, explicit allocator, target/device provider or another approved source.

Escape analysis does not repair an invalid view lifetime by silently allocating or deep-copying the data.

Logical operations such as indexing, view formation, reshape, permutation, transpose, broadcasting and ordinary algebra do not acquire allocation semantics merely because a later materialization might allocate.

FFI and Raw Buffer Access

A shaped value does not automatically have a foreign-stable descriptor ABI. Crossing a foreign boundary therefore requires the foreign layout contract to be explicit.

For APIs expecting one dense buffer, verify the relevant element ABI and IsContiguous before treating Ptr and SizeOf as a single foreign payload range.

A strided, transposed, reversed or broadcast view may need explicit Materialize() before it can satisfy such a contract. That decision is never made silently by Ptr access.

Compiler and Tooling Guarantees

The compiler tracks the shaped facts needed to validate source semantics, including element type, rank, shape, static versus runtime extents, strides, layout, memory space, contiguity, ownership, borrow authority and backing dependencies.

Diagnostics should report whether failure comes from static shape incompatibility, runtime checking, axis selection, indexing, storage placement or borrowing rather than reducing the problem to a low-level pointer error.

Editor hover and completion should expose the same resolved shaped types and properties used by compilation, including when try is required and whether an operation returns Option or Result.

Best Practices

  • Use vector, matrix and tensor when rank and shape are part of the problem domain.
  • Prefer views for slicing, permutation, transpose and compatible reshape when ownership is not required.
  • Use Materialize only when independent owning storage is actually needed.
  • Use ToShape when an owner can be consumed and the same backing should be retained.
  • Keep shaped-to-shaped broadcasting explicit with BroadcastTo.
  • Remember that * is elementwise while x is canonical algebraic matrix multiplication.
  • Use Dot, Outer and Contract to make algebraic intent explicit.
  • Do not infer physical contiguity from shape alone; inspect IsContiguous when it matters.
  • Do not infer ownership from a view or raw pointer.
  • Make memory-space changes and storage production explicit.
  • Let compile-time shape errors remain compile-time errors rather than adding unnecessary runtime handling.
  • Use try only when validity genuinely depends on runtime information or storage work can fail.

Summary

  • Sec has first-class owning vector, matrix and tensor families.
  • tensor_view is the compiler-known non-owning affine-strided view family.
  • Rank is compile-time-known for every core shaped type form.
  • Tensor extents may be fully static or runtime values carried by Shape[Rank].
  • Shape and Strides are nominal metadata values, not interchangeable ordinary collections.
  • Len is the product of all extents.
  • Canonical ordinary owning storage is dense row-major.
  • Views may use positive, negative or zero strides.
  • Zero stride represents broadcasting; negative stride represents reverse traversal.
  • IsContiguous means one forward dense non-overlapping logical payload range.
  • MemorySpace describes access and transfer domain, not allocation origin.
  • Ptr is unsafe and does not imply a view is contiguous.
  • Slicing and view transformations do not allocate or copy.
  • Full scalar indexing yields an element Place rather than a rank-zero tensor.
  • Reshape returns a view over the same backing.
  • ToShape consumes an owner while preserving backing storage.
  • Materialize creates new owning storage and may allocate.
  • Permute and Transpose are view transformations.
  • Shaped broadcasting is explicit through BroadcastTo.
  • Ordinary + - * / are elementwise for compatible shaped operands.
  • x is the contextual operator for canonical matrix multiplication.
  • Vector algebra provides explicit Dot, Outer, Magnitude, Normalize and Cross semantics.
  • General tensor contraction is explicit through Contract and ordered axis lists.
  • Shaped iteration follows logical axis order, not accidental physical address order.
  • The compiler never performs an implicit storage transfer merely to make an operation legal.
  • Provably invalid shaped operations are compile-time errors.
  • Runtime-dependent shape validity uses checked operations and ShapeError.
  • Storage-producing failures use StorageError.
  • Shaped types do not require or assume a hidden global heap.