Dazzle is a systems language for games, engines, tools and real-time code. Code with no unsafe block has no undefined behaviour, with no borrow checker. Whole programs run as fast as their C++. And C and C++ libraries are used straight from source, with no bindings, because Dazzle compiles to standard C++17.
On Windows, in PowerShell: irm https://dazzle-lang.github.io/install.ps1 | iex. Then dz new hello, cd hello and dz run. You need a C++ compiler, which is all dz asks of the machine. Installing →
Dazzle is a successor to C++ for the code C++ is chosen for. C++ gives you control and abstraction, and buries both under forty years of syntax and undefined behaviour; the languages that strip it down take away what a large codebase is built from. Dazzle keeps RAII, encapsulation, traits, generics and inheritance, with no garbage collector and no runtime, and adds what C++ cannot promise.
Safe
On one thread, a program with no unsafe has no undefined behaviour: no use after free, no reference left dangling by a push_back, no unchecked index, no undefined arithmetic. There is no borrow checker: aliasing stays legal and no struct carries a lifetime. Each signature says what memory a call may end, and the compiler checks it. Safety →
Fast
The C++ optimiser underneath, no garbage collector, and memory the compiler places in each scope’s region by itself. Three whole programs, written idiomatically in both languages, take 0.77 to 1.17 times as long in Dazzle as in C++, and A* search beats its C++ on time and memory, mostly through a faster hash map than std::unordered_map. Benchmarks →
Fluent in C++
C and C++ libraries are used straight from their source and headers, classes, overloaded methods and the template instances you name included, with no bindings to write. The output is standard C++17 for the compiler you already use, so a C++ codebase can adopt Dazzle one module at a time. C++ interop →
Simple where C++ is not
RAII in three members instead of the rule of five, errors as values with one or clause, generics checked where they are written, modules without headers, and a compiler whose every error says what to write instead. The tour →
Each of these is a complete program in the repository. Each one uses a C library built from its own source and called directly, with no bindings to write. Copy one, and you have a project that already builds.
Here are ten of the mistakes that both g++ and clang++ compile. Seven of them compile without a single warning on either compiler, even with -Wall -Wextra, and the first eight are undefined behaviour. In Dazzle, each one is a compile error or has one defined meaning:
The mistake
C++
Dazzle
Indexing past the end of an array
undefined behaviour
checked in every build
Reading an uninitialised variable
undefined behaviour
every value is initialised
Using a pointer that may be null
undefined behaviour, no warning
error DZ0528
Copying a struct that owns memory
double free, no warning
error DZ0403
Signed overflow
undefined, x + 1 < x folded to false
wraps, in every build
Dividing by zero
undefined, no warning
panics, naming the line
Falling off the end of a function
undefined behaviour, with a warning
error DZ0405
Keeping a reference across a push_back
undefined behaviour, no warning
error DZ0413
Using a value after std::move
compiles, reads whatever is left
error DZ0401
Ignoring a returned error
compiles silently
error DZ0510
Each diagnostic points at the line, says what is wrong there, and ends with a help line:
$ dz check .warning[DZ0406]: variable `b` is never used┌─ src/main.dz:17:9
│17│ let b = a
│^nothing reads this│= help: if that is deliberate, name it `_b`; otherwise remove it
error[DZ0403]: `Buffer` is move-only and cannot be copied┌─ src/main.dz:17:13
│17│ let b = a│^this would copy it│= help: it declares a `drop`, so copying it would release the same resource twice — write `move(…)` to transfer ownership, or give `Buffer` a `copy(other: &Self)` to make it copyable (spec §6.3)
1 error emitted
$ dz check .error[DZ0528]: reaching through a `^T` with `.` is unsafe┌─ src/main.dz:18:20
│18│ println!("{}", p.health)
│^^^^^^^^outside an `unsafe` block│= help: wrap it in `unsafe { … }` — nothing checks that the memory is still there. To use a pointer safely, turn it into a reference once with `p or …` (spec §5.2)
1 error emitted
The bug C++ code actually has is a reference used after a call grew, shrank or freed the memory it points into: a pointer into a vector kept across a push_back, an iterator kept across an erase. Dazzle catches it at compile time, without lifetimes. A type names its memory with ties, and every function says in its signature which of them a call may end, with a kills clause:
// in std's List: each signature says what a call may end
pubfnpush(&mutself, v: T) killsself@items// may move the array
pubfnlen(&self) -> usize // no clause: kills nothing
$ dz check .error[DZ0413]: used after the memory it points into was killed: `scores@items`┌─ src/main.dz:6:5
│5│ scores.push(20) // may move the array
│---------------this call kills `scores@items`6│first += 1
│^^^^^points into `scores@items`│= help: a call, write, move or drop that can move or free memory ends every reference into it: use this before that happens, or get it again after (spec §6.7)
1 error emitted
The compiler checks each clause against its function’s body, and dz fix writes the clause a body needs. Aliasing stays legal, a &mut and a & to the same value included, and no struct carries a lifetime parameter. So safe programs Rust’s borrow checker refuses compile: a &mut to one element beside a & to the next, a name kept across a method that only records a score, a value returned from a lookup or inserted. What the borrow checker refuses →
Ties are one rule of several. Together they make a promise: a program with no unsafe has no undefined behaviour on one thread. A moved value cannot be used, nothing is dropped twice, every value is initialised, every index is checked, arithmetic is defined, and reading through a raw pointer happens only inside unsafe. Where the promise ends is written down too: unsafe blocks, the C and C++ a program calls, and threads, which Dazzle does not have yet. Safety →
The same ties let the compiler choose where memory comes from, so ordinary code names no allocator. A value that never leaves the scope that builds it goes in that scope’s region, released in one step when the scope ends. A returned value is built where the caller keeps it, and anything else goes on the heap. The editor shows where each allocation went, and dz placements lists them with the reason. Ownership →
Safety costs little at run time: most of it is checked while compiling, and what is left is a bounds check, a divisor check, and an instruction or two where arithmetic C++ leaves undefined is given a meaning. Three whole programs are written idiomatically in both languages, built by the same compiler with the same flags, and checked to compute the same result. Here is how long the Dazzle program takes, as a multiple of the C++ program’s time:
Program
clang++
g++
Peak memory against C++
A* search: a binary heap and hash maps keyed by a struct
0.77×
0.88×
0.75×
A word count: 2 million words in a hash map, then sorted
0.91×
1.02×
1.17×
A tokenizer, parser and evaluator, failing through !->
1.17×
1.07×
0.94×
Below 1.00× Dazzle is ahead. The C++ halves use std::vector, std::unordered_map, std::string, std::unique_ptr and std::sort, as a C++ reviewer would accept them. Each language uses its own standard library, so a ratio includes the library’s design: A* search’s lead is mostly Dazzle’s hash map, a SwissTable, against std::unordered_map’s node per entry, and with the same hash map on both sides the two are even. Every run, and what is left →
$ dz run --release .hello, stranger
src/main.dz:7: a name cannot be empty
hello, nobody
There are no exceptions and no Result<T, E>. A function that can fail says so in its signature, and the caller handles the failure with one or clause. An Error is 32 bytes, never allocates, and carries the source location that created it. Forgetting to handle one is a compile error. Errors →
A constructor, drop and copy. There is no rule of five: the compiler writes moves and assignments, and a moved-from value is reset to its defaults instead of left “valid but unspecified”. Ownership →
Errors point at your code
Generics are checked where they are written and monomorphized by Dazzle, not by C++ templates. A mistake is reported once, in your function, never in generated code. Generics →
C and C++ from source
Name a library’s sources in the manifest, or its CMake project, and dz builds it and imports its header: functions, structs, enums, macros, namespaces, classes and overloaded methods. There are no bindings to write or maintain. C++ interop →
A compiler that teaches
Every diagnostic has a stable code and nearly every one a help: line. dz explain prints the rule with a wrong and a right program, dz fix applies the fixes it is certain of, and the language server runs on half-typed code. Tooling →
A few lines of manifest build a library from its own source, or with its own CMake build. libclang reads its header with the same flags the generated code is compiled with. Its declarations become an ordinary Dazzle module, C++ classes and overloaded methods included:
letmutcounter=shp::shapes::Counter(10) // a C++ class and its constructor
counter.add(5) // an overload set, resolved by argument type
shp::shapes::translate(&mutmoving, 1, 1) // `Point&` is a `&mut Point`
A function that takes only values is safe to call. One that is handed a pointer is safe once the library’s recipe says which of its functions keep a pointer past the call, and those stay unsafe. Every example in the gallery above works this way: raylib, SDL3, Box2D, sokol, stb and RGFW, each built from its own source. C++ interop →