Type Aliases

Type aliases create a new name for an existing type. They don’t create new types — they’re purely for readability. A Distance that aliases f32 is still an f32 under the hood.

Basic Aliases

Use type to declare an alias:

type Distance:f32
type UserId:i32
type Temperature:f32

fn main() {
    var d:Distance = 100.5
    var id:UserId = 42
    var temp:Temperature = 72.3

    print("distance: {d}\n")
    print("user id: {id}\n")
    print("temperature: {temp}\n")
}

From the compiler’s perspective, Distance IS f32. You can mix them freely:

type Distance:f32

fn main() {
    var d:Distance = 10.5
    var x:f32 = d          // fine, same type
    var total:Distance = d + 5.0    // fine
    print("{total}\n")
}

Making Function Signatures Clearer

The main value of aliases is self-documenting APIs:

type Seconds:f32
type Meters:f32
type MetersPerSecond:f32

fn compute_speed:MetersPerSecond(distance:Meters, time:Seconds) {
    return distance / time
}

fn main() {
    var d:Meters = 100.0
    var t:Seconds = 9.58
    var v = compute_speed(d, t)
    print("speed: {v} m/s\n")
}

Compare to the untyped version:

fn compute_speed:f32(distance:f32, time:f32) {
    return distance / time
}

Both compile to the same code, but the aliased version tells you the units at a glance.

Aliases for Complex Types

Aliases really shine when the underlying type is long or repetitive:

type Callback:fn(i32, i32):i32

fn use_callback(op:Callback, a:i32, b:i32) {
    var result = op(a, b)
    print("result: {result}\n")
}

fn add:i32(a:i32, b:i32) { return a + b }
fn mul:i32(a:i32, b:i32) { return a * b }

fn main() {
    use_callback(add, 5, 3)
    use_callback(mul, 5, 3)
}

Without the alias, you’d write fn(i32, i32):i32 every time you reference that function signature.

Struct Aliases

You can alias struct types to give them a shorter name:

struct Point2D {
    x:f32
    y:f32
}

type Point:Point2D

fn main() {
    var p:Point = Point2D{3.0, 4.0}
    print("({p.x}, {p.y})\n")
}

Both Point and Point2D refer to the same struct, so you can use either name when constructing or passing values.

Aliases in Module APIs

Modules often use aliases to create domain-specific vocabulary without inventing new types. The raylib module does this:

// Inside raylib module
type Vector2:vec2
type Vector3:vec3
type Vector4:vec4

Vector2 is just vec2 under a different name — code reads more naturally in the raylib style, but you still get GX’s operator overloading, swizzle ops, and math helpers for free.

Practical Example: Units of Measurement

type Celsius:f32
type Fahrenheit:f32

fn c_to_f:Fahrenheit(c:Celsius) {
    return c * 9.0 / 5.0 + 32.0
}

fn f_to_c:Celsius(f:Fahrenheit) {
    return (f - 32.0) * 5.0 / 9.0
}

fn main() {
    var freezing:Celsius = 0.0
    var body_temp:Fahrenheit = 98.6

    print("{freezing}C = {c_to_f(freezing)}F\n")
    print("{body_temp}F = {f_to_c(body_temp)}C\n")
}

The alias names document intent. Readers immediately know which values are Celsius and which are Fahrenheit.

Try it — Define type Milliseconds:i64 and type Seconds:f64, then write conversion functions between them in the Playground.


Expert Corner

Aliases are transparent: Unlike Rust’s struct NewType(f32) or Haskell’s newtype, GX aliases don’t create distinct types. A Distance can be assigned to an f32 variable with no cast, and vice versa. This is intentional — the goal is readability, not type-level protection. If you need strict type separation, use a single-field struct:

struct DistanceKm { value:f32 }
struct DistanceMi { value:f32 }

Now the type checker will reject mixing them without explicit conversion.

Generated C code: Type aliases become C typedef declarations. type UserId:i32 becomes typedef int32_t UserId;. The C compiler treats them as the same type, just like GX does.

When aliases help vs hurt: Aliases are great for:

  • Self-documenting parameters (type Seconds:f32)
  • Hiding complex function pointer signatures (type Callback:fn(...)...)
  • Cross-module type mapping (type Vector2:vec2)

Aliases become confusing when:

  • Too many aliases obscure the actual type (what IS a WidgetHandle?)
  • Aliases for primitives add noise without clarity (type Int:i32 — just write i32)
  • Multiple aliases for the same thing create inconsistency

Not the same as generics: An alias assigns a new name to one specific type. It doesn’t let you write code that works across many types. GX has template-style generics (see the templates tutorial) for that use case.

Why types come after the colon: type Name:Type mirrors variable declaration var name:Type. Reading left to right: “introduce name Distance, which is of type f32.” Consistency with the rest of the language is valued over matching C’s typedef oldtype newname which reads backwards.