Data Types
Every value in Oxide has a data type, which tells the compiler what kind of data is being specified so it knows how to work with that data. Oxide is statically typed, meaning the compiler must know the types of all variables at compile time. The compiler can usually infer types from values and how we use them, but when many types are possible, we must add a type annotation.
Scalar Types
A scalar type represents a single value. Oxide has four primary scalar types: integers, floating-point numbers, Booleans, and characters. Oxide provides intuitive type aliases that will feel familiar if you're coming from Swift, Kotlin, or TypeScript.
Integer Types
An integer is a number without a fractional component. Oxide provides signed and unsigned integers of various sizes:
| Oxide Type | Rust Equivalent | Size | Range |
|---|---|---|---|
Int8 | i8 | 8-bit | -128 to 127 |
Int16 | i16 | 16-bit | -32,768 to 32,767 |
Int32 | i32 | 32-bit | -2.1B to 2.1B |
Int64 | i64 | 64-bit | Very large |
Int | i32 | 32-bit | Default signed integer |
IntSize | isize | arch | Pointer-sized signed |
UInt8 | u8 | 8-bit | 0 to 255 |
UInt16 | u16 | 16-bit | 0 to 65,535 |
UInt32 | u32 | 32-bit | 0 to 4.3B |
UInt64 | u64 | 64-bit | Very large |
UInt | u32 | 32-bit | Default unsigned integer |
UIntSize | usize | arch | Pointer-sized unsigned |
The Int type (which maps to Rust's i32) is the default choice for integers and is generally the fastest, even on 64-bit systems. Use UIntSize when indexing collections, as it matches the size of memory addresses on your system.
fn main() {
let age: Int = 30
let temperature: Int = -15
let count: UIntSize = 1000
// Type inference works too
let inferred = 42 // Defaults to Int
}
Integer Literals
You can write integer literals in various forms:
| Literal | Example |
|---|---|
| Decimal | 98_222 |
| Hex | 0xff |
| Octal | 0o77 |
| Binary | 0b1111_0000 |
Note that underscores can be inserted for readability: 1_000_000 is the same as 1000000.
Floating-Point Types
Oxide has two floating-point types for numbers with decimal points:
| Oxide Type | Rust Equivalent | Size | Precision |
|---|---|---|---|
Float32 | f32 | 32-bit | ~6-7 digits |
Float64 | f64 | 64-bit | ~15-16 digits |
Float | f64 | 64-bit | Default floating-point |
The Float type (which maps to Rust's f64) is the default because modern CPUs handle double-precision floats nearly as fast as single-precision, and it provides more accuracy.
fn main() {
let pi: Float = 3.14159
let temperature = 98.6 // Inferred as Float
let precise: Float64 = 2.718281828459045
// Scientific notation
let avogadro: Float = 6.022e23
}
Numeric Operations
Oxide supports the standard mathematical operations: addition, subtraction, multiplication, division, and remainder:
fn main() {
// Addition
let sum = 5 + 10
// Subtraction
let difference = 95.5 - 4.3
// Multiplication
let product = 4 * 30
// Division
let quotient = 56.7 / 32.2
let truncated = 5 / 3 // Results in 1 (integer division)
// Remainder
let remainder = 43 % 5
}
The Boolean Type
Oxide's Boolean type has two possible values: true and false. Booleans are one byte in size and are specified using the Bool type:
fn main() {
let isActive: Bool = true
let isComplete = false // Inferred as Bool
// Booleans are often the result of comparisons
let isGreater = 5 > 3 // true
}
Rust comparison: Oxide uses Bool instead of bool, following the convention of capitalizing type names.
The Character Type
The char type represents a single Unicode scalar value. Character literals use single quotes:
fn main() {
let letter = 'a'
let emoji = '😀'
let heart = '❤'
}
The char type is four bytes and represents a Unicode Scalar Value, which means it can represent much more than just ASCII.
Compound Types
Compound types can group multiple values into one type. Oxide has two primitive compound types: tuples and arrays.
Tuples
A tuple groups together values of different types into one compound type. Tuples have a fixed length; once declared, they cannot grow or shrink.
fn main() {
let tup: (Int, Float, Bool) = (500, 6.4, true)
// Destructuring
let (x, y, z) = tup
println!("The value of y is: \(y)")
// Access by index
let five_hundred = tup.0
let six_point_four = tup.1
let is_true = tup.2
}
The tuple without any values, (), is called the unit type and represents an empty value or empty return type.
Arrays
Arrays contain multiple values of the same type with a fixed length. Use square brackets for array literals:
fn main() {
let numbers: [Int; 5] = [1, 2, 3, 4, 5]
let months: [&str; 12] = [
"January", "February", "March", "April",
"May", "June", "July", "August",
"September", "October", "November", "December"
]
// Initialize with same value
let zeros: [Int; 5] = [0; 5] // [0, 0, 0, 0, 0]
// Accessing elements
let first = numbers[0]
let second = numbers[1]
}
Arrays are useful when you want data on the stack rather than the heap, or when you need a fixed number of elements. For a collection that can grow or shrink, use Vec<T> instead.
The String Type
Oxide has two string types:
str: A string slice, usually seen as&str. This is an immutable reference to string data.String: A growable, heap-allocated string.
fn main() {
// String literal (type is &str)
let greeting = "Hello, world!"
// Create an owned String
let name: String = "Alice".toString()
// String with interpolation
let message = "Hello, \(name)!"
println!("\(message)")
}
String interpolation with \(expression) is a key Oxide feature. Any expression inside \() is evaluated and converted to a string:
fn main() {
let count = 42
let price = 19.99
println!("Count: \(count), Price: $\(price)")
println!("Total: $\(count as Float * price)")
}
Rust comparison: Rust uses format!("{}", x) for string formatting. Oxide's \(x) syntax is inspired by Swift and is more concise.
#![allow(unused)] fn main() { // Rust println!("Count: {}, Price: ${}", count, price); }
Nullable Types
Oxide has first-class support for nullable (optional) types using the ? suffix. A T? type can hold either a value of type T or null:
fn main() {
let maybeNumber: Int? = 42
let nothing: String? = null
// Check if value exists
if let number = maybeNumber {
println!("Got number: \(number)")
}
// Provide a default with ??
let value = maybeNumber ?? 0
// Force unwrap with !! (use carefully!)
let forced = maybeNumber!!
}
The T? syntax is equivalent to Rust's Option<T>, and null is equivalent to None:
| Oxide | Rust |
|---|---|
Int? | Option<i32> |
String? | Option<String> |
null | None |
Some(x) | Some(x) |
x ?? y | x.unwrap_or(y) |
x!! | x.unwrap() |
fn findUser(id: Int): User? {
if id == 1 {
Some(User { name: "Alice".toString() })
} else {
null
}
}
fn main() {
let user = findUser(1) ?? User { name: "Guest".toString() }
println!("Hello, \(user.name)")
}
Collection Types
Oxide uses Rust's standard collection types directly:
Vectors
Vec<T> is a growable array type:
fn main() {
// Create a vector with the vec! macro
var numbers: Vec<Int> = vec![1, 2, 3]
// Add elements
numbers.push(4)
numbers.push(5)
// Access elements
let first = numbers[0]
let maybe_tenth: Int? = numbers.get(10).copied()
// Iterate
for num in numbers.iter() {
println!("\(num)")
}
}
HashMaps
HashMap<K, V> stores key-value pairs:
import std.collections.HashMap
fn main() {
var scores: HashMap<String, Int> = HashMap.new()
scores.insert("Blue".toString(), 10)
scores.insert("Red".toString(), 50)
let blue_score = scores.get(&"Blue".toString())
for (team, score) in scores.iter() {
println!("\(team): \(score)")
}
}
Note: Oxide v1.0 uses Rust's collection names directly (Vec, HashMap) rather than providing aliases like Array or Dict. This helps you learn the actual Rust types you'll encounter in the ecosystem.
Type Inference
Oxide has strong type inference. The compiler can usually figure out types from context:
fn main() {
let x = 5 // Int
let y = 3.14 // Float
let z = true // Bool
let s = "hello" // &str
var items = vec![] // Vec<???> - needs annotation or usage
items.push(1) // Now compiler knows it's Vec<Int>
}
When the compiler cannot infer the type, you need to provide an annotation:
fn main() {
// Compiler needs help here
let guess: Int = "42".parse().unwrap()
// Or specify the type in the turbofish
let guess = "42".parse<Int>().unwrap()
}
Summary
Oxide provides intuitive type names that feel familiar to developers from many language backgrounds while mapping directly to Rust's type system:
- Integers:
Int,Int64,UInt,UIntSize, etc. - Floats:
Float,Float32,Float64 - Boolean:
Bool - Character:
char - Tuples:
(T, U, V) - Arrays:
[T; N] - Strings:
&str,Stringwith\(expr)interpolation - Nullable:
T?withnull,??, and!!operators - Collections:
Vec<T>,HashMap<K, V>
Since Oxide types ARE Rust types (just with different names), you get full compatibility with the entire Rust ecosystem.