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.
Raw execution speed and predictable machine-code output.
Manual, regions, GC, reference counting, or ownership-based memory management.
Compile-time speed, dependency complexity, and distribution size.
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 |
- Rust is the safest here: its borrow checker prevents data races and use-after-free at compile time.
- Zig makes allocators explicit and checks bounds, overflow, and more at run time in debug and safe release builds (checks that fast release builds drop), sitting between C and Rust.
- Go, Java, C#, Python, JS, Ruby use garbage collectors, trading memory overhead and latency for convenience.
- C++ offers RAII and smart pointers, but safety is opt-in and easy to bypass.
- Vela's region names are a lightweight cousin of Rust's lifetimes: always one name the programmer writes, tied to a block, and never inferred across tasks. That keeps the rules few, at the price of rejecting some safe programs that Rust accepts, such as a value pointing into two regions.
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.
- Fast: C, Go, Zig, OCaml, Vela, Python/JS/Ruby (no compile step).
- Moderate: Rust, Java, C#, Swift.
- Slow: C++ and Haskell, due to templates, headers, or aggressive optimization.
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.
- Minimal: Go, Vela.
- Moderate: C, Python, Ruby, JavaScript.
- Complex: C++ (templates, headers), Rust (lifetimes, traits), Haskell (type classes, laziness).
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 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.