VYB_
Open Source · Systems Programming · LLVM

Vyb Programming Language
Systems Programming by Rick Goldberg

Vyb is an open-source systems programming language created by software engineer Rick Goldberg and developed by Aniviza.

Vyb is the open-source systems programming language created by Rick Goldberg and developed by Aniviza LLC. The canonical project site is vyb-lang.org; source and maintained documentation live in the rickenator/Vyb GitHub repository.

Ask DeepWiki

Vyb Programming Guide#

Where the docs live. This README is the project overview. The canonical, maintained reference for every language feature is the docs/refman/PROGRAMMERS_GUIDE.md — language tour, ownership & polymorphism, closure capture forms, async (Future<T> / await / async lambdas), the memory model, and a full standard-library tour, with a core-feature registry (§1.1) that maps each feature to its section and is updated when core features change. Exact per-symbol signatures live in the auto-generated pages under docs/refman/.


1. Introduction#

Welcome to the Vyb programming language version 0.7.3. This overview walks you through what Vyb is and the thinking behind it. Vyb is a systems language built on a simple conviction: you should not have to choose between safety and power. Strong type safety and explicit ownership give your code discipline and predictability, while low-level escape hatches keep raw, machine-near control within reach when you need it. Code runs as fast native executables or through a JIT, so performance stays predictable from first prototype to final program. The syntax is readable and name-first, the abstractions are zero-cost, and the language aims to stay out of your way as your ambitions grow. The rest of this guide shows how those ideas take shape through feature summaries and examples.

1.1 Purpose & Audience#

This guide is intended for systems programmers, language designers, and developers who want:

Whether you're coming from C/C++, Rust, D, or other modern systems languages, you'll find Vyb's template-driven approach familiar yet uniquely powerful.

Here's a comparison of Vyb against several modern systems languages, showing key similarities and differences:

Language Templates / Generics Memory Model Concurrency Syntax Style Unique Feature Comment
Vyb Monomorphized generics everywhere Ownership types (my/our/mild/their), reference counting Async/await Name-first syntax name(params)<Type> ->, aspect/bind polymorphism select expressions, defer, freedom blocks, fail/trap error system Combines zero-cost generics with readable ownership semantics
Rust Monomorphized generics Ownership/borrow checker; optional Arc/Rc async/await, threads, channels C-style braces, macros Zero-cost abstractions; strong compile-time safety No global GC; all memory safety enforced at compile time
D Runtime & compile-time templates GC by default; @nogc for manual alloc/free std.concurrency fibers, threads C-style; mixins Compile-time function execution (CTFE) Blend of high-level features with systems control
C++ Templates & concepts (20+) Manual new/delete; smart pointers (unique_ptr, shared_ptr) Threads, coroutines (co_await) C-style braces Metaprogramming via templates & concepts Extensive ecosystem; highest portability
Nim Generics + macros GC by default; optional manual alloc Async (async/await), threads Python-like indentation Hygienic macros; optional GC or ARC Very concise syntax; strong metaprogramming support
Go Generics (1.18+) GC only Goroutines, channels C-style, minimal CSP-style concurrency Simple, fast compile; built-in tooling

Note: Each language offers a different balance of safety, performance, and ergonomics. Vyb's strength lies in unifying monomorphized generics, ownership types with low-level freedom, and a single toolchain that JITs, compiles AOT, and links native executables — in a terse, self-hostable package.

1.2 What is Vyb?#

Vyb is a statically typed, compiled systems language targeting native code via LLVM. Its key differentiators:

Quick Start#

# Clone and build
git clone https://github.com/rickenator/Vyb.git
cd Vyb
mkdir -p build && cd build && LLVM_DIR=/usr/lib/llvm-18/cmake cmake .. && make -j$(nproc) && cd ..

# Run the full test suite (1061 .vyb tests) with the canonical harness
python3 test/run_tests.py --vyb build/vyb --test-dir test --execute-jit

# Run your first Vyb program
echo 'main()<Int> -> { return 42 }' > hello.vyb
build/vyb hello.vyb  # Returns exit code 42

# Try select expressions with pattern matching
cat > example.vyb << 'EOF'
main()<Int> -> {
    numbers<Vec<Int>> = Vec()
    numbers.push(10)
    numbers.push(20)

    result<Int> = select(numbers.len()) -> {
        0 -> 0,
        2 -> {
            sum<Int> = numbers.get(0) + numbers.get(1)
            pass sum
        },
        ? -> -1
    }
    return result
}
EOF
build/vyb example.vyb  # Returns 30

# Complex return types with auto-serialization
echo 'main()<Int,String> -> { return 42, "Hello!" }' > tuple.vyb
build/vyb tuple.vyb  # Outputs: [42, "Hello!"]

Compilation to Native Code#

Vyb provides a complete compilation pipeline from source to standalone executables:

Building Standalone Executables

# Build executable (simplest form)
build/vyb hello.vyb --build hello
./hello  # Run directly!

# Build with custom name
build/vyb program.vyb --build myapp
./myapp

# Build with optimization
build/vyb program.vyb -b myapp -O3  # Maximum optimization

# Static linking (fully standalone, no runtime dependencies)
build/vyb program.vyb -b myapp --static

# What happens:
# 1. Compiles hello.vyb → hello.o (LLVM object file)
# 2. Compiles runtime/vyb_runtime.c → vyb_runtime.o (C runtime)
# 3. Links with system linker (lld/ld) + CRT files + libc/libm
# 4. Creates executable: hello

# Verify the executable
file myapp
# Output: myapp: ELF 64-bit LSB executable, x86-64, dynamically linked, with debug_info

ldd myapp
# Output:
#   linux-vdso.so.1 (0x00007fff...)
#   libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f...)
#   libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f...)

Projects (vyb.toml)

For multi-file projects, vyb.toml describes the package and every [[bin]], and vyb build compiles and links the whole project in one step:

[package]
name = "myapp"
version = "0.1.0"

[[bin]]
name = "myapp"
path = "src/main.vyb"

[dependencies]
mylib = { path = "../mylib" }
# Scaffold a new project
vyb new myapp          # creates myapp/vyb.toml + myapp/src/main.vyb

# Build every [[bin]] from the manifest (src/ and local path deps are added to
# the module search paths automatically; resolved deps are pinned in vyb.lock)
cd myapp && vyb build
./target/myapp

Compiling to Object Files

# Compile to object file (default -O2 optimization)
build/vyb hello.vyb --compile hello.o

# Specify optimization level
build/vyb program.vyb -c program.o -O0  # No optimization (fast compile)
build/vyb program.vyb -c program.o -O1  # Basic optimization
build/vyb program.vyb -c program.o -O2  # Moderate optimization (default)
build/vyb program.vyb -c program.o -O3  # Aggressive optimization

# Verify object file was created
file hello.o
# Output: hello.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), with debug_info, not stripped

# Inspect symbols in object file
objdump -t hello.o | grep main
# Output: 0000000000000000 g     F .text  000000000000005c main

Cross-Compilation Support

Supports 20+ architectures out of the box:

Complete Compilation Features:

See: doc/MODULE_FFI_BINARY_ROADMAP.md for the compilation pipeline

1.3 Key Concepts & Terminology#

1.4 Import vs Smuggle - Vyb's Unique Module System#

Vyb introduces a distinctive approach to module imports with two keywords that serve different security and trust models:

import - Trusted, Verified Modules:

smuggle - Flexible, External Sources:

Syntax:

import <module::path> [as <alias>] [from "<locator>"] [;]
smuggle <module::path> [from "<locator>"] [as <alias>] [;]

Locator formats for from:

This dual system provides both safety for production code and flexibility for development:

# Production imports - verified and trusted
import std::io::println
import utils::math::calculate from "./utils"

# Development/experimental - flexible but marked as such
smuggle debug::trace from "github.com/dev/tools"
smuggle experimental::feature from "./local/experiments"

main()<Int> -> {
    println("Production ready!")
    trace("Debug info from smuggled module")
    return calculate(42)
}

Declare dependencies in vyb.toml:

[dependencies]
std = "^1.0.0"  # Signed, from Vyb registry
utils = { git = "https://github.com/user/utils" }  # External, smuggled

This unique import/smuggle distinction makes Vyb's module system both secure and flexible, clearly marking the trust level of your dependencies. See doc/FEATURE_STATUS.md for implementation status.

In This Release#

Vyb v0.7.3 (freedom-1.0 series) is a mature systems programming language with native executable generation and a broad core feature set.

Recent Milestones#

These features were completed in the current release cycle and are fully tested:

✅ **Binary Executable Generation **#

✅ **JSON Serialization & Deserialization **#

Native Code Compilation#

Core Language Features#

Advanced Type System#

String Methods#

Comprehensive string manipulation built into the String type:

Method Description Example
.len() Length in bytes s.len()Int
.contains(sub) Substring test s.contains("Vyb")Bool
.starts_with(pre) Prefix test s.starts_with("He")Bool
.ends_with(suf) Suffix test s.ends_with("!")Bool
.to_upper() Uppercase copy s.to_upper()String
.to_lower() Lowercase copy s.to_lower()String
.substring(start, len) Slice s.substring(0, 5)String
.char_at(i) ASCII code at index s.char_at(1)Int
.trim() / .strip() Strip surrounding whitespace s.trim()String
.replace(old, new) Replace all occurrences s.replace("a", "b")String
.split(sep) Split into Vec<String> of its parts s.split(",")Vec<String>
.format(args...) Substitute {} placeholders in sequence s.format(42)String
String::from_bytes(ptr, len) Construct from bytes String::from_bytes(p, n)

