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.