Vela vs. The Landscape

A practical comparison across the programming-language spectrum.

At a Glance

Vela sits in the systems-language zone: it is compiled and statically typed, and it manages memory with regions that the compiler checks, with no garbage collector. This page compares it with representatives from every level of abstraction, from bare-metal C to dynamic high-level languages like Python and JavaScript.

Performance

Raw execution speed and predictable machine-code output.

Memory

Manual, regions, GC, reference counting, or ownership-based memory management.

Build

Compile-time speed, dependency complexity, and distribution size.

Safety

Type/memory safety guarantees and runtime crash prevention.

Comparison Table

Language Level Performance Memory Model Build Speed Safety Best For
C Low Excellent Manual Very Fast Unsafe by default OS kernels, embedded, drivers
Rust Low–Mid Excellent Ownership + borrow checker Moderate Very Safe Systems, WebAssembly, infrastructure
Zig Low–Mid Excellent Manual, explicit allocators Fast Safer C, runtime checks in debug builds Systems, cross-compilation, games
Vela Low–Mid Excellent Regions (arenas) Fast Type-safe, region-checked pointers, no borrow checker Learning, small systems tools
C++ Mid Excellent Manual + RAII + smart pointers Slow Unsafe by default, opt-in safety Games, engines, high-performance apps
Go Mid Good Garbage collected Very Fast Memory-safe, type-safe Cloud services, networking, tooling
Swift Mid Good ARC + ownership Moderate Very Safe Apple platforms, server-side
Java Mid–High Good Garbage collected (JVM) Moderate Memory-safe, type-safe Enterprise backends, Android
C# Mid–High Good Garbage collected (.NET) Moderate Memory-safe, type-safe Enterprise, games (Unity), desktop
Haskell High Good Garbage collected + lazy Slow Very Safe Compilers, research, finance
OCaml High Good Garbage collected Fast Very Safe Compilers, theorem provers, Jane Street
JavaScript High Moderate Garbage collected Instant Dynamic, runtime errors common Web frontends, Node.js services
Python High Moderate Reference counting + cycle collector Instant Dynamic, runtime errors common Scripting, data science, ML, web
Ruby High Moderate Garbage collected Instant Dynamic, runtime errors common Web backends, scripting, DevOps

Performance

Vela generates plain C, so its theoretical performance ceiling is essentially the same as C, C++, Zig, and Rust. There is no interpreter and no garbage collector adding overhead; the runtime is a small C header of helpers (printing, the region allocator, and the integer, index, and slice checks).

Rust and C can still edge ahead in some micro-benchmarks because they offer finer control over aliasing and layout, but for typical programs the difference is negligible. Go, Java, and C# are fast but carry GC pauses and runtime weight. CPython and Ruby are often one or two orders of magnitude slower for CPU-bound work because they interpret dynamically typed code; JavaScript's JIT compilers narrow that gap to a small multiple of C for many workloads.

Memory

Vela manages memory with regions: region r declares an arena, new int(42) at r allocates in it, and everything in r is freed at once when the block declaring it closes. There is no free. A pointer's type names its region (^int@r), as text's does (text@r, where plain text lasts as long as the program, much like Rust's &'static str), and the checker keeps pointers and text from being stored anywhere that outlives their region, so use-after-free and double free can't happen, with no garbage collector, reference counts, or borrow checker. To keep a value longer than its region, a program copies it into a longer-lived one with value.copy(), which follows pointers and keeps shared values shared, much like a deep clone. The costs: memory is freed a whole region at a time, and one value's pointers all live in one region (see Memory and Regions, and Vela Regions for the full guide, its limitations, and their workarounds).

Language Memory is freed Use after free Double free Leaks Cost at run time What you write
C By hand, with free Possible Possible Possible None Every malloc and free
C++ By destructors and smart pointers Possible (raw pointers, references) Possible with raw pointers Rare; shared_ptr cycles leak Counts for shared pointers Ownership types
Zig By hand, through explicit allocators (often arenas) Possible; often caught by the debug allocator Caught by the debug allocator Reported by the debug allocator None in fast release builds Allocator parameters, defer
Vela A whole region, when its block ends Ruled out at compile time Impossible: no free Not past the region's block; a long-lived region can grow None Region names in pointer types, region parameters
Rust When the owner goes out of scope Ruled out at compile time (safe Rust) Ruled out Rare (reference cycles, mem::forget) None (counts only for Rc/Arc) Ownership, borrows, lifetimes (mostly inferred)
Swift When a reference count reaches zero Ruled out Ruled out Cycles, unless broken with weak Count updates weak/unowned for cycles
Go, Java, C# By a garbage collector, later Ruled out Ruled out Only through data kept reachable Collector time and memory Nothing
Regions are the closest match to how Zig programs use arena allocators, and to the region systems of languages such as Cyclone, with the checking kept to a few rules. Allocation is a pointer bump, and freeing a region costs one call per chunk. Future Directions keeps the other options, such as move-only pointers and borrowed references, as ways to grow the model.

