Geblang language features

A type system that holds at runtime, concurrency that genuinely parallelises, a full object model, pattern matching, and a standard library that covers real work. Here is what each capability looks like in working code.

Type system

Types that hold at runtime

Types are enforced before your program runs and stay enforced while it runs. They are not optional annotations, and generic bindings are available to runtime checks. Values are non-null unless you opt in with ?T; any remains an explicit escape hatch for genuinely dynamic boundaries.

The type-system guide covers collections, unions, intersections, aliases, generics, null safety, and runtime type checks.

import io;

func lookup(int id): ?string {
    if (id == 1) {
        return "Mara";
    }
    return null;
}

let name = lookup(2) ?? "guest";
io.println(name);

Functions

Overloading, pipes, partial application, and decorators

Functions can be overloaded by argument count and type, so one name can accept an int or a string and dispatch to the right body. Decorators wrap behavior rather than sitting inert as metadata, while the pipe operator and partial application keep transformations readable.

See functions and callables for named arguments, closures, variadics, callable objects, and generic functions.

import io;

func describe(int n): string {
    return "int ${n}";
}

func describe(string s): string {
    return "string ${s}";
}

io.println(describe(42));
io.println(describe("hi"));
import io;

func double(int n): int {
    return n * 2;
}

func add(int a, int b): int {
    return a + b;
}

let add10 = add(_, 10);
io.println(5 |> double |> add10);
import io;

@memoize
func fib(int n): int {
    if (n < 2) {
        return n;
    }
    return fib(n - 1) + fib(n - 2);
}

io.println(fib(30));

Concurrency

Goroutines, channels, and real parallelism

Tasks run on goroutines, channels coordinate ownership, and awaiting a task does not turn the language into a single-threaded event loop. This worker consumes typed jobs from a channel while the producer continues independently. The same model powers the HTTP server.

Shared mutable state still needs coordination. Use channels, atomic values, store.Store, or an external database rather than mutating one plain container from several tasks. Read the async and concurrency guide for the complete model.

import io;
import async;
import async.channel as channel;

let jobs = channel.Channel<int>(4);

let worker = async.run(func(): int {
    let total = 0;
    while (true) {
        let job = jobs.recv();
        if (job == null) {
            break;
        }
        total = total + (job as int);
    }
    return total;
});

for (int n = 1; n <= 4; n++) {
    jobs.send(n);
}
jobs.close();

io.println(await worker);

Objects

A full object model

Classes, interfaces, inheritance, and generics compose with operator overloading and immutable types. Operators such as + and == dispatch to methods you define, and a string cast uses your type's own __string.

The classes and interfaces guide covers generic classes, abstract and immutable classes, interface defaults, decorators, and every supported operator method.

import io;

class Vec {
    int x;
    int y;

    func Vec(int x, int y) {
        this.x = x;
        this.y = y;
    }

    func __add(Vec other): Vec {
        return Vec(this.x + other.x, this.y + other.y);
    }

    func __string(): string {
        return "(${this.x}, ${this.y})";
    }
}

io.println(Vec(1, 2) + Vec(3, 4));

Pattern matching

Match values, types, and tagged enums

A match expression evaluates to the matched branch. It handles plain values, types, and tagged enum variants carrying data, so domain-state dispatch stays exhaustive and readable. Here the successful payment identifier is bound directly in its branch.

See control flow and pattern matching for list patterns, alternatives, guards, and type cases.

import io;

enum Payment {
    Paid(string);
    Declined(string);
    Pending;
}

func message(Payment payment): string {
    return match (payment) {
        case Payment.Paid(string id) => "receipt ${id}";
        case Payment.Declined(string reason) => "declined: ${reason}";
        case Payment.Pending => "processing";
    };
}

io.println(message(Payment.Paid("pay_123")));

Failure handling

Typed errors where callers need a choice

Use exceptions for exceptional control flow, nullable ?T for a missing value, and Result<T, E> when failure is part of a function's normal contract. A caller can inspect, transform, or provide a typed fallback without losing the success type.

The standard library also provides Option<T>. The utilities reference documents both types.

import io;
import result;

func divide(int a, int b): result.Result<int, string> {
    if (b == 0) {
        return result.err("division by zero");
    }
    return result.ok(a // b);
}

let answer = divide(20, 4);
io.println(answer.unwrapOr(0));

Toolchain

One toolchain, not ten packages

A static type checker, autoformatter, test runner, language server, VS Code extension, REPL, and step debugger ship together. They share one language implementation rather than relying on separately configured packages.

  • geblang check type-checks and lints without running code.
  • geblang fmt formats source in place.
  • geblang test discovers and runs test suites.
  • geblang build produces one self-contained executable.

Standard library

HTTP, databases, data formats, and more

The standard library covers HTTP clients and servers, WebSockets, SQLite, PostgreSQL, MySQL, Redis, message brokers, crypto and JWT, JSON, YAML, TOML, XML, templating, datetime, math, and more.

Database connections wrap a production connection pool and support parameterized statements. This complete SQLite example needs no separate server; the same API connects to PostgreSQL and MySQL.

import io;
import db;

let conn = db.Connection("sqlite", ":memory:");
defer conn.close();

conn.exec("create table users (id integer, name text)");
conn.exec(
    "insert into users (id, name) values (:id, :name)",
    {"id": 1, "name": "Mara"}
);

let rows = conn.query(
    "select name from users where id = :id",
    {"id": 1}
);
defer rows.close();

io.println(rows.first()["name"]);

For full web applications, Gebweb is the recommended framework. It builds on these primitives with controllers, typed request binding, validation, dependency injection, OpenAPI, auth, caching, and server-rendered views.

Explore Geblang in working programs

Continue with practical examples, install the toolchain, or build a web application with Gebweb.