Compare

Geblang vs Python

Keep the quick, readable style of a Python script and gain types the runtime actually enforces, null safety, CPU-bound work on every core, arrays and data frames in the standard library, and a single executable to deploy.

Geblang and Python at a glance

PythonGeblang
TypesOptional hints, checked by external tools such as mypy or pyright and ignored by the interpreterDeclared types are checked before the program runs and enforced while it runs, including on parsed input
None / nullAny variable can hold NoneTypes are non-null by default; ?string opts in, and ?? supplies a fallback
CPU-bound parallelismIn the standard CPython build the global interpreter lock lets one thread run Python code at a time; Python 3.13 and later also offer an optional free-threaded buildAsync tasks run on goroutines across every core in one process
Data and numericsNumPy, pandas, and SciPy installed from PyPIndarray, dataframe, and stats in the standard library, plus local ONNX inference and FFI to C libraries
DeploymentInterpreter plus a virtual environment of dependencies, or a separate packaging toolgeblang build produces one self-contained executable
ToolingSeparate tools for checking, formatting, and testingChecker, formatter, test runner, REPL, language server, and debugger in one toolchain

Types that hold at run time

Python type hints document intent, but the interpreter does not check them. A list containing a string reaches the function, and the failure happens later, wherever the bad value is first used.

Python

def total(prices: list[int]) -> int:
    return sum(prices)

parsed = [10, 20, "30"]
print(total(parsed))  # TypeError raised inside sum(), not at the call

Geblang

import io;
import math;

func total(list<int> prices): int {
    return math.sum(prices);
}

any parsed = [10, 20, "30"];
try {
    io.println(total(parsed));
} catch (TypeError e) {
    io.println("rejected: " + e.message);
}

Geblang checks the argument against list<int> at the call and names the offending element. Values with a known type are checked before the program starts; values typed any, such as parsed input, are checked when they arrive. See the type system guide.

Null safety by default

In Python, Optional[str] is another hint. A None can still arrive in a field declared str, and the program fails later with AttributeError.

Python

import json
from dataclasses import dataclass
from typing import Optional

@dataclass
class User:
    name: str
    email: Optional[str]

user = User(**json.loads('{"name": null, "email": "[email protected]"}'))
print(user.name.upper())  # AttributeError: 'NoneType' object has no attribute 'upper'

Geblang

import io;
import json;

class User {
    string name;
    ?string email;
}

func contact(User user): string {
    return user.email ?? "no email for ${user.name}";
}

io.println(contact(json.parseAs("{\"name\": \"Lin\", \"email\": null}", User)));

try {
    json.parseAs("{\"name\": null, \"email\": \"[email protected]\"}", User);
} catch (TypeError e) {
    io.println("rejected: " + e.message);
}

A Geblang type never holds null unless it is declared nullable with ?. Nullable values are handled with ?? and optional chaining, and a null arriving in a non-null field is rejected where it enters. See nullability.

CPU-bound work on every core, without the GIL

Splitting CPU-bound work across threads is the obvious move. In the standard CPython build only one thread executes Python code at a time, so the usual answer is multiprocessing, with its separate processes and data copying.

Python

from concurrent.futures import ThreadPoolExecutor

with ThreadPoolExecutor(max_workers=4) as pool:
    counts = list(pool.map(count_primes, [20000] * 4))
# With the GIL, these four threads share one core for Python code.

Geblang

import async;
import io;

func countPrimes(int limit): int {
    int count = 0;
    for (n in 2..limit) {
        bool prime = true;
        int d = 2;
        while (d * d <= n) {
            if (n % d == 0) {
                prime = false;
                break;
            }
            d += 1;
        }
        if (prime) {
            count += 1;
        }
    }
    return count;
}

let tasks = [async.run(func(): int { return countPrimes(20000); }) for _ in 0..<4];
io.println(await async.all(tasks));

Each async.run task is a goroutine, so the four counts run on four cores in one process, and await async.all collects the results in order. Channels and select coordinate longer-running workers. See async and generators.

Data work in the standard library

Python's data stack is a set of third-party packages. Geblang ships the core of it: data frames with typed columns, grouping, joins, and CSV, JSON, and SQL IO; n-dimensional arrays with broadcasting and linear algebra; and probability distributions.

Python

import pandas as pd

sales = pd.DataFrame({
    "region": ["north", "south", "north", "south"],
    "units": [12, 7, 9, 15],
    "price": [2.5, 4.0, 2.5, 4.0],
})
sales["revenue"] = sales["units"] * sales["price"]
print(sales.groupby("region")["revenue"].sum())

Geblang

import dataframe as df;
import io;

let sales = df.fromDict({
    "region": ["north", "south", "north", "south"],
    "units": [12, 7, 9, 15],
    "price": [2.5f, 4.0f, 2.5f, 4.0f],
});

let report = sales
    .withColumn("revenue", df.col("units") * df.col("price"))
    .groupBy("region")
    .agg({"revenue": ["sum"]})
    .sort("region");

io.println(report.toDicts());

For models, the onnx and transformers modules run ONNX models and sentence embeddings locally, and the ffi module calls C libraries in-process, with geblang bind generating a typed wrapper from a manifest.

One file to deploy

Shipping a Python program means shipping an interpreter and a virtual environment, or adding a packaging tool. geblang build bundles your code, its dependencies, and the runtime into a single executable. Copy it to the server and run it.

geblang build --entry app --out dist/app
./dist/app

# Optionally write a Dockerfile next to the binary
geblang build --entry app --out dist/app --docker

Binaries can also be built for another operating system or architecture. See bundling and builds.

Tooling in the box

  • geblang check finds type errors, unknown members, and unreachable code without running anything (static checks).
  • geblang test runs *_test.gb suites with a built-in assertion library (testing).
  • geblang fmt formats source in one canonical style, and geblang repl starts an interactive session.
  • The VS Code extension brings completion, diagnostics, and step debugging through the built-in language server and debug adapter.

Which should you choose?

Choose Geblang when:

  • You want type errors caught before a script runs and enforced while it runs.
  • CPU-bound work should use every core without moving to separate processes.
  • Your data processing fits arrays, data frames, and distributions from the standard library.
  • You want to ship a CLI or service as one file, with no interpreter or virtual environment on the target machine.

Python still makes sense when your work centres on notebooks, model training with PyTorch or scikit-learn, or a library that only exists on PyPI.

Frequently asked questions

Can Geblang replace Python for scripting and backend work?

For command-line tools, automation, web services, and data processing, yes. Geblang reads like a scripting language, enforces its types, runs CPU-bound work on every core, and ships as one executable. Python remains the standard for notebooks and for training machine-learning models.

Does Geblang have an equivalent of NumPy and pandas?

Yes, in the standard library. The ndarray module provides n-dimensional arrays with broadcasting, reductions, and linear algebra, and the dataframe module provides typed columns with filtering, grouping, joins, and CSV, JSON, and SQL input and output. The stats module adds probability distributions.

Can Geblang call C libraries the way Python uses ctypes?

Yes. The ffi module calls C-ABI shared libraries in-process, and geblang bind generates a typed Geblang module from a manifest that describes the library. FFI access is opt-in with the --allow-ffi flag.

How does Geblang run code in parallel without a global interpreter lock?

Each async task runs on a goroutine, so tasks started with async.run execute on all CPU cores inside one process. Results come back through await, and tasks can coordinate through channels and select.

How do I deploy a Geblang program?

Run geblang build to produce one self-contained executable that bundles your code, its dependencies, and the runtime. Copy it to the target machine and run it, with no interpreter or virtual environment to install. Add --docker to also write a Dockerfile.