Types

Sec is statically typed. Every expression that survives semantic analysis has a resolved type, and that type has language-defined meaning independent of its machine representation.

The type system covers ordinary scalar values, exact decimal arithmetic, temporal values, references, sequences, collections, shaped data, hardware layouts, generic types, semantic units and user-defined nominal types.

Rule: Representation does not define type identity. Two types may have identical storage while remaining semantically different.

Type Principles

Sec's type system follows several language-wide principles:

  • types are resolved statically;
  • named types preserve nominal identity;
  • untyped literals may be shaped by context;
  • already typed values are not broadly converted implicitly;
  • checked conversions are explicit;
  • type semantics are independent of LLVM, MLIR and ABI layout;
  • ownership behavior is a separate semantic property of the resolved type.
type CustomerID uint64
type ProductID uint64

let customer: CustomerID := 42
let product: ProductID := customer    // invalid

Both named types may eventually use the same machine representation, but that representation does not make them interchangeable.

Type Categories

The central type model includes these broad categories:

Category Examples
Specialany, void
Logical and text scalarsbool, byte, char, rune, string
Signed integersint, int8 ... int256
Unsigned integersuint, uint8 ... uint256
Binary floating pointfloat, float32, float64
Exact decimaldecimal, decimal128
Temporaldate, time, datetime, duration
Fixed sequencesT[N]
Owning sequencesT[]
Borrowed slicesref T[], ref mut T[]
Safe referencesref T, ref mut T
Raw addressesRawPtr[T]
Function valuesfn(T) R, mut fn() R, -> fn() R
Collectionslist, map, set
Shaped typesvector, matrix, tensor, tensor_view
Hardware layoutbit, bit[N], register[N]
Core nominal genericsResult[T, E], Option[T], Task[T], Thread[T]
User-defined nominal typesnamed types, structs, enums, unions, interfaces, units and registers

This page introduces the families. Their complete behavior is defined in the specialized manual chapters.

Builtin and Compiler-Known Types

Fundamental language types and first-class compiler-known type constructors use lowercase names and require no import.

any
bool
byte
char
error
rune
string
void

int
int8
int16
int32
int64
int128
int256

uint
uint8
uint16
uint32
uint64
uint128
uint256

float
float32
float64

decimal
decimal128

date
time
datetime
duration

bit
register

list
map
set

vector
matrix
tensor
tensor_view

Some compiler-known nominal types retain uppercase names:

Result[T, E]
Option[T]
Iterator[T]
Task[T]
Thread[T]
Shape[Rank]
Strides[Rank]
TensorLayout[Rank]
MemorySpace
RawPtr[T]

Compiler knowledge does not make these types structurally interchangeable. Their identity and semantics are still defined by their owning type rules.

Special Types

void

void represents the absence of a returned value:

fn Notify(message: string) void {
    fmt.Println(message)
}

void is not an ordinary storable value type. Variables and stored fields cannot have type void.

Specific compiler-known generic forms may use it where their own rules permit:

Result[void, IOError]
RawPtr[void]

any

any is Sec's compiler-known general-value type. It does not turn Sec into a dynamically typed language.

Using any does not permit unchecked implicit conversion, unrestricted member access, hidden reflection or arbitrary runtime type coercion. Narrowing a value from any requires defined type-safe semantics.

error

error is the compiler-known root of Sec's typed error system. It is not a general object base class.

enum IOError error {
    OpenError
    ReadError
}

The detailed error type model is described in the Error Handling chapters.

Logical and Text Types

bool

bool has exactly two logical values:

true
false

Conditions require a boolean expression:

if count > 0 {
    Process()
}

Numeric values do not implicitly become boolean values:

if count {       // invalid
}

Numeric-to-boolean conversion is explicit and infallible:

let ready := bool(count)

Zero converts to false; non-zero converts to true.

byte

byte is the compiler-known byte scalar type. It is intended for raw byte-oriented data such as binary buffers, protocols and FFI.

let marker: byte := 0x7F