String concatenation with + auto-converts non-String operands:

id<Int> = 42
msg<String> = "User ID: " + id    // "User ID: 42"

Splitting returns a fresh Vec<String> of the parts between each occurrence of the separator. An empty separator yields a single-element Vec holding the whole string; leading, trailing, and consecutive separators produce empty parts.

csv<String> = "alpha,beta,gamma"
parts<Vec<String>> = csv.split(",")   // parts.len() == 3; parts.get(1) == "beta"

Formatting substitutes each {} placeholder with the string form of the corresponding argument in order. Arguments of any serializable type (String, Int, Float, Bool, ...) are converted automatically; placeholders beyond the supplied arguments are emitted verbatim.

tmpl<String> = "User {} has {} badge(s)."
msg<String> = tmpl.format("Vyb", 3)   // "User Vyb has 3 badge(s)."

Method calls on value receivers — every String method works not only on a named variable but on any String value: a literal, a function/expression result, or a struct/array field. Only the receiver itself still needs to be a String; no intermediate variable is required.

upper<String> = "hello".to_upper()                        // "HELLO"
head<String> = full_name().substring(0, 5)                // first 5 chars
count<Int> = get_csv().split(",").len()                   // number of fields

Async Programming & Debugging#

Vyb parses and compiles async functions returning Future<T> and the await expression. An async fn(params...)<Future<T>> (T = Int, Float, Bool, String, or Void) starts a task on the stdlib's multi-threaded executor when called — its scalar arguments are snapshotted into a closing environment, and a String result travels back as a heap slot that await hands to the consumer as an owned transfer (a Float travels as its bit pattern and a Bool as 0/1 in the task's result slot). await parks the caller until the task completes (from main) or suspends the current fiber (so a worker can await a child task), including as a bare statement (await f) for Future<Void>. The future is returned by value as a real struct. Owned parameters are snapshotted into the task env: a String keeps its buffer with a retain (+1), an our<T> takes a shared-control-block retain (+1), and a Vec<T> is deep-copied so the env owns an independent copy; the env's per-layout dtor releases each one on cleanup (reclaiming Vec<String> element references too), so they safely outlive the caller's scope while the worker runs.

Async Function Syntax

// Async function returning Future<Int>
async compute_value()<Future<Int>> -> {
    println("Computing...")
    return 42
}

// Async function that awaits another async function
async process_data()<Future<String>> -> {
    value<Int> = await compute_value()  // runs the pending task to completion
    println("Got value")
    return "processed"
}

Key Features

Usage Example

main()<Void> -> {
    // Call async functions; each returns a Future<T>
    future1<Future<Int>> = compute_value()
    future2<Future<String>> = process_data()
    println("Tasks initiated")
    return
}

See: test/async/async_simple.vyb for a working example.

See also:

Concurrency Modules (stdlib)#

The standard library ships a layered concurrency story, all built on the external pthread runtime (no raw C ABI in user code):

import asyncs
h1<Int> = async_spawn(|| -> { async_sleep_ms(20); return 10 }) else 0
h2<Int> = async_spawn(|| -> { async_sleep_ms(20); return 32 }) else 0
v1 = async_await(h1)   // 10, ~20ms total for both
v2 = async_await(h2)   // 32
async_detach(h1)       // reclaim the finished task early
async_run_all()        // flush + reclaim

async_spawn/async_accept return Int? and are absent on failure; async_poll returns Int? (absent while a fiber is still running, present holding its result, which may legitimately be -1) — no sentinel overloads.

Replaces the old C++ AsyncRuntime executor that has been retired; concurrency now lives on the external pthread runtime and this cooperative executor.

Introspection System#

Vyb features runtime type introspection for self-aware programs:

Type Reflection Operators

// Get runtime type hash — use for equality comparison
x<Int> = 42
same<Bool> = (typeof(x) == typeof(100))   // true — both Int

// Get type name as String
name<String> = typename(x)                // "Int"

// Works on any expression
same_expr<Bool> = (typeof(x + 5) == typeof(x * 2))   // true

Key Features

Usage Examples

// Type comparison
x<Int> = 42
y<Float> = 3.14
if (typeof(x) != typeof(y)) {
    println("different types")
}

See: test/introspection/ for working examples and doc/INTROSPECTION_DESIGN.md for design philosophy

Aspect System#

Philosophy: Vyb uses aspects + structs instead of classes and inheritance. This provides polymorphism, code reuse, and composition without the complexity and pitfalls of OOP class hierarchies. See doc/TRAIT_SYSTEM_DESIGN.md for detailed design and rationale.

Simple aspect receivers use self; the compiler treats it as the bound Self type. Use explicit receiver types like self<their<Self>> only when ownership mode matters.

Complete Aspect Example

# Define an aspect (interface): method signatures only, no state
aspect Display {
    show(self)<String> ->
    format(self, prefix<String>)<String> ->
}

# A concrete struct - just data
struct Point {
    x<Int>,
    y<Int>
}

# Bind the aspect to the type, implementing the methods against the data
bind Display -> Point {
    show(self)<String> -> {
        return "Point at (" + self.x + ", " + self.y + ")"
    }

    format(self, prefix<String>)<String> -> {
        return prefix + ": (" + self.x + "," + self.y + ")"
    }
}

# Another type can bind the same aspect
struct Rectangle {
    width<Int>,
    height<Int>
}

bind Display -> Rectangle {
    show(self)<String> -> {
        return "Rectangle " + self.width + "x" + self.height
    }

    format(self, prefix<String>)<String> -> {
        return prefix + ": " + self.width + "x" + self.height
    }
}

main()<Int> -> {
    point<Point> = Point { x = 10, y = 20 }
    rect<Rectangle> = Rectangle { width = 5, height = 7 }

    # Dot-call the bound aspect methods - they read the struct's own fields
    println(point.show())         # "Point at (10, 20)"
    println(point.format("Item")) # "Item: (10,20)"
    println(rect.show())          # "Rectangle 5x7"
    println(rect.format("Item"))  # "Item: 5x7"

    return 0
}

Why Aspects > Classes:

See: doc/ASPECT_SYSTEM_DESIGN.md for complete specification and test/aspect/ for working examples

Core Contracts (core::aspects)

The standard library ships six canonical contract aspects — Display, Debug, Clone, Equatable, Hashable, and Comparable (with Comparable : Equatable) — bound to the primitive scalar types (Int/Float/Bool/Char/String). core::aspects is auto-imported into non-stdlib modules (opt out with no_core()), so contract methods are available on built-in scalars with no import. They bind to user structs the same way, and drive the generic call sites for the stdlib collections and Iterator protocol:

// Hash/comparison-backed collections (import collections) resolve the
// Hashable/Comparable bounds on generic keys automatically.
nums<BTreeMap<Int, String>> = BTreeMap<Int, String>()

// Unqualified bounded-type-parameter dispatch resolves through the bound.
show_all<T<Display>>(item<T>)<Void> -> {
    println(item.display())   // resolves via the bound Display aspect
}

Primitive Types

Signed Integers:

Type Description Size Range/Notes Example
Int / Int64 Signed integer (default) 64-bit -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 x<Int> = 42
Int32 32-bit signed integer 32-bit -2,147,483,648 to 2,147,483,647 count<Int32> = 100
Int16 16-bit signed integer 16-bit -32,768 to 32,767 small<Int16> = 1000
Int8 8-bit signed integer 8-bit -128 to 127 byte<Int8> = 127

Unsigned Integers:

Type Description Size Range/Notes Example
UInt64 64-bit unsigned integer 64-bit 0 to 18,446,744,073,709,551,615 max<UInt64> = 18446744073709551615u
UInt32 32-bit unsigned integer 32-bit 0 to 4,294,967,295 count<UInt32> = 100
UInt16 16-bit unsigned integer 16-bit 0 to 65,535 port<UInt16> = 8080
UInt8 8-bit unsigned integer 8-bit 0 to 255 byte<UInt8> = 255

Floating Point:

Type Description Size Precision Example
Float / Float64 Double precision (default) 64-bit IEEE 754 double (~15-17 digits) pi<Float> = 3.14159
Float32 Single precision 32-bit IEEE 754 single (~6-9 digits) ratio<Float32> = 1.5

Character Types:

Type Description Size Range/Notes Example
Char UTF-8 code unit 8-bit Single byte (0-255) ch<Char> = 65 # 'A'
Rune Unicode code point 32-bit Full Unicode range (U+0000 to U+10FFFF) emoji<Rune> = 128512 # 😀

Other Types:

Type Description Size Range/Notes Example
Bool Boolean 1-bit true or false flag<Bool> = true
String UTF-8 string Variable Heap-allocated fat pointer { ptr, len } name<String> = "Alice"
Bytes Raw binary data Variable Fat pointer for byte sequences { ptr, len } Future byte literals
Void No value 0-bit Used for functions that don't return print()<Void> -> { ... }