Build Speed

Vela's compiler is tiny and emits C quickly. The total build time is dominated by the C compiler, which is still very fast for small programs. This makes Vela competitive with C, Zig, and Go for iterative development.

Safety

Vela provides static type safety and exhaustive pattern matching on kinds, which catches many bugs at compile time: there are no implicit conversions (an int and a float don't mix in arithmetic until one is turned into the other, and only a plain value becomes a maybe where one is expected), every value-returning task must produce a value on every path (a <- value, or named results that are set), a missing value is a maybe T that must be checked before use (pointers can't be null), and a failure is a T ~ E whose error a call statement can't ignore by accident. Memory is freed through regions, whose pointers the checker keeps from outliving them, so freed memory can't be used or freed again. Integer overflow and division by zero stop the program with a message instead of being undefined, as they are in C, and so does an index out of range of an array or slice. It is much safer than C, but its guarantees are narrower than Rust's (which also rules out data races) or those of GC'd languages; Safety lists what the compiler checks and where it can't yet.

Language Type Safety Memory Safety Null Safety
C Weak Unsafe No
C++ Moderate Unsafe by default No
Vela Strong Regions, compile-time checked maybe T; no null pointers
Zig Strong Manual + checks Optional
Go Strong GC-safe Nil exists
Rust Strong Compile-time proven Option<T>
Swift Strong ARC + compile-time Optional
Java / C# Strong GC-safe Null exists
Haskell / OCaml Very Strong GC-safe Option types
Python / JS / Ruby Dynamic GC-safe Null/None common

Syntax & Complexity

Vela aims for Go-like minimalism: one way to define functions, one way to define records, one way to define unions. It avoids the dualities that plague other languages (structs vs classes, interfaces vs traits). It does have both references and pointers, but they don't overlap: a reference (flex&, fixed&) is another name for a place, never stored and never null, and a pointer (^T) is a value kept in a region.

Regions add the most to Vela's syntax of any feature: a region statement, @r in pointer types, and r: region parameters. That is still far less than lifetimes and borrowing, and code that doesn't use pointers never sees it.

Like Go, a Vela task can send several values — task divmod(a: int, b: int) -> int, int, then fixed q, r = divmod(17, 5) — so a pair of results needs no record and no output parameter, as it does in C. Those values are not a tuple: they can only be received by a list of names. Vela has tuples too, as a separate type (-> (int, int) sends one), like Rust's and Swift's but with items that never change: t.0 = 1 is an error even in a flex tuple, which can only take a whole new one.

A Vela task or shape can take types as well as values — task first<T>(xs: [T]@r) -> maybe T, shape Box<T> — and one task or shape is made for each set of types used, as C++ templates and Rust generics are. What a type must allow is said with one of five words rather than a system of its own: frozen, same, similar, shown, and ordered, where Rust has traits and impl blocks, C++ concepts (or nothing, and errors deep inside a template), and Go interfaces used as constraints. That makes generic code readable and its errors short. A type that has a method is asked for by a pattern, <T: Shaped>, which a shape or kind says it follows (shape Circle::Shaped), much as a Rust type implements a trait, but written in the type's own block, so no other module adds one to it, and with no pattern as the type of a value, where Rust has dyn and Go's interfaces are just that. A pattern may give a method a default body, as a trait does, and ask for fields, which a trait can't and a Swift or Kotlin protocol can. There is still no way to ask for a type that can be added: a task that adds its values takes numbers. A kind takes types too (kind Option<T>), as Rust's enum does. Go's generics arrived late and Vela's are small on purpose; C++ and Rust go much further, with the compile times and the error messages that come with it.

A kind can imitate another, having all its variants before its own (kind AppError::ParseErr), and a value of the kind imitated goes where the imitating one is expected, so a task can send on another task's failure as its own. Rust gets there by wrapping one enum in a variant of another and writing a From conversion for ?, TypeScript by joining union types (type AppError = ParseErr | Io), and OCaml with polymorphic variants; Go and C have nothing like it, and Java and C# reach for a class hierarchy, whose open set of subclasses no switch can check for every case.

A pipe, line |> quantity() |> reserve(5), reads calls in the order they run, as F#, OCaml, and Elixir's |> do; |>> gives the value to the second argument, where Elixir always gives the first and Hack writes a placeholder. A step that fails stops the chain, which then fails with that failure, much as Rust's ? leaves a function, but the chain stays a value the task reads, with a plan B or a match, rather than an early return.