Equal storage width does not automatically make byte identical to another fixed-width integer type for every semantic or ABI purpose.

char

char is Sec's character scalar type:

let ch := 'A'
let newline := '\n'

The numeric family suffix for char is t:

let zero := 0t
let a := 65t
let hexA := 0x41t

rune

rune is Sec's Unicode scalar type:

let omega := 0x03A9r
let face := 0x1F600r
let r: rune := 'A'

char and rune are distinct types. Conversion between them follows explicit conversion and representability rules.

string

string is Sec's compiler-known string type:

let name := "Sec"
let message := $"Hello {name}"

A string is not an implicit array of char, rune or byte. Conversion and traversal use explicit string/core operations.

Array slicing syntax does not apply directly to string. Checked substring extraction belongs to the public string API.

Integer Types

Sec provides signed and unsigned integer families.

Signed integers

int
int8
int16
int32
int64
int128
int256

Unsigned integers

uint
uint8
uint16
uint32
uint64
uint128
uint256

Explicit-width integer types have the width stated by the type name. Use these types when storage size, binary layout, protocol representation, interoperability, or another exact-width requirement is part of the program's semantics.

Target-sized integers

int and uint are target-sized integer types.

let index: int := 0
let size: uint := 4096

Their physical width is selected by the target profile. Code that requires an exact external or ABI width should use an explicit-width type:

int32
uint64
int128
Rule: A target may choose the physical width of int and uint, but it does not change their signedness or ordinary language semantics.

Binary Floating-Point Types

Sec provides:

float
float32
float64

float32 and float64 request explicit binary floating-point widths. float is the general binary floating-point family type.

let small: float32 := 1.5
let wide: float64 := 1.5
let general := 1.5g

The g suffix selects the binary floating-point family. It does not select a fixed width.

Use an explicit width when layout, an external data format or an ABI requires one.

Exact Decimal Types

Sec provides two exact base-10 decimal types:

decimal
decimal128

An unsuffixed fractional or exponent literal defaults to decimal when no stronger context selects another compatible type:

let value := 3.14
let scientific := 1e3

Both values infer as exact decimal values.

The m suffix explicitly selects the exact decimal family:

let whole := 10m
let exact := 3.14m
let scientific := 1.25e-3m
let wide: decimal128 := 8m

Exact decimal literals are interpreted from their source spelling. They are not first converted through binary floating point.

decimal32 and decimal64 are not canonical Sec types.

Temporal Types

Sec provides four compiler-known temporal value types:

date
time
datetime
duration
Type Meaning
dateA calendar date
timeA time of day
datetimeA combined date and time value
durationAn elapsed amount of time

These are first-class Sec types, not aliases for strings or integers. They do not implicitly convert to or from numeric types.

Detailed calendar, timezone, precision, parsing, formatting and arithmetic rules belong to the temporal type rules rather than being inferred from their names.

Literal Typing

Literal tokens preserve their source information until semantic context resolves their type.

Default inference

Literal form Default type
10int
3.14decimal
1e3decimal
'A'char
"Sec"string
truebool

Numeric family suffixes

i    signed integer family
u    unsigned integer family
g    binary floating-point family
m    exact decimal family
t    char
r    rune

Examples:

8i
8u
8g
8m
65t
65r

Family suffixes normally select a scalar family, not a fixed width. Context may still select a concrete width:

let small: int8 := 8i
let wide: int256 := 8i

let smallFloat: float32 := 8g
let wideFloat: float64 := 8g

let exact: decimal128 := 8m

Context shaping

An untyped literal may be shaped directly by its target context:

let small: int8 := 10
let wide: uint256 := 10
let ratio: float64 := 3.14
let exact: decimal128 := 3.14
let percent: Percent := 50

Context shaping is not the same operation as converting an already typed runtime value.

Variables and Type Inference

Variables are immutable by default:

let a := 9
let name := "Sec"

An explicit type may be supplied:

let a: int := 9
let ratio: float64 := 3.14

Mutability is explicit:

