Skip to main content

Type System

Osta's type system is designed to provide a robust foundation for building safe and efficient programs. It categorizes types into three primary groups: primitives, struct-like types, and references. Each group serves distinct purposes, from low-level data representation to complex data structures and memory management. The type system supports advanced features such as types as first-class citizens, dependent types, linear types, and meta-compilation, enabling expressive and reliable code. This document provides a detailed overview of each type category, including definitions and examples.

Primitive Types

Primitive types are the basic building blocks for data in Osta, optimized for performance and simplicity. They include integers and floating-point numbers, each with specific use cases.

Signed Integers

Signed integers, denoted as iN (e.g., i8, i16, i32, i128), represent whole numbers with both positive and negative values. The number N specifies the bit size, which can be any positive integer. Additionally, isize is a signed integer matching the platform's pointer size.

Unsigned Integers

Unsigned integers, denoted as uN (e.g., u8, u16, u64, u128), represent non-negative whole numbers. Like signed integers, N specifies the bit size. The usize type is an unsigned integer matching the platform's pointer size.

Floating-Point Numbers

Floating-point types include f32 (single-precision) and f64 (double-precision), used for representing real numbers with fractional components.

Compound Types

Compound types enable the creation of complex data structures by combining multiple fields or variants. They include structs, tagged unions, enums, and unions, each with distinct characteristics.

Structs

Structs are composite types that group fields of different types into a single unit. Each field has a name and a type, and the struct occupies contiguous memory.

Example

struct Foo {
x: u8
}

Variant

Variants (also known as tagged unions) represent values that can be one of several variants. Internally, they are implemented as a struct containing an enum (to indicate the active variant) and a union (to store the variant's data).

Example

variant Option<T> {
Some(T),
None,
}

Enums

Enums define a set of named constants. They can have any internal type, but all constants must share the same type. If no values are provided the first constant will default to 0i32 and the following constants will be 1i32, 2i32... If just the first constant is specified to an integer the following constants will be the succession from that integer with the same type.

Examples

Constants
enum Color {
RED = (255, 0, 0),
GREEN = (0, 255, 0),
BLUE = (0, 0, 255),
}
0-First Integers
enum Mode {
OPEN,
RESTRICTED,
CLOSE,
}
N-First Integers
enum Letter {
A = 65,
B,
C,
}

Unions

Unions allow multiple fields to share the same memory location. Only one field is active at a time, and the programmer is responsible for ensuring correct access.

Example

union Data {
i: i32,
f: f32,
}

Reference Types

Reference types manage access to data in memory, providing mechanisms for both direct and safe manipulation. They include pointers and references, each with distinct behaviors regarding ownership, safety, and lifetime management.

Pointers

Pointers are raw memory addresses that provide direct access to data. Osta's pointers track two properties: whether they can be safely dereferenced (deref) and whether the current scope owns the pointer. A pointer returned by malloc or new is owned by the caller scope and has deref. Performing arithmetic operations on a pointer (e.g., incrementing) results in a new pointer without deref. When a pointer is passed by copy to a function, the deref property is disabled in the called function's scope to prevent unsafe access. Pointers can also be passed as references to avoid copying while preserving deref capabilities. Critically, if a pointer with both deref and own exits its scope without being deallocated or transferred, the program will not compile, as this indicates a potential memory leak.

References

References are borrowed pointers that enforce memory safety through compile-time checks. They carry permissions (read, write, execute) and may have lifetimes tied to other objects. The execute permission requires read. Multiple references with read permissions to the same object can coexist, but a reference with write permission must be exclusive. References can outlive the function they are passed to, such as when used by the returned object. For example, a reference passed to a thread must outlive the thread object, which is destroyed by calling join() or persists indefinitely if detach() is called.