Collection Types

Type Description Mutability Example
[T; N] Fixed-size array Mutable elements nums<[Int; 5]> = [1, 2, 3, 4, 5]
Vec<T> Dynamic array Mutable elements items<Vec<String>> = Vec()
HashMap<K,V> Hash-bucket map (import collections) Mutable m<HashMap<String, Int>> = HashMap<String, Int>()
HashSet<K> Distinct-key set (import collections) Mutable s<HashSet<String>> = HashSet<String>()
BTreeMap<K,V> Key-ordered map (import collections) Mutable b<BTreeMap<Int, String>> = BTreeMap<Int, String>()
Tuple<T,U,...> Heterogeneous tuple (variadic) Immutable data<Tuple<Int,String,Bool>>

Ownership Types

Type Description Use Case Example
my<T> Unique ownership Single owner, move semantics data<my<Person>> = my(Person{...})
our<T> Shared ownership Reference counting config<our<Settings>> = our(Settings{...})
their<T> Borrowed reference Non-owning access ref<their<Data>> = view(owner)
loc<T> Raw pointer Freedom operations only ptr<loc<Int>> = loc(variable)

Canonical Ownership Syntax#

Vyb features unified canonical syntax for ownership and borrowing operations:

Type Annotations

# In variable declarations and function signatures
data<my<String>>     # Unique ownership type
shared<our<Config>>  # Shared ownership type
view<their<Data>>    # Borrowed reference type

Value Construction

# Create owned values with my() and our() constructors
unique<my<String>>   = my("owned string")
shared<our<Config>>  = our(Config::new())
result<my<Data>>     = my(compute_data())

Borrowing Operations

# Create temporary references with view/borrow/soft functions
readonly<their<String const>> = view(data)     # Immutable borrow
writable<their<String>>       = borrow(data)   # Mutable borrow
weak<mild<Node>>              = soft(shared)   # Weak reference
length<Int>                   = view(data).len()
borrow(data).clear()

Legacy Syntax Migration

All legacy make_my(), make_our(), and prefix view expr / borrow expr syntax can be migrated to canonical syntax using the migration tool:

# Scan for legacy syntax
python3 migrate_syntax.py --scan --directory .

# Apply migrations with backup
python3 migrate_syntax.py --migrate --directory . --backup --report

Migration Results: ✅ 346 syntax updates applied across 22 files, ensuring consistent canonical syntax throughout the entire codebase.

Memory Management#

Weak References with mild

Vyb introduces mild<T> - weak references that solve circular reference problems without preventing cleanup:

Why mild?

Key Methods:

# grab() -> our<T>?
# Attempts to upgrade mild reference to strong reference
# Returns nil if target has been destroyed
parent_strong<our<TreeNode>> = node.parent.grab()

# released() -> Bool
# Check if target object has been freed
# Returns true if destroyed, false if still alive
is_alive<Bool> = !node.parent.released()

Creating Mild References:

# Use soft() to create mild<T> from our<T>
shared_data<our<Config>> = our(Config::new())
shadow<mild<Config>> = soft(shared_data)  # Create mild reference

Example: Tree with Parent Pointers

struct TreeNode {
    value<Int>,
    children<Vec<our<TreeNode>>>,  # Strong refs to children
    parent<mild<TreeNode>>          # Mild ref to parent (breaks cycle)
}

create_child(parent<our<TreeNode>>, value<Int>)<our<TreeNode>> -> {
    return our(TreeNode {
        value: value,
        children: Vec(),
        parent: soft(parent)  # Create weak reference with soft()
    })
}

access_parent(node<our<TreeNode>>)<Int> -> {
    # Safely access parent through weak reference
    if (parent<our<TreeNode>> = node.parent.grab()) {
        return parent.value  # Success - parent still alive
    } else {
        return -1  # Parent was destroyed
    }
}

See: doc/OWNERSHIP_MILD.md for complete documentation and test/ownership/mild_test.vyb for examples

Developer Experience#

Native Bridge / FFI#

Vyb's native bridge to C libraries is shipped and production-usable:

Layer Status Description
Vyb Runtime ✅ Complete runtime/vyb_runtime.c — ownership-tracked Vec/String/Math/I/O (our<T> is reference-counted) all linked automatically
LLVM intrinsics ✅ Complete malloc, free, memset, printf-style print all registered in JIT
C stdlib (math) ✅ Complete libm linked; sqrt, sin, cos, pow, etc. all working
C stdlib (I/O) ✅ Complete libc linked; I/O built on top of C runtime
extern "C" blocks ✅ Complete Declare C functions callable from Vyb (incl. variadic printf(fmt, ...)); maps loc<T>T* and C aliases (CInt, CString, CSize, …)
#[repr(C)] structs ✅ Complete Force C-compatible struct layout for FFI
vyb bindgen (MVP + libclang) vyb bindgen <header.h> emits importable extern/repr(C)/enum bindings from a C subset; vyb bindgen <header.h> --full adds the libclang full-preprocessor backend (#include expansion, conditional evaluation, expression + function-like macros)

With extern "C" and the typed C aliases, the POSIX API is reachable with a thin Vyb wrapper, enabling networking, file I/O, threading, and more without language-level changes. FFI callbacks arrive as bare loc<fn> function pointer parameters (see test/ffi/callback_fnptr.vyb), and variadic C functions work directly (printf("%s", s) accepts a Vyb String). See doc/FFI_DESIGN.md for the complete design.

Language Overview#

Vyb (freedom-1.0 series) is a mature, actively developed systems programming language with name-first syntax, a sized type system (Int8–Int64, UInt8–UInt64, Float32/Float64, Char, Rune, Bytes), compile-time monomorphized generics, aspect/bind polymorphism, pattern matching, Vec<T>, and comprehensive collection support. The core language is stable and well tested.

Language Features Showcase#

// Complete language demonstration showing all major features

// Modern struct syntax with typed fields
struct Person {
    name<String>,
    age<Int>,
    scores<Vec<Int>>
}

// Define aspect for gradeable entities
aspect Gradeable {
    letter_grade(self)<String> -> { }
    is_passing(self)<Bool> -> { }
}

// Bind aspect to Person using match in the implementation
bind Gradeable -> Person {
    letter_grade(self)<String> -> {
        // Calculate average score
        total<Int> = 0
        i<Int> = 0
        while (i < self.scores.len()) {
            total = total + self.scores.get(i)
            i = i + 1
        }
        avg<Int> = total / self.scores.len()

        // Match statement with comparison patterns for grade ranges
        match (avg) {
            >= 90 -> return "A",
            >= 80 -> return "B",
            >= 70 -> return "C",
            >= 60 -> return "D",
            ? -> return "F"
        }
    }

    is_passing(self)<Bool> -> {
        grade<String> = self.letter_grade()
        match (grade) {
            "F" -> return false,
            ? -> return true
        }
    }
}

// select expression — Vyb-original: pattern matching that returns a value
get_grade_description(score<Int>)<String> -> {
    grade<String> = select(score) -> {
        >= 90 -> "Excellent",
        >= 80 -> "Good",
        >= 70 -> "Satisfactory",
        >= 60 -> "Passing",
        ? -> "Needs Improvement"
    }
    println(grade)
    return grade
}

// Generic function using aspect bounds
print_student_status<T<Gradeable>>(student<T>)<Void> -> {
    grade<String> = student.letter_grade()
    passing<Bool> = student.is_passing()
    println("Grade: " + grade)
    if (passing) {
        println("Status: PASSING")
    } else {
        println("Status: FAILING")
    }
}

// Resizable collections with full method support
create_person(name<String>, age<Int>)<Person> -> {
    person<Person> = Person {
        name = name,
        age = age,
        scores = Vec()
    }
    person.scores.push(85)
    person.scores.push(92)
    person.scores.push(78)
    return person
}

main()<Int> -> {
    student<Person> = create_person("Alice", 20)

    // Defer for guaranteed cleanup
    defer println("done")

    // Type introspection
    same<Bool> = (typeof(student.age) == typeof(42))

    // Call aspect method through generic function
    print_student_status(student)

    // Use select with comparison patterns
    description<String> = get_grade_description(85)

    return 0
}

Memory Safety & Freedom Operations#

Vyb's design philosophy: FREEDOM over restrictions. The language provides compiler-managed ownership by default, but empowers programmers with low-level control when needed:

// Ownership type syntax (runtime enforcement is in place for our/mild/their;
// full my<T> move tracking is still in progress)
restricted_memory_example()<Int> -> {
    owned<my<String>> = my("unique data")         // Unique ownership
    shared<our<String>> = our("shared data")      // Shared, ref-counted
    another_ref<our<String>> = shared             // Reference count +1
    view_ref<their<String const>> = view(shared)  // Immutable borrow
    return 42
}

// freedom block — low-level pointer access
freedom_memory_example()<Int> -> {
    x<Int> = 42
    freedom {
        p<loc<Int>> = loc(x)       // Get pointer to x
        println("in freedom block")
    }
    return x
}

FREEDOM Code Guidelines:

  1. Minimize the scope of freedom blocks
  2. Document all invariants and assumptions
  3. Validate pointers before dereferencing
  4. Encapsulate freedom operations behind restricted abstractions
  5. Test freedom code extensively

Syntax and Literals#

Vyb uses indentation-sensitive syntax with optional braces and semicolons. Whitespace defines blocks, so consistent indentation is key. The unified name<Type> syntax provides consistency across all language constructs.

Literal Forms

// Integer literals
x<Int> = 42                       // 64-bit signed integer (default)
large<Int> = -9223372036854775808  // Full 64-bit range
red<Int> = 0xFF                   // Hexadecimal (base 16)
mask<Int> = 0b11001110            // Binary (base 2)
ud<UInt32> = 255u                 // Unsigned literal suffix
maxU<UInt64> = 18446744073709551615u  // Full UInt64 range (2^64 - 1)

// Sized integers use the same literal forms (0x80 means 128 as a byte).
b<UInt8> = 0x80

// Floating point literals
pi<Float> = 3.14159               // 64-bit IEEE 754 double precision
e<Float> = 2.71828

// Boolean literals
flag<Bool> = true
active<Bool> = false

// String literals
name<String> = "Alice"            // UTF-8 string
greeting<String> = "Hello\nVyb"  // Escape sequences supported
empty<String> = ""

// Array literals (fixed-size)
numbers<[Int; 5]> = [1, 2, 3, 4, 5]
floats<[Float; 3]> = [1.0, 2.5, -3.14]

// Dynamic arrays (Vec<T>)
items<Vec<Int>> = Vec()

// Immutable binding
PI<Float const> = 3.14159

Syntax Examples

// Variable declarations with unified syntax
x<Int> = 42                  // Mutable variable
PI<Float const> = 3.14159    // Immutable constant (name<Type const>)

// Function declarations — name comes first
add(a<Int>, b<Int>)<Int> -> a + b

// Struct definitions with typed fields
struct Point {
    x<Float>,
    y<Float>
}

// Pattern matching with comparison operators and wildcard ?
process_value(val<Int>)<String> -> {
    match (val) {
        0 -> "zero",
        >= 1 -> "positive",
        ? -> "negative"
    }
}

// select — pattern matching that returns a value (Vyb-original)
classify(score<Int>)<String> -> {
    return select(score) -> {
        >= 90 -> "A",
        >= 80 -> "B",
        ? -> "C-or-below"
    }
}

// defer — LIFO scope-exit cleanup
open_and_process()<Int> -> {
    defer println("cleanup")
    println("working")
    return 0
}

Type System Features

Current Primitive Types:

Current Collection Types:

HashMap, HashSet, and BTreeMap ship in stdlib/collections and are imported with import collections. Vec<T> itself layers Vyb-written higher-order helpers there — map / filter / reduce, sorted / reversed / find, min / max, and sort_in_place — taking non-capturing lambdas (fn) where a mapping function is needed.

Ownership Types: my<T> (unique), our<T> (shared), their<T> (borrowed), mild<T> (mild reference), loc<T> (freedom raw pointer)

Type Aliasing: All numeric types support multiple naming conventions:

Basic Syntax#

Vyb uses clean, expressive syntax with flexible parameter syntax:

# Functions support both standard and shorthand parameter syntax

# Standard syntax: explicit parameter<Type> or parameter<Type const>
add_standard(a<Int>, b<Int const>)<Int> -> {
    return a + b
}

# Shorthand syntax: Type directly (implicitly mutable)
add_shorthand(Int a, Int b)<Int> -> {
    return a + b
}

# Mixed syntax: combining both forms in same function
mixed_params(x<Int>, Int y, const Int z)<Int> -> {
    return x + y + z
}

# Complex return types with auto-serialization
get_data()<Int, String, Bool> -> {
    return 42, "result", true
}
# When called from main(), outputs: {"Int":42,"String":"result","Bool":true}

Function Parameters#

Vyb supports two parameter syntax styles for maximum flexibility:

# Standard syntax: explicit mutability
format_standard(prefix<String>, value<Int const>)<String> -> {
    return prefix + String::from_int(value)
}

# Shorthand syntax: type-first (more concise)
format_shorthand(String prefix, const Int value)<String> -> {
    return prefix + String::from_int(value)
}

# Both produce identical behavior and LLVM IR

Variables and Types#

# Variables with type inference
x = 42          # Int
name = "Alice"  # String
flag = true     # Bool

# Explicit typing
count<Int> = 0
message<String> = "Hello"

# Immutable values (const is a type modifier)
PI<Float const> = 3.14159
MAX_SIZE<Int const> = 1000

Structs and Data#

# Define a struct with modern syntax
struct Point {
    x<Int>,
    y<Int>
}

# Create and use with field access
main()<Int> -> {
    p<Point> = Point { x = 10, y = 20 }
    return p.x + p.y  # Returns 30
}

Modules & Import System#

Vyb's unique dual import system provides both security and flexibility:

# Trusted imports from verified sources
import std::collections::Vec
import utils::math::calculate
import std::io::println

# Flexible imports for development and experimentation
smuggle debug::trace from "github.com/dev/tools"
smuggle experimental::parser from "./local/experiments"

# Use imported symbols
main()<Int> -> {
    numbers<Vec<Int>> = Vec()
    numbers.push(calculate(10))

    println(numbers)  # From trusted std::io
    trace("Debug info")  # From smuggled debug module

    return numbers.get(0)
}

Import Types:

Declare dependencies in vyb.toml:

[dependencies]
std = "^1.0.0"                    # Verified registry package
utils = { git = "https://..." }    # External Git repository
local_tools = { path = "../tools" } # Local development dependency

Bundle & Sharing System: Fine-grained visibility control with bundles (bundle(...) and share(...) directives; see test/modules/import_bundle_allowed.vyb and test/modules/import_private_rejected.vyb):

# Declare module bundles
bundle(math, math.Core)

# Control symbol visibility
share(all) public_function()<Int> -> { ... }      # Public to all
share(math.UI) ui_helper()<Int> -> { ... }        # Only to math.UI bundle
internal_helper()<Int> -> { ... }                 # Private to this file

Arrays and Collections#

# Resizable vectors with full method support
main()<Int> -> {
    numbers<Vec<Int>> = Vec()
    numbers.push(10)
    numbers.push(20)
    numbers.push(30)

    println(numbers)  # Outputs: { "type": "Vec<Int>", "address": "0x..." }

    length<Int> = numbers.len()    # Gets 3
    first<Int> = numbers.get(0)    # Gets 10
    last<Int> = numbers.pop()      # Gets and removes 30

    return first + last  # Returns 40
}

# Vec iteration - parentheses mandatory
iterate_example()<Int> -> {
    items<Vec<Int>> = Vec()
    items.push(1)
    items.push(2)
    items.push(3)

    sum<Int> = 0
    for (item in items) {
        sum = sum + item
    }

    return sum  # Returns 6
}

# Fixed-size arrays also supported
array_example()<Int> -> {
    fixed<[Int; 3]> = [1, 2, 3]
    return fixed[0]  # Array indexing
}

Keyed & Set Collections — imported with import collections:

import collections

main()<Int> -> {
    # HashMap<K, V> — hash-bucket index, auto-grows, average O(1) lookup
    scores<HashMap<String, Int>> = HashMap<String, Int>()
    scores.put("alpha", 1)
    scores.put("beta", 2)
    scores.put("alpha", 99)               # overwrites the existing key
    n_scores<Int> = scores.size()         # 2
    score<Int?> = scores.get("beta")                 # Int? (2 when present, absent if missing)
    has_alpha<Bool> = scores.contains_key("alpha")   # true

    # HashSet<K> — distinct keys
    tags<HashSet<String>> = HashSet<String>()
    tags.insert("x")
    tags.insert("y")
    again<Bool> = tags.insert("x")        # false (already present)

    # BTreeMap<K, V> — keys iterate in sorted (Comparable) order
    ordered<BTreeMap<Int, String>> = BTreeMap<Int, String>()
    ordered.put(3, "three")
    ordered.put(1, "one")
    ordered.put(2, "two")                 # iteration order 1, 2, 3
    return 0
}

Higher-Order Vec Combinators — non-capturing lambdas, also import collections:

import collections

main()<Int> -> {
    v<Vec<Int>> = Vec()
    v.push(3); v.push(9); v.push(5)

    doubled<Vec<Int>> = v.map(|n<Int>| -> n * 2)          # fresh [6, 18, 10]
    odds<Vec<Int>>    = v.filter(|n<Int>| -> n % 2 == 1)  # fresh [3, 9, 5]
    total<Int>        = v.reduce(0, |a<Int>, b<Int>| -> a + b)  # 17
    v.sort_in_place()                                     # in place: [3, 5, 9]
    smallest<Int> = v.first()                             # 3
    return 0
}

Control Flow#

# Conditional expressions with modern syntax
check_sign(x<Int>)<String> -> {
    if (x > 0) {
        return "positive"
    } else if (x < 0) {
        return "negative"
    } else {
        return "zero"
    }
}

# While loops with break/continue
factorial(n<Int>)<Int> -> {
    result<Int> = 1
    i<Int> = 1
    while (i <= n) {
        if (i == 0) {
            continue
        }
        result = result * i
        i = i + 1
        if (result > 1000) {
            break
        }
    }
    return result
}

# Vec iteration with for loops (parentheses mandatory)
sum_vector(numbers<Vec<Int>>)<Int> -> {
    total<Int> = 0
    for (num in numbers) {
        if (num < 0) {
            continue  # Skip negative numbers
        }
        total = total + num
        if (total > 100) {
            break  # Stop if sum exceeds 100
        }
    }
    return total
}

# For loops over the Iterator protocol: any non-identifier iterable
# expression desugars onto `.next()` (e.g. `v.iter()`, or a custom
# `bind Iterator` type). `break`/`continue` work and re-enter `next()`.
# An optional step (`for (x in v.iter(), 2)`) yields every 2nd element.
# The range (`0..n`) and plain-Vec (`for (x in vec)`) forms above are unchanged.
for (x in numbers.iter()) {
    total = total + x
}

# Range-based for loops (inclusive)
count_to_ten()<Int> -> {
    sum<Int> = 0
    for (i in 0..10) {
        sum = sum + i
    }
    return sum  # Returns 55
}

# Pattern matching with match statements
describe_number(x<Int>)<String> -> {
    match (x) {
        0 -> return "zero",
        1 -> return "one",
        42 -> return "the answer",
        ? -> return "some number"
    }
}

# Match arms can execute any statement, including return
# The match statement itself doesn't return a value, but pattern arms can
# return from the enclosing function when the type matches

# Select expressions - elegant pattern matching that returns a value
compute_bonus(level<Int>)<Int> -> {
    # Simple select with naked expressions (auto-return)
    multiplier<Int> = select(level) -> {
        1 -> 10,
        2 -> 20,
        3 -> 30,
        ? -> 5
    };
    return multiplier * 100
}

# Select with complex blocks and explicit pass keyword
process_request(code<Int>)<Int> -> {
    result<Int> = select(code) -> {
        1 -> {
            temp<Int> = 10;
            pass temp
        },
        2 -> {
            x<Int> = 20;
            pass x
        },
        3 -> {
            msg<Int> = 300;
            println(msg);
            pass msg
        },
        ? -> {
            pass 0
        }
    };
    return result
}

# Select expressions combine the best of match and expression values:
# - Naked expressions (1 -> 10) auto-return without 'pass'
# - Complex blocks ({ ... }) use 'pass' keyword to return value
# - 'pass' returns from the block, NOT the enclosing function
# - Type inference from first case ensures type safety
# - Wildcard '?' provides default case handling

select may also be used as a bare statement for side effects only — arms need not yield a value, and a block arm without pass simply falls through to the following statements. And because a select arm body is itself an expression, a select may appear as an arm of an enclosing select for per-branch sub-selection (see test/new_features/test_select_statement.vyb and test/new_features/test_select_nested.vyb).

Comparison Patterns (Range Matching)#

Vyb's pattern matching supports comparison patterns for elegant range-based matching with compile-time safety:

# Comparison operators in patterns: >, <, >=, <=, ==, !=

# Select expression - returns a value directly
classify_score(score<Int>)<String> -> {
    return select(score) -> {
        >= 90 -> "A",
        >= 80 -> "B",
        >= 70 -> "C",
        >= 60 -> "D",
        ? -> "F"
    }
}

# Match statement - returns from enclosing function
classify_score_match(score<Int>)<String> -> {
    match (score) {
        >= 90 -> return "A",
        >= 80 -> return "B",
        >= 70 -> return "C",
        >= 60 -> return "D",
        ? -> return "F"
    }
}

# Tax rate calculation with select
compute_tax_rate(income<Int>)<Float> -> {
    return select(income) -> {
        < 10000 -> 0.0,
        < 50000 -> 0.15,
        < 100000 -> 0.25,
        ? -> 0.35
    }
}

# Comparison patterns work with floats and integers
categorize_temperature(temp<Float>)<String> -> {
    match (temp) {
        < 0.0 -> return "freezing",
        < 10.0 -> return "cold",
        < 20.0 -> return "mild",
        < 30.0 -> return "warm",
        ? -> return "hot"
    }
}

# Unreachable pattern detection - compiler ERROR
invalid_patterns(x<Int>)<String> -> {
    match (x) {
        > 10 -> return "big",
        > 5 -> return "medium",  # ERROR: unreachable (subsumed by > 10)
        ? -> return "small"
    }
}

# Other unreachable pattern errors:
# - Wildcard before end: match (x) { ? -> "any", 5 -> "five" }  # ERROR
# - Duplicate patterns: match (x) { > 10 -> "a", > 10 -> "b" }  # ERROR
# - Overlapping ranges: match (x) { > 5 -> "a", >= 3 -> "b" }  # ERROR

Comparison Pattern Rules:

Match vs Select with Comparison Patterns:

Set Patterns ({…}) in Select Arms#

An arm's pattern may be a brace-delimited set of several discrete values, so callers don't repeat a result per value. The arm matches when the target equals any element; first-match-wins ordering is preserved.

classify(x<Int>)<String> -> {
    return select(x) -> {
        {1, 3, 5, 7, 9} -> "odd",
        {2, 4, 6, 8}    -> "even",
        ?               -> "out of bounds"
    }
}

enum Color { Red, Green, Blue }
name(c<Color>)<String> -> {
    return select(c) -> {
        {Red, Blue}  -> "primary",
        {Green}      -> "green",
        ?            -> "unknown"
    }
}

Set elements may be literals (Int/Float/String/Bool/null) or bare enum-variant names (North, Shape::Square). These are enforced at compile time:

Variadic Tuples#

Vyb supports fully variadic tuple types that can hold any number of heterogeneous elements (1 to N):

# Single-element tuples
get_single()<Tuple<Int>> -> {
    return 42  # Automatically wrapped in tuple struct
}

# Two-element tuples (dual syntax)
get_pair_inline()<Int, String> -> {
    return 10, "hello"  # Inline syntax
}

get_pair_generic()<Tuple<Int, String>> -> {
    return 20, "world"  # Generic syntax (equivalent)
}

# Multi-element tuples with mixed types
get_data()<Tuple<String, Int, Bool, Float>> -> {
    return "status", 200, true, 3.14
}

# Seven-element tuple demonstrating variadic support
get_complex()<Tuple<Int, Int, Bool, String, Int, Bool, Int>> -> {
    return 1, 2, true, "test", 3, false, 4
}

# Tuples work seamlessly with complex types
get_mixed()<Tuple<String, Vec<Int>, Bool>> -> {
    numbers<Vec<Int>> = Vec()
    numbers.push(1)
    numbers.push(2)
    return "data", numbers, true
}

main()<Int> -> {
    # All tuple syntaxes work identically
    single<Tuple<Int>> = get_single()
    pair<Tuple<Int,String>> = get_pair_inline()
    data<Tuple<String,Int,Bool,Float>> = get_data()

    return 0
}

Tuple Features:

String Theory#

Vyb's String type is a production-ready fat pointer implementation with comprehensive method support and natural literal syntax. Unlike C's null-terminated strings or C++'s heavyweight std::string, Vyb Strings combine the best of both worlds: efficient representation with modern conveniences.

String Structure#

# Internally, String is a fat pointer struct:
struct String {
    ptr: *i8,    # Pointer to null-terminated byte data
    len: i64     # Length (excluding null terminator)
}

This design provides:

Natural String Syntax#

String literals in Vyb are first-class citizens:

# Direct literal assignment
greeting<String> = "Hello, Vyb!"

# Literal concatenation (just works™)
message<String> = "Hello" + " " + "World"

# Method calls on literals
length<Int> = "Vyb".len()                    # Returns 3
first<Int> = "Quantum".char_at(0)            # Returns 'Q' (81)
check<Bool> = "Einstein".starts_with("Ein")  # Returns true

# All without explicit constructors!

String literals process backslash escapes: \n LF, \r CR, \t TAB, \\ a literal backslash, \" a double quote, \0 NUL, and \xHH a hex byte. Any other \X is kept verbatim (regex/path-friendly), so "\r\n" is a real CRLF pair — HTTP requests depend on this.

Complete Method Reference#

Constructor

# Create from raw bytes (C interop)
name<String> = String::from_bytes("Alice", 5)
raw<String> = String::from_bytes(c_ptr, c_len)

Property Access

msg<String> = "Hello"
length<Int> = msg.len()  # Returns 5, O(1) operation

Substring Operations

text<String> = "Hello World"

# Extract substring (end optional)
hello<String> = text.substring(0, 5)      # "Hello"
world<String> = text.substring(6, 11)     # "World"
rest<String> = text.substring(6)          # "World" (to end)

# Safe character access with bounds checking
first<Int> = text.char_at(0)      # 72 ('H')
space<Int> = text.char_at(5)      # 32 (' ')
invalid<Int> = text.char_at(99)   # 0 (null char for out of bounds)

Search and Comparison

sentence<String> = "The quick brown fox"

# Prefix/suffix checking
has_the<Bool> = sentence.starts_with("The")      # true
has_fox<Bool> = sentence.ends_with("fox")        # true
has_dog<Bool> = sentence.ends_with("dog")        # false

# Substring search
has_quick<Bool> = sentence.contains("quick")     # true
has_slow<Bool> = sentence.contains("slow")       # false

# Empty strings always match
always<Bool> = sentence.starts_with("")          # true

Case Conversion

mixed<String> = "Hello World"

# ASCII case conversion (allocates new string)
upper<String> = mixed.to_upper()   # "HELLO WORLD"
lower<String> = mixed.to_lower()   # "hello world"

# Original unchanged (immutability)
println(mixed)  # Still "Hello World"

String Concatenation

# Using + operator (most natural)
full<String> = "Hello" + " " + "World"

# Chaining operations
result<String> = "Code".to_upper() + " " + "Language".to_lower()
# Result: "CODE language"

# Mixed types auto-convert: "Count: " + 42
# message<String> = "Count: " + 42

Practical Examples#

Text Processing

process_input(text<String>)<Bool> -> {
    # Validate input
    if (text.len() == 0) {
        return false
    }

    # Check for command prefix
    if (text.starts_with("/")) {
        command<String> = text.substring(1)
        println("Command: " + command)
        return true
    }

    # Search for keywords
    if (text.contains("help")) {
        println("Help requested")
        return true
    }

    return false
}

String Manipulation

format_name(first<String>, last<String>)<String> -> {
    # Capitalize first letter of each name
    first_upper<String> = first.char_at(0).to_string().to_upper() +
                          first.substring(1).to_lower()

    last_upper<String> = last.char_at(0).to_string().to_upper() +
                         last.substring(1).to_lower()

    # Combine with space
    return first_upper + " " + last_upper
}

main()<Int> -> {
    formatted<String> = format_name("ALICE", "wonderland")
    println(formatted)  # "Alice Wonderland"
    return 0
}

Data Validation

validate_email(email<String>)<Bool> -> {
    # Simple email validation
    if (email.len() < 3) {
        return false  # Too short
    }

    if (!email.contains("@")) {
        return false  # No @ symbol
    }

    return true
}

Memory Management#

Allocation Strategy

# Read-only operations: ZERO allocations
len<Int> = "Hello".len()                    # No malloc
ch<Int> = "World".char_at(0)                # No malloc
check<Bool> = "Test".starts_with("Te")      # No malloc

# Transform operations: Allocate new strings
upper<String> = "hello".to_upper()          # malloc(6) for "HELLO\0"
sub<String> = "Hello World".substring(0, 5) # malloc(6) for "Hello\0"
concat<String> = "A" + "B"                  # malloc(3) for "AB\0"

# Reference-counted our<T> handles cleanup automatically when the last strong
# reference leaves scope with no strong references remaining

Bounds Safety

# All index operations are bounds-checked at runtime
text<String> = "Vyb"

safe<Int> = text.char_at(2)      # OK: returns 'n' (110)
safe2<Int> = text.char_at(0)     # OK: returns 'V' (86)

# Out of bounds returns safe defaults
oob1<Int> = text.char_at(-1)     # Returns 0 (null char)
oob2<Int> = text.char_at(100)    # Returns 0 (null char)

# substring returns empty string for invalid bounds
empty<String> = text.substring(10, 20)  # Returns {null, 0}

Performance Characteristics#

Operation Time Space Notes
len() O(1) O(1) Just reads struct field
char_at(i) O(1) O(1) Bounds-checked array access
starts_with(s) O(k) O(1) k = prefix length, uses memcmp
ends_with(s) O(k) O(1) k = suffix length, uses memcmp
contains(s) O(n×k) O(1) Uses C strstr (KMP-like)
substring(i,j) O(k) O(k) k = j-i, allocates new buffer
to_upper() O(n) O(n) Allocates new buffer, ASCII only
to_lower() O(n) O(n) Allocates new buffer, ASCII only
split(sep) O(n×k) O(n) k = separator length; allocates a new buffer per part
format(args...) O(n+a) O(n+a) n = fmt length, a = serialized argument bytes
a + b O(n+m) O(n+m) Allocates new buffer

C Interoperability#

All String methods produce null-terminated strings for C compatibility:

# Use with C functions via the FFI (extern "C" + typed C aliases)
name<String> = "Alice"
c_str<*i8> = name.to_bytes()  # Get raw pointer

# Interoperable with:
# printf("%s", c_str)
# strlen(c_str)
# strcmp(c_str1, c_str2)
# strstr(haystack, needle)

Concatenation & Chaining#

# Simple things are simple
msg<String> = "Hello" + " " + "World"

# Complex things are possible (when needed)
advanced<String> = data
    .to_lower()
    .substring(0, 100)
    .replace("old", "new")
    .trim()

String Theory Achievement Unlocked: You now understand Vyb Strings better than most physicists understand actual string theory! 🎻✨

Error Handling with trap/fail/ensure#

Vyb features an explicit error handling system that combines the clarity of exceptions with the safety of Result types. The trap/fail/ensure trio provides compile-time error tracking with zero runtime overhead for the happy path.

The Philosophy#

Traditional error handling offers two flawed extremes:

Vyb's approach:

Error Fundamentals#

Functions declare error returns in their signatures using failable types:

# Functions that can fail return (T, error_ptr) tuples
divide(a<Int>, b<Int>)<Int> -> {
    if (b == 0) {
        fail 42  # Return error with code 42
    }
    return a / b
}

# The compiler wraps this as: { i64 result, i8* error } in LLVM

Type System Integration:

The fail Keyword#

Create and propagate errors with fail:

# Simple error codes (primitive types)
fail 404                    # Integer error
fail "not found"            # String error
fail 3.14                   # Float error

# Structured errors (custom types)
struct DivisionError {
    code<Int>,
    dividend<Int>,
    divisor<Int>
}

divide_structured(a<Int>, b<Int>)<Int> -> {
    if (b == 0) {
        fail DivisionError {
            code = 42,
            dividend = a,
            divisor = b
        }
    }
    return a / b
}

Error Memory Layout:

Heap-allocated error struct (16 bytes):
  Offset 0-7:  Type ID hash (i64) - For pattern matching
  Offset 8-15: Error value/data    - Primitive or struct data

Type ID Hashing:

The trap Block#

Handle errors with pattern matching:

# Basic trap with single error type
compute_safely(x<Int>, y<Int>)<Int> -> {
    result<Int> = {
        value<Int> = divide(x, y)  # May fail
        value * 2
    } trap (e<Int>) -> {
        println("Division failed with code: " + String::from_int(e))
        -1  # Return fallback value
    }
    return result
}

# Multiple error types with pattern matching
process_data(x<Int>)<String> -> {
    {
        value<Int> = risky_operation(x)
        "Success: " + String::from_int(value)
    } trap (e<Int>) -> {
        "Integer error: " + String::from_int(e)
    } trap (e<String>) -> {
        "String error: " + e
    } trap (e<DivisionError>) -> {
        "Division error code: " + String::from_int(e.code)
    }
}

# Wildcard trap - catch any error type
safe_operation(x<Int>)<Int> -> {
    result<Int> = {
        risky_call(x)
    } trap (e<DivisionError>) -> {
        println("Known error: division by zero")
        return 0
    } trap (e<?>) -> {
        println("Unknown error caught by wildcard!")
        return -1
    }
    return result
}

# Multi-type trap - catch union of error types
process_data(input<String>)<Int> -> {
    result<Int> = {
        parse_and_validate(input)
    } trap (e<ParseError | ValidationError>) -> {
        # Handler matches either ParseError or ValidationError
        println("Input processing failed")
        return -1
    } trap (e<NetworkError | TimeoutError>) -> {
        # Different handler for network-related errors
        println("Network issue, retrying...")
        return retry_operation(input)
    }
    return result
}

Pattern Matching Features:

Error Propagation#

Errors automatically propagate through call stacks:

# Three-level error propagation
divide(a<Int>, b<Int>)<Int> -> {
    if (b == 0) {
        fail 42  # Create error at bottom
    }
    return a / b
}

compute(x<Int>, y<Int>)<Int> -> {
    result<Int> = divide(x, y)  # Propagates error if divide fails
    return result + 10           # Only reached if divide succeeds
}

main()<Int> -> {
    val1<Int> = compute(10, 2)   # OK: 10/2 + 10 = 15

    val2<Int> = {
        compute(10, 0)  # Fails: division by zero
    } trap (e<Int>) -> {
        println("Caught error: " + String::from_int(e))
        -1  # Return fallback
    }

    return val1 + val2  # Returns 15 + (-1) = 14
}

Propagation Mechanics:

  1. divide(10, 0) allocates error on heap, returns (undef, error_ptr)
  2. compute checks error pointer, finds non-null, propagates (undef, error_ptr)
  3. main trap block catches error, extracts type and value
  4. Error memory freed after handling

Untrapped Errors#

Errors that escape without trap handlers trigger runtime termination:

divide(a<Int>, b<Int>)<Int> -> {
    if (b == 0) {
        fail 42
    }
    return a / b
}

main()<Int> -> {
    result<Int> = divide(10, 0)  # No trap handler!
    return result
}

Runtime Output:

┌─ UNTRAPPED FAILURE ──────────────────────────────────────────┐
│ Error: <runtime error>                                       │
│ Thread: 6472648897627130861                                  │
│ Time: 2025-10-21 10:01:29.810                                │
└──────────────────────────────────────────────────────────────┘

Exit Code: 1

Safety Guarantees:

Struct Errors with Context#

Rich error types carry debugging context:

struct ValidationError {
    field_name<String>,
    expected<String>,
    actual<String>,
    line_number<Int>
}

validate_input(input<String>)<Bool> -> {
    if (input.len() == 0) {
        fail ValidationError {
            field_name = "input",
            expected = "non-empty string",
            actual = "empty string",
            line_number = 42
        }
    }
    return true
}

main()<Int> -> {
    {
        is_valid<Bool> = validate_input("")
        0
    } trap (e<ValidationError>) -> {
        println("Validation failed:")
        println("  Field: " + e.field_name)
        println("  Expected: " + e.expected)
        println("  Actual: " + e.actual)
        println("  Line: " + String::from_int(e.line_number))
        1
    }
}

Output:

Validation failed:
  Field: input
  Expected: non-empty string
  Actual: empty string
  Line: 42

The ensure Keyword ✅ IMPLEMENTED#

The ensure keyword provides cleanup/finally semantics, guaranteeing code runs whether the block succeeds or fails:

# The ensure block runs on both success and failure paths
process_file(path<String>)<String> -> {
    file<File> = open_file(path)

    result<String> = {
        file.read()
    } trap (e<IOError>) -> {
        return ""
    } ensure -> {
        file.close()  # Always runs, success or failure
    }

    return result
}

# Execution order on error:
# 1. Block code executes and fails
# 2. Matching trap handler runs (if present)
# 3. ensure block always runs last

# Implementation details:
# - Parser fully supports } ensure -> { } syntax
# - Codegen inlines ensure blocks into control flow
# - Works with both success and failure paths
# - See: test/trap/test_ensure_simple.vyb

LLVM Code Generation#

The error handling system compiles to efficient LLVM IR:

; Failable function returning {i64 result, i8* error}
define { i64, ptr } @divide(i64 %a, i64 %b) {
entry:
  %is_zero = icmp eq i64 %b, 0
  br i1 %is_zero, label %error_path, label %success_path

error_path:
  ; Allocate error struct: 8 bytes type_id + 8 bytes value
  %error_mem = call ptr @malloc(i64 16)

  ; Store type ID at offset 0
  %type_id = i64 -3994496327427856726  ; hash("Int")
  store i64 %type_id, ptr %error_mem

  ; Store error value at offset 8
  %value_ptr = getelementptr i8, ptr %error_mem, i64 8
  store i64 42, ptr %value_ptr

  ; Return (undef, error_ptr) tuple
  %result = insertvalue { i64, ptr } undef, ptr %error_mem, 1
  ret { i64, ptr } %result

success_path:
  %quotient = sdiv i64 %a, %b
  ; Return (quotient, null) tuple
  %success = insertvalue { i64, ptr } undef, i64 %quotient, 0
  %success2 = insertvalue { i64, ptr } %success, ptr null, 1
  ret { i64, ptr } %success2
}

; Call site with error checking
define { i64, ptr } @compute(i64 %x, i64 %y) {
entry:
  %call = call { i64, ptr } @divide(i64 %x, i64 %y)
  %value = extractvalue { i64, ptr } %call, 0
  %error = extractvalue { i64, ptr } %call, 1

  ; Check if error occurred
  %has_error = icmp ne ptr %error, null
  br i1 %has_error, label %error_propagate, label %success

error_propagate:
  ; Propagate error up the stack
  %prop = insertvalue { i64, ptr } undef, ptr %error, 1
  ret { i64, ptr } %prop

success:
  %result = add i64 %value, 10
  %ret = insertvalue { i64, ptr } undef, i64 %result, 0
  %ret2 = insertvalue { i64, ptr } %ret, ptr null, 1
  ret { i64, ptr } %ret2
}

; Trap handler with type matching
define i64 @main() {
entry:
  %trap_error = alloca ptr
  store ptr null, ptr %trap_error
  br label %try_block

try_block:
  %call = call { i64, ptr } @compute(i64 10, i64 0)
  %value = extractvalue { i64, ptr } %call, 0
  %error = extractvalue { i64, ptr } %call, 1
  %has_error = icmp ne ptr %error, null
  br i1 %has_error, label %trap_landing, label %try_success

trap_landing:
  ; Load error and check type
  %error_typeid = load i64, ptr %error
  %type_matches = icmp eq i64 %error_typeid, -3994496327427856726
  br i1 %type_matches, label %catch_handler, label %unmatched

catch_handler:
  ; Extract error value from offset 8
  %value_ptr = getelementptr i8, ptr %error, i64 8
  %error_value = load i64, ptr %value_ptr

  ; Free error memory
  call void @free(ptr %error)

  ; Handle error (return -1)
  br label %after_trap

unmatched:
  ; No matching handler - call runtime
  call void @__vyb_runtime_untrapped_error(ptr %error)
  unreachable

try_success:
  br label %after_trap

after_trap:
  %result = phi i64 [ %value, %try_success ], [ -1, %catch_handler ]
  ret i64 %result
}

Performance Characteristics#

Happy Path (No Errors):

Error Path:

Comparison to Alternatives:

Approach Success Overhead Error Overhead Hidden Control Flow Compile-time Safety
Vyb trap/fail ~1 comparison 1 malloc + type match No Yes
C++ exceptions Exception tables Stack unwinding + allocation Yes Partial
Rust Result<T,E> Match overhead Enum size increase No Yes
Go error returns Comparison + check Allocation No Weak (can ignore)
Java checked exceptions Exception tables Stack trace + allocation Yes Partial

Error Handling Best Practices#

1. Use Specific Error Types

# Good: Rich context
struct FileError {
    path<String>,
    operation<String>,
    reason<String>
}

# Less good: Generic integer codes
fail 404

2. Handle Errors Close to Source

# Good: Handle immediately if recovery is possible
result<String> = {
    read_file(path)
} trap (e<FileError>) -> {
    println("Using default config due to: " + e.reason)
    default_config()
}

# Sometimes okay: Let errors propagate if caller should decide
data<String> = read_file(path)  # Propagates error to caller

3. Document Error Conditions

# Read configuration from file
#
# Errors:
#   FileError { operation = "open", ... } - File doesn't exist
#   FileError { operation = "read", ... } - Permission denied
#   ParseError { ... } - Invalid TOML format
read_config(path<String>)<Config> -> {
    # Implementation...
}

4. Prefer Structured Errors Over Codes

# Good: Self-documenting
struct NetworkError {
    url<String>,
    status_code<Int>,
    retry_after<Int>
}

# Less good: Requires external documentation
fail 503  # What does this mean?

Example: Complete Error Handling#

A comprehensive example showing all error handling features:

# Define domain-specific error types
struct ParseError {
    input<String>,
    position<Int>,
    expected<String>
}

struct ValidationError {
    field<String>,
    constraint<String>
}

struct DatabaseError {
    query<String>,
    code<Int>
}

# Parse integer from string
parse_int(s<String>)<Int> -> {
    # Actual parsing logic would go here
    if (s.len() == 0) {
        fail ParseError {
            input = s,
            position = 0,
            expected = "non-empty string"
        }
    }
    return 42  # Simplified
}

# Validate age constraint
validate_age(age<Int>)<Bool> -> {
    if (age < 0) {
        fail ValidationError {
            field = "age",
            constraint = "must be non-negative"
        }
    }
    if (age > 150) {
        fail ValidationError {
            field = "age",
            constraint = "must be <= 150"
        }
    }
    return true
}

# Store user in database (can fail)
store_user(name<String>, age<Int>)<Int> -> {
    valid<Bool> = validate_age(age)  # Propagates ValidationError

    # Simulate database operation
    if (age == 42) {
        fail DatabaseError {
            query = "INSERT INTO users",
            code = 1062  # Duplicate entry
        }
    }

    return 1  # User ID
}

# Process user registration with comprehensive error handling
register_user(name<String>, age_str<String>)<String> -> {
    # Multi-level error handling with different error types
    result<String> = {
        # Parse age (may fail with ParseError)
        age<Int> = parse_int(age_str)

        # Store user (may fail with ValidationError or DatabaseError)
        user_id<Int> = store_user(name, age)

        "User registered with ID: " + String::from_int(user_id)

    } trap (e<ParseError>) -> {
        msg<String> = "Parse failed: " + e.expected +
                     " at position " + String::from_int(e.position)
        println(msg)
        "PARSE_ERROR"

    } trap (e<ValidationError>) -> {
        msg<String> = "Validation failed for " + e.field +
                     ": " + e.constraint
        println(msg)
        "VALIDATION_ERROR"

    } trap (e<DatabaseError>) -> {
        msg<String> = "Database error " + String::from_int(e.code) +
                     " in query: " + e.query
        println(msg)
        "DATABASE_ERROR"
    }

    return result
}

main()<Int> -> {
    # Test success path
    result1<String> = register_user("Alice", "25")
    println(result1)  # "User registered with ID: 1"

    # Test ParseError
    result2<String> = register_user("Bob", "")
    println(result2)  # "PARSE_ERROR"

    # Test ValidationError
    result3<String> = register_user("Charlie", "-5")
    println(result3)  # "VALIDATION_ERROR"

    # Test DatabaseError
    result4<String> = register_user("Dave", "42")
    println(result4)  # "DATABASE_ERROR"

    return 0
}

Key Takeaways:

The Vyb error handling system achieves the rare combination of safety, performance, and ergonomics that makes robust error handling a joy rather than a chore.

Build System#

Vyb uses CMake for building:

# Clean build
mkdir -p build && cd build
cmake .. && make clean && make -j

# Quick rebuild (from project root)
make -C build -j

# Run a single file
build/vyb test/string/string_test.vyb

# Run demos and examples
python3 test/run_examples.py --vyb build/vyb
cmake --build build --target run-examples

# Run the milestone gate
python3 test/run_milestone_tests.py --vyb build/vyb
cmake --build build --target run-milestone

Test Harness#

Vyb's canonical test runner is test/run_tests.py, the same suite wired into CTest as the run-tests target and used for the full regression gate (currently 1061 .vyb tests, all passing):

Quick Testing#

# Full suite (JIT execution), from the repo root
python3 test/run_tests.py --vyb build/vyb --test-dir test --execute-jit

# Run a single category (parse, semantic, async, tls, ...)
python3 test/run_tests.py --vyb build/vyb --test-dir test --category async

# Save results as JSON
python3 test/run_tests.py --vyb build/vyb --test-dir test --json results.json

A secondary parallel harness (test_harness.py) plus triage_tool.py add HTML reporting, failure-triage, and performance analysis on top of that suite; the authoritative pass/fail count is always the run-tests CTest target output.

Test Analysis and Triage#

# Analyze test failures and create triage plan
./triage_tool.py results.json

# Generate markdown triage report
./triage_tool.py results.json --format markdown --output triage.md

# Focus on critical issues only
./triage_tool.py results.json --priority critical,high

Test Features#

Project Structure#

Vyb/
├── src/              # C++ source code
│   ├── main.cpp      # Entry point with LLVM JIT
│   ├── lexer.cpp     # Tokenization
│   ├── parser.cpp    # Syntax analysis
│   └── ast.cpp       # Abstract syntax tree
├── include/vyb/      # Header files
├── test/             # Vyb test programs
├── examples/         # Example programs
├── doc/              # Documentation
└── build/            # Build output

Examples#

Simple Programs#

Hello World:

main()<Void> -> {
    println("Hello, Vyb!")
}

Mathematical Computation:

fibonacci(n<Int>)<Int> -> {
    if (n <= 1) {
        return n
    } else {
        return fibonacci(n - 1) + fibonacci(n - 2)
    }
}

main()<Int> -> {
    return fibonacci(10)  # Returns 55
}

Data Processing with Auto-Serialization:

struct Result {
    success<Bool>,
    value<Int>,
    message<String>
}

process_data(x<Int>)<Result> -> {
    if (x > 0) {
        return Result {
            success = true,
            value = x * 2,
            message = "Processing successful"
        }
    } else {
        return Result {
            success = false,
            value = 0,
            message = "Invalid input"
        }
    }
}

main()<Result> -> {
    return process_data(21)
}
# Outputs structured JSON for the Result

JSON Serialization & Deserialization#

Vyb includes a complete JSON serialization system with bidirectional conversion between structs and JSON:

Automatic Serialization#

struct Person {
    name<String>,
    age<Int>,
    active<Bool>
}

main()<Int> -> {
    person<Person> = Person { name: "Alice", age: 30, active: true }

    # Serialize to JSON string
    json<String> = person.to_string()
    println(json)  # Output: {"name": "Alice", "age": 30, "active": true}

    return 0
}

JSON Deserialization#

# Deserialize JSON back to struct
person2<Person> = Person::from_string(json)

# Access fields normally
println(person2.name)  # Output: Alice
println(person2.age.to_string())  # Output: 30

Supported Types#

Auto-Serialization for main() Returns#

One of Vyb's standout features is automatic serialization of complex return types from main():

Implementation Details#

The JSON system is built on:

See test/json/ for comprehensive examples and runtime/vyb_type_metadata.c for implementation.

This makes Vyb excellent for data processing scripts, API services, and configuration management.

Memory Safety#

Vyb provides multiple memory management strategies:

# Unique ownership (like Rust's Box)
owned<my<String>> = my("unique data")

# Shared ownership (reference counted)
shared<our<String>> = our("shared data")
another_ref<our<String>> = shared  # Reference count incremented

# Borrowing (non-owning references)
view_ref<their<String>> = view(shared)      # Immutable borrow
mut_ref<their<String>> = borrow(owned)      # Mutable borrow

# Raw pointers for freedom operations
freedom {
    x<Int> = 42
    ptr<loc<Int>> = loc(x)  # Get pointer to x
    at(ptr) = 99           # Modify through pointer
}

Testing & Development Tools#

Vyb includes a modern, comprehensive testing infrastructure designed for efficient development and quality assurance:

🧪 Modern Test Harness#

The new Python-based test harness provides enterprise-grade testing capabilities:

# Basic test run with parallel execution
python3 test_harness.py --parallel

# Full test suite with HTML reporting and triage analysis
python3 test_harness.py --parallel --html-report --triage --performance

# Test specific patterns or directories
python3 test_harness.py --filter "async" --verbose
python3 test_harness.py --directory test/units --timeout 30

Test Harness Features

Test Statistics

🔧 Syntax Migration Tools#

Automated tools ensure codebase consistency and syntax standardization:

# Scan for legacy syntax patterns
python3 migrate_syntax.py --scan --directory . --report

# Apply canonical syntax migrations with backup
python3 migrate_syntax.py --migrate --directory . --backup

Migration Capabilities

📊 Triage Analysis Tool#

Automated failure analysis and development prioritization:

# Generate triage report from test results
python3 triage_tool.py test_results.json --output triage_report.html

Triage Features

📈 Development Workflow#

The integrated toolchain supports efficient development:

  1. Write Code: Use canonical syntax with ownership types
  2. Run Tests: python3 test_harness.py --parallel --triage
  3. Check Syntax: python3 migrate_syntax.py --scan
  4. Debug Issues: Use triage reports for prioritized debugging
  5. Commit Changes: Regular Git commits with test validation

Architecture#

Vyb is built on solid foundations:

Contributing#

Vyb is actively developed with regular commits tracking progress:

  1. Language Features: Add new syntax, types, or operations
  2. Standard Library: Implement core modules and utilities
  3. Testing: Create comprehensive test cases
  4. Documentation: Improve guides and examples
  5. Performance: Optimize compilation and runtime

See doc/ directory for detailed design documents and RFCs.

Recent Progress#

Latest cycle (v0.7.x): stdlib concurrency + a network/UI demo

v0.4.2 (freedom-1.0 series): Generic function monomorphization and FREEDOM blocks

Language Status: Vyb (freedom-1.0 series, tracked as v0.7.x) is an actively developed systems programming language with unified canonical syntax, a sized type system (Int8–Int64, UInt8–UInt64, Float32/Float64, Char, Rune, Bytes), compile-time monomorphized generics, aspect/bind polymorphism, a fail/trap error system, JIT/AOT/native codegen, and a modern test harness. The core language is stable and well tested; see doc/FEATURE_STATUS.md for the current feature matrix.

Getting Help#


Appendix#

A. Grammar (EBNF)#

The formal EBNF grammar now lives in the canonical reference, docs/refman/PROGRAMMERS_GUIDE.md §Appendix D — Grammar (EBNF). It is consolidated there so the project has one grammar home; the appendix also notes which productions are legacy.

B. Memory Model Reference#

Ownership Types:

Borrowing Operations:

Freedom Operations:

C. Auto-Serialization Reference#

Vyb automatically serializes complex return types from main():

Simple Returns:

Complex Returns:

Customization: Implement Serialize aspect for custom serialization behavior.

Glossary#

AST (Abstract Syntax Tree) Tree representation of source code structure produced by the parser.

Borrow Checking Compile-time analysis ensuring references don't outlive the data they point to.

Bundle Module visibility grouping via bundle(...) / share(...) directives.

Import Secure module inclusion from verified, signed sources.

JIT (Just-In-Time) Runtime compilation of code to native machine instructions for performance.

LLVM Low Level Virtual Machine - compiler infrastructure used by Vyb's backend.

Monomorphization Compile-time process of creating specialized versions of generic functions/types.

Ownership Memory management system using my<T>, our<T>, their<T>, mild<T> types.

Pattern Matching match statements that destructure and test values against patterns.

Smuggle Flexible module inclusion from external, potentially unverified sources.

Template Compile-time generic construct parameterized by types or constants.

Vec Resizable array collection with methods like push(), pop(), len(), get().


License#

Copyright 2026 Aniviza LLC. Licensed under the Apache License, Version 2.0 — see the LICENSE file for the full text.


Vyb (freedom-1.0 series): A mature systems programming language with a comprehensive sized type system, unified name-first syntax, pattern matching, compile-time monomorphized generics, resizable collections, an aspect/bind polymorphism system, and a unique import/smuggle module system.