let mut counter := 0
counter += 1

A mutable declaration may omit the initializer only when the declared type is defaultable:

let mut value: int

An immutable declaration requires an initializer:

let value: int    // invalid

Multiple declarators

let a := 9, b := "hello", c := true

Each declarator is checked and inferred independently.

Type-first declarations

int mut: x
int mut: a, b, c

int: minimum := 0, maximum := 100

Sec also supports immutable parenthesized type-first declaration groups:

TokenType (
    ILLEGAL := "ILLEGAL",
    EOF := "EOF",
    IDENT := "IDENT",
)

This is a declaration group, not construction of a TokenType value.

Rule: Type inference resolves from explicit language information. It does not guess between semantically different types.

Type Identity

Type identity is stronger than representation identity.

Named types

type Percent int range 0..100
type Age int range 0..130

Percent, Age and int are different types.

Generic types

list[int]
list[string]

Pair[int, string]
Pair[string, int]

Generic arguments participate in concrete type identity.

Arrays

int[4]
int[5]

Array length participates in type identity.

References

ref Buffer
ref mut Buffer

Reference mode participates in type identity.

Function values

fn(int) bool
fn(int) int
fn(ref Buffer) void
fn(ref mut Buffer) void

mut fn() int
-> fn() Resource

Parameter types, ownership modes, callable capability and return type all participate in callable type identity.

Conversions

Sec does not broadly convert already typed values implicitly.

type Percent int range 0..100

let raw: int := 90
let value: Percent := raw    // invalid

The general explicit conversion syntax is:

TargetType(value)

Examples:

int32(value)
decimal(value)
bool(value)
char(value)
rune(value)
Percent(value)

Conversions are checked. They must not silently truncate, wrap, lose precision, violate a contract or construct an invalid scalar value.

A conversion that may fail requires try:

let small := try int32(largeValue)
let r := try rune(codePoint)
let p := try Percent(raw)

When compile-time information proves the conversion valid, the compiler may eliminate the runtime check. The source-level semantics remain checked.

Important: Literal shaping and conversion are different operations. A literal has not yet acquired an unrelated runtime type that must be converted.

Defaultability

Every type is classified as either:

Defaultable
NonDefaultable

A defaultable type has one compiler-resolved valid default value. A non-defaultable type requires explicit initialization in any context that would otherwise need a default.

int mut: value

This is valid because int is defaultable.

Named types, constrained types, structs, unions, generic instances and other compound types determine defaultability from their own semantic rules.

Rule: Undefined storage is never treated as a language default value.

Arrays, Owning Sequences and Slices

Sec uses postfix sequence syntax.

Fixed array

T[N]

Examples:

int[4]
byte[256]

The array length is part of the type.

Owning unsized sequence

T[]

Example:

Packet[]

Borrowed slice

ref T[]
ref mut T[]

Examples:

ref byte[]
ref mut int[]

A slice is a non-owning reference to sequence storage. Array, sequence, slice, indexing and bounds behavior are described in the collection chapters.

References and Raw Pointers

Safe references

ref T
ref mut T

ref T provides shared access. ref mut T provides exclusive mutable access.

Safe references do not own the referenced value. Ordinary Sec source does not expose lifetime parameters.

Raw pointers

RawPtr[T]

Examples:

let address: RawPtr[byte]
let opaque: RawPtr[void]

RawPtr[T] is an unchecked low-level address. It is not a safe reference, does not imply ownership and does not extend lifetime.

Function Types

Functions and lambdas may exist as typed values.

fn(int) bool
fn(int, int) int
fn(string) void

Parameter names are not part of function-type identity.

Callable capability and parameter ownership are part of the type model:

fn() int
mut fn() int
-> fn() Resource

fn(ref Buffer) void
fn(ref mut Buffer) void

The detailed callable, lambda and closure model is described in the function chapters.

First-Class Collections

Sec has compiler-known collection type constructors:

list[T]
list[T, Capacity]

map[K, V]
map[K, V, Capacity]