Vela's methods are written inside the shape or kind they belong to, as in a C++, Swift, or Kotlin class, but with no inheritance, no constructors, and no hidden receiver: every member of the value is written after a dot (.count, .cap()), so nothing comes into scope unseen, where C++ and Java let a bare count mean a field. A method that changes its value says alters, as Rust writes &mut self and C++ leaves off const, and only a value that can change may call it. Go puts methods anywhere in the package and Rust in impl blocks, possibly several; Vela keeps them in the type's own block, so one place shows everything a type does, and no other module can add to it.

A parameter says how it is given, where C++ has T, const T&, and T&, C# has in and ref, and Swift inout: x: T is read and never changed, flex x: T is the task's own copy to change, flex& x: T changes the caller's place, and fixed& x: T reads it where it is. The call marks a place it gives with &, as C#'s ref and Swift's & do, so a change is visible where the call is written. Rust's borrow checker makes sure two names never change one value; Vela checks one call at a time, refusing a place given twice where the call changes it, and lists what it doesn't check yet on Safety. A plain parameter larger than 64 bytes is passed by address when nothing can change it during the call, which C++ programmers write by hand as const T&.

== is cheap for every type, so an array, a shape, or a tuple is compared by where it is, as Java, C#, and Python's is compare objects; ~= compares item by item, as Rust's derived PartialEq, Java's equals, and Python's == do, and its cost is in the symbol. Every value can be shown without a line of code (show(p) writes Point { x: 1, y: 2 }), as Rust's #[derive(Debug)] and Python's repr give, and a shape's own str method replaces it. A shape can have shared fields and methods, one for the whole program, as static members are in C++, Java, C#, and Swift, named through the shape (Tally.count) and never through a value.

Results can also be named, as in Go: -> total: int, set with total <- value and sent by reaching the end of the task. Go gives a named result its zero value, so forgetting to set it is silent; Vela has no zero values, so the compiler rejects a path that reads one, or ends, without setting it. Every line that changes what a task sends starts with a result's name and an arrow, which Go's plain = doesn't show.

Modules work much like Go's packages, but a module is one file: include shapes/geometry reads shapes/geometry.vela, whose names are written geometry.area, and include shapes/geometry (area) also brings area in, as Python's from … import does. The word is C's, but unlike #include it doesn't paste the file's text in: the file keeps its own names, and is read once however many files include it. Braces bring in only the names listed, like JavaScript's import {…} from, and => renames a module or a name, as as does in Rust, Python, and JavaScript; unlike them, the old name then names nothing in that file. A variant keeps its name but can be written through its kind, Size.Small, as in Rust and Swift. Where Go exports a name by capitalizing it and Rust by marking it pub, everything in a Vela file is public unless marked priv, fields included, so a name's spelling never decides who can use it. There is no package manager yet.

Arrays are written [int#4], the count after a # as in item #4 (Rust writes [i32; 4]), and are values copied whole, as C's arrays are inside a struct. A slice, [int]@r, is Go's slice or Rust's &[T] as a pointer and a length, but like every Vela pointer it points into a region, so it can't outlive its memory. It grows and shrinks, as Go's with append or Rust's Vec do, with xs.add(v) and xs.del(i); like Go's, it grows by copying into a bigger block, but the old block stays in the region until the region is freed. Adding to a copy of a slice doesn't write over items another copy counts, which Go's append can, unless that copy took its last items out with del first. Every index is checked, as in Go, Rust, and Zig's safe builds, and the length is xs.len, as in Zig.

Where Vela Fits

Vela is best understood as an experimental, teaching-friendly systems language. It is not trying to dethrone Rust or C. Instead, it demonstrates that you can have a small, fast, compiled language with modern features — pattern matching, sum types, maybe values instead of null, errors as types, memory in checked regions, and clean syntax — while staying close to the metal.

When to choose Vela: learning about compilers and language design, experiments, and small demos where you want C performance without C syntax. (Vela reads input a line or a number at a time, and files through its library; see Current Limits.)

When to avoid Vela: production systems, large teams, programs that need sets, rich standard libraries, or concurrency support, and long-running programs whose data changes piece by piece, since regions free memory in bulk (see the workarounds).

Summary

Vela borrows the best ideas from the systems world — C's simplicity and speed, Rust's static safety mindset, Go's minimalism, Zig's explicit memory philosophy, and Cyclone's checked regions — while keeping the implementation small enough to understand in one sitting. It trades absolute safety for simplicity, and rich ecosystems for a clean slate. For what it is, that is exactly the point.