Ovum has a rich type system with primitive types and user-defined types. The type system is static and does not permit implicit type coercions (an Int
won’t automatically become a Float
without an explicit cast, for example).
Fundamental types are passed by value and represent the basic building blocks of the language:
int
(8 bytes) - 64-bit signed integer
42
, -17
, 0x1A
(hex), 0b1010
(binary)float
(8 bytes) - 64-bit floating-point number (IEEE 754 double precision)
3.14
, 2.0e10
, 1.5E-3
, .5
, 5.
Infinity
, -Infinity
, NaN
byte
(1 byte) - 8-bit unsigned integer
255
, 0x00
, 0b11111111
char
- single Unicode character (UTF-32)
'A'
, '中'
, '\n'
, '\t'
, '\0'
bool
- Boolean value (true
, false
)
bool
pointer
- raw memory address (only meaningful in unsafe
code)
Fundamental Type Constraints: Fundamental types cannot be made nullable (
int?
is invalid). They are notObject
types and cannot be stored inObjectArray
or cast toObject
. To convert nullable primitives to fundamentals, cast to primitive first:(nullableInt as Int) as int
.
Primitive reference types are built-in reference wrappers around fundamental types, passed by reference:
Int
- reference wrapper for int
valuesFloat
- reference wrapper for float
valuesByte
- reference wrapper for byte
valuesChar
- reference wrapper for char
valuesBool
- reference wrapper for bool
valuesPointer
- reference wrapper for pointer
values (only meaningful in unsafe
code)Nullable Primitives: Any primitive reference type can be made nullable by appending
?
(e.g.,Int?
,Float?
,Bool?
).
String
- immutable text data (UTF-8 encoded)
"Hello"
, "Multi-line\nstring"
, ""
(empty string)"Hello" + " " + "World"
Object
- root of all reference types
Ovum provides specialized array classes for different element types (no generics/templates):
Primitive Arrays:
IntArray
- array of Int
reference wrappersFloatArray
- array of Float
reference wrappersBoolArray
- array of Bool
reference wrappersCharArray
- array of Char
reference wrappersByteArray
- array of Byte
reference wrappersPointerArray
- array of Pointer
reference wrappersObject Arrays:
ObjectArray
- array of any Object
-derived typesStringArray
- convenience array of String
(used for Main
function arguments)Array Creation:
val numbers: IntArray = IntArray(10) // Create array of Int reference wrappers
val names: StringArray = StringArray(5) // Create string array of size 5
val objects: ObjectArray = ObjectArray(3) // Create object array of size 3
Create type aliases for better code readability:
typealias UserId = Int
typealias UserName = String
fun ProcessUser(id: UserId, name: UserName): Void {
// Implementation
}
=
)The standard assignment operator assigns references for reference types:
val original: String = "Hello"
val reference: String = original // Both variables point to the same string
:=
)The copy assignment operator performs deep copy for reference types:
val original: String = "Hello"
val copy: String := original // Creates a new string with the same content
Use the as
operator for explicit casting:
val intValue: int = 42
val floatValue: float = (intValue as float) // int to float
val floatNum: float = 3.14
val intNum: int = (floatNum as int) // float to int (truncates)
// Implicit bidirectional casting between fundamental and primitive reference types
val fundamentalInt: int = 42
val primitiveInt: Int = fundamentalInt // Implicit: int -> Int
val backToFundamental: int = primitiveInt // Implicit: Int -> int
// Implicit conversion from literals to primitive types
val count: Int = 0 // Implicit: int literal -> Int
val flag: Bool = true // Implicit: bool literal -> Bool
val pi: Float = 3.14 // Implicit: float literal -> Float
// Arithmetic works seamlessly
val sum: Int = 10 + 20 // Int + Int = Int (implicit conversion from literals)
val result: int = sum + 5 // Int + int = int (implicit conversion)
Any value can be explicitly cast to bool
:
val intVal: int = 42
val boolVal: bool = (intVal as bool) // true (non-zero)
val zeroInt: int = 0
val falseBool: bool = (zeroInt as bool) // false (zero)
val nullString: String? = null
val nullBool: bool = (nullString as bool) // false (null)
// With primitive reference types (implicit conversion)
val primitiveInt: Int = 42 // Implicit conversion from literal
val primitiveBool: bool = primitiveInt // Implicit: Int -> bool
val boolRef: Bool = true // Implicit: bool literal -> Bool
Rules: Fundamentals and primitive reference types: zero → false
, non-zero → true
.
References: null
→ false
, non-null → true
Some casts require unsafe
blocks:
unsafe {
val obj: Object = Point(10, 20)
val bytes: ByteArray = (obj as ByteArray) // Raw byte view
val mutableBytes: ByteArray = (obj as var ByteArray) // Mutable byte view
}
Fundamental types (int
, float
, byte
, char
, bool
, pointer
) are passed by value (copied):
fun ModifyInt(x: int): Void {
x = x + 1 // Only modifies the local copy
}
Primitive reference types (Int
, Float
, Byte
, Char
, Bool
, Pointer
) and all other reference types (including String
, arrays, and user-defined types) are passed by reference:
fun ModifyIntRef(var x: Int): Void {
x = x + 1 // Implicit conversion: Int + int -> Int
}
fun ModifyArray(arr: IntArray): Void {
arr[0] := Int(999) // Use := for deep copy assignment
}
Immutability: References are immutable by default - use var
for mutable references:
fun CannotReassign(str: String): Void {
// str = "New value" // ERROR: Cannot reassign immutable reference
}
fun CanReassign(var str: String): Void {
str = "New value" // OK: str is mutable
}
Static typing: Every variable and expression has a type checked at compile time
Limited implicit conversions: The compiler only performs implicit conversions between a primitive reference type and its matching fundamental (for example, Int
↔ int
). Any conversion across different primitive families—such as Int
to Float
or Float
to int
—must use an explicit cast.
Type safety: Prevents many common errors
Nullable types: Any reference type (including primitive reference types) can be made nullable by appending ?
. Fundamental types cannot be nullable.
val x: int = 42
val y: String = "Hello"
// val z: int = x + y // ERROR: Cannot add int and String
val intVal: int = 42
val floatVal: float = 3.14
val result: float = (intVal as float) + floatVal // OK: Explicit conversion
// Using primitive reference types (implicit conversion between wrappers and fundamentals)
val refInt: Int = 42 // Implicit conversion from literal to Int
val refFloat: Float = 3.14 // Implicit conversion from literal to Float
val sum: Int = refInt + (refFloat as Int) // Requires explicit narrowing
val fundamentalSum: int = sum + 10 // Implicit: Int -> int when assigning to a fundamental
// Converting nullable primitives to fundamentals
val nullableInt: Int? = 42 // Implicit conversion from literal
val fundamentalFromNullable: int = (nullableInt ?: 0) as int // Two-step conversion
Types used as parameters in pure functions must implement IComparable
for stable ordering:
pure fun ProcessData(data: IComparable): Int {
// data must implement IComparable for stable ordering
return data.GetHash()
}
Type information is preserved at runtime for reference types:
fun ProcessObject(obj: Object): Void {
if (obj is String) {
val str: String? = obj as String
if (str != null) {
val nonNullStr: String = str ?: "default" // Use Elvis operator
sys::Print("String length: " + nonNullStr.Length().ToString())
}
} else if (obj is IntArray) {
val arr: IntArray? = obj as IntArray
if (arr != null) {
val nonNullArr: IntArray = arr ?: IntArray(0) // Use Elvis operator
sys::Print("Array size: " + nonNullArr.Length().ToString())
}
} else if (obj is Int) {
val intRef: Int? = obj as Int
if (intRef != null) {
val nonNullInt: Int = intRef ?: 0 // Use Elvis operator
sys::Print("Int value: " + nonNullInt.ToString()) // Implicit conversion to string
}
}
}