set[T]
set[T, Capacity]

They require no import and are not aliases for standard-library generic types. Compiler knowledge allows collection semantics to participate directly in allocation, ownership, indexing, analysis and lowering.

More specialized containers remain normal nominal library types.

First-Class Shaped Types

Sec includes compiler-known shaped type constructors:

vector[T, N]
matrix[T, Rows, Columns]
tensor[T, Dimensions...]
tensor[T, Shape[Rank]]
tensor_view[T, Rank]

Examples:

vector[float64, 3]
matrix[float32, 4, 4]
tensor[float32, 3, 224, 224]
tensor_view[float32, 3]

Compile-time-known and runtime-shaped tensors are distinct forms. tensor_view is a non-owning view type form rather than an owning tensor.

Safe source-level views use reference syntax:

ref tensor_view[float32, 3]
ref mut tensor_view[float32, 3]

Shape, rank, stride, layout and memory-space semantics are described in the shaped-types chapter and rulebook.

Hardware Layout Types

Sec provides compiler-known hardware-oriented forms:

bit
bit[N]
register[N]

Example:

type Control register[8] {
    Enabled: bit
    Mode: bit[3]
    _: bit[4]
}

These are language-level layout forms, not ordinary standard-library generics. Their widths and field placement participate in compile-time validation.

Unit-Bearing Numeric Types

A unit is semantic quantity identity. It is not itself a numeric representation. A unit-bearing numeric type combines a numeric carrier with a unit descriptor.

unit Meter physical
unit Second physical
unit Item uint other

let exact: decimal<Meter> := 10
let measured: float64<Meter> := 10
let count: <Item> := 4

A unit declaration may define a default carrier, but that carrier is not part of unit identity. The same unit may therefore appear with more than one suitable numeric carrier where the unit rules permit it.

Compound unit-only expressions such as <m/s> default to decimal.

Result and Option

Result[T, E] and Option[T] are nominal core generic types.

Result[Data, IOError]
Option[User]

Result[T, E] is the foundation of explicit typed error handling. Its error type must be error or a concrete Sec error type.

Result[Data, IOError]    // valid
Result[Data, error]      // valid
Result[Data, string]     // invalid

Option[T] represents explicit optionality. Safe references themselves are non-null, so optional reference semantics use Option rather than nullable ref.

Types and Ownership

Type compatibility does not by itself determine whether a value is copied, moved or borrowed.

The compiler separately classifies semantic operations such as:

  • copy;
  • move;
  • borrow;
  • mutation;
  • destruction;
  • SSA reuse where no source-level transfer occurs.

Copyability, move-only behavior, destruction requirements and borrow rules are semantic properties of the resolved type.

Rule: Backend loads and stores do not define source-level copy or move semantics.

Must-use and discardability

A type may also carry separate rules about whether produced values must be used and whether explicit terminal discard is permitted.

Result[int, IOError]

is must-use, while explicit discard may still be legal under the discard rules. These properties are distinct from copyability and physical representation.

Summary

  • Sec is statically typed.
  • Every semantically valid expression has a resolved type.
  • Representation does not define type identity.
  • Named types are nominal and remain distinct from their underlying types.
  • Untyped literals may be shaped by context.
  • Already typed values do not broadly convert implicitly.
  • Checked conversions use TargetType(value) and require try when failure is possible.
  • int and uint are target-sized; fixed-width integer types extend through 256 bits.
  • Unsuffixed fractional and exponent literals default to exact decimal.
  • decimal128 is the wider exact decimal type.
  • date, time, datetime and duration are first-class temporal types.
  • Arrays and sequences use postfix T[N] and T[] syntax.
  • Safe references use ref T and ref mut T.
  • RawPtr[T] is an unchecked non-owning address type.
  • Functions, collections, shaped data and hardware layouts are part of the type system.
  • Units add semantic quantity identity independently of numeric representation.
  • Result and Option are nominal core generic types.
  • Ownership, copying, moving and destruction are semantic properties associated with resolved types.