The Missing Bit

Ten, thinking about types

2025-08-09

I was thinking about what type system I wanted for ten and I think I made up my mind on many parts.

Scalars

I like Zig's arbitrary length integers, so if the implementation permits it (I still don't know how the language will be implemented) I will also do it.

So:

  • signed ints: i8, i16, i32, i64, i128 maybe iN
  • unsigned ints: u8, u16, u32, u64, u128 maybe uN
  • sizes: usize, isize

For floats I guess there aren't a million of them:

  • floats: f16, f32, f64, f128 maybe f80

Of course, booleans: true and false

I was thinking about a char type that would hold a single unicode character, but unicode is very hard so I'll keep this for later or for the stdlib.

The last thing to explore is native vector types. It might be interesting to have language support for them.

Void

I also need a void type for functions that don't return anything useful.

const print = fn(message: []u8) -> void {
    // prints to stdout
};

I think the void keyword is clear enough. It's what functions that do side effects return.

Optional Types

Of course, optionals are explicit.

const maybe_number: ?i32 = 42;
const nothing: ?i32 = null;

if (maybe_number) |value| {
    // value is i32 here
    dbg.print(value);
} else {
    dbg.print("no value");
}

Strings

Now I don't know about strings. Strings are hard. I think the first iterations of the language will just use a byte array with no understanding of it (like what Zig does).

Struct

Structs for record-type data.

const Point = struct {
    x: f32;
    y: f32;
};

const Person = struct {
    name: []u8;
    age: u32;
    // Self-reference MUST be optional to avoid infinite type expansion
    friend: ?Person;
};

// Usage
const p1 = Point{ x = 1.0, y = 2.0 };
const alice = Person{
    name = "Alice",
    age = 30,
    friend = null
};

When a struct references itself (like Person.friend), the reference must be optional (?Person) to prevent infinite type expansion.

I was considering two syntax options: const Point = struct {} versus struct Point {}. At first I thought the special syntax would be simpler, but after thinking about generics and looking at Zig's stdlib, I realized most types need comptime code for validation, optimization, and choosing implementations.

So I'm going with first-class types and functions:

const List = fn(T: comptime Type) -> Type {
    return struct {
        items: []T;

        const add = fn(self: *@This(), item: T) -> void {
            // methods can use T directly
        };
    };
};

const add = fn(a: i32, b: i32) -> i32 { return a + b; };

const IntList = List(i32);  // Generic instantiation
const operations = [_]fn(i32, i32) -> i32{ add, subtract };

This gives maximum composability and makes comptime programming natural. Functions and types are just values you can pass around, compute, and combine. The order dependence is worth it for this level of power.

At the language level, struct fields will use pointer semantics by default. The memory system will use custom allocators where pointers are offsets within memory zones, avoiding traditional pointer dereferencing overhead. However, there should be a way to force direct inclusion of a struct within another struct. An inline keyword would serve this purpose.

const Person = struct {
    name: []u8;
    age: u32;
    friend: Friend;           // pointer by default - stored as reference
    inline address: Address;  // forced inline inclusion - stored as value
};

Now packed struct might also be something required for some usages.

Tuples

I was wondering if I should include tuples. They are basicaly anonymous struct.

I think it will wind up to the implementation and requirements.

But if I do, I guess I would use a syntax like: {a, b, c} as constructor, but it could clash with block delimiter, I will have to study this in more details. But this is a syntax problem.

Array

Real array, so a consinuous block of memory is a requirement for any low level programming and I want to support this.

I guess I will use the same approach as zig. [4]u8 is a 4 bytes array and []u8 is a slice, a slice being a pointer with length.

But I want a higher level memory management (and safety) than zig, so I'll have to think about it.

List

Lists will be implemented using structs in userland. I don't want to include them at the language level, this keeps the language core small. Use the standard library for that.

Union sum type

I think sum types are crucial for a good type system. They let you model "this OR that" relationships cleanly.

I'm planning to use enum for tagged unions (safe) and union for raw unions (unsafe systems programming):

// Tagged union - compiler tracks which variant is active
const Color = enum {
    Red;                    // no payload (implicit void)
    Green;                  // no payload
    Blue;                   // no payload  
    Custom: []u8;           // has payload (color name)
    RGB: struct { r: u8, g: u8, b: u8 };  // struct payload
};

const red = Color.Red;
const custom = Color.Custom("purple");
const rgb = Color.RGB{r = 255, g = 0, b = 128};

// Raw union - you manage the variant yourself, no tag
const Data = union {
    i: i32;
    f: f32;
};

The tagged version is what you'll use 99% of the time. The raw version is for when you need maximum control. I guess an activation system like zig, which is enabled in debug could also work to make them safe.

Other types I need to think about

There are a few more types that will probably be needed:

Error types - I want a special error union system (not regular enums) for functions that can fail:

const parse_number = fn(text: []u8) -> !i32 { ... };  // might fail

Pointers - For systems programming, I'll need explicit pointers:

const ptr: *i32 = &value;           // single pointer
const many_ptr: [*]i32 = &array;    // many-item pointer

But I don't know how to make them fit with the vision I have of the language (memory safety).

Type type - Since I have comptime, I need a type that represents types:

const GenericList = fn(T: comptime Type) -> Type { ... };  // Type is the type of types

Type for the actual type-of-types. Like how i32 has type Type.

I keep lowercase type as a keyword.

Never type - For functions that never return (panic, exit):

const panic = fn(msg: []u8) -> never { ... };

These are more advanced and I can add them later. But error unions are definitely needed early on.

If you wish to comment or discuss this post, just mention me on Bluesky or email me.