Native code.
Familiar syntax.
No pointers.
Cromo is a compiled language with the syntax of C and C++, RAII and ownership checked by the compiler, and no garbage collector. One command, cromo, compiles and links your program. Cromo brings together the best ideas of C++, Rust and Go, with rules that stay simple.
import std.time
// RAII: the destructor runs when the timer leaves its scope.
struct Timer {
string name
time.Instant start
Timer(move string label) {
name = move label
start = time.now()
}
~Timer() {
println("{}: {} µs", name, start.elapsed().microseconds())
}
}
i32 main() {
Timer timer = Timer("sum")
map<bool, i64> parity
for (i64 n = 0; n < 1000; n++) {
parity[n & 1 == 0] += n
}
println("even {}, odd {}", parity[true], parity[false])
ret 0
}
$ cromo run timer.cro even 249500, odd 250000 sum: 120 µs
The control of C++, without its traps
Cromo keeps what C and C++ programmers already know — braces, value types, destructors — and leaves out pointers, undefined behavior and the borrow checker's lifetimes.
Ownership you can read
ref lends, move gives away, copy() duplicates. No lifetimes to annotate: the compiler follows every path.
RAII, always
Destructors run at the end of every scope, on ret and break, and while an exception unwinds.
LLVM inside
Cromo emits LLVM IR and optimizes it with a pinned LLVM. Overflow is a panic in debug builds and wraps around in --release.
No SDK, no libc
cromo carries LLVM and the LLD linker; the runtime and the library come with it. No Visual Studio, no libc: programs start in kilobytes.
Windows and Linux
Build for Linux or Windows from either system with --target linux or --target windows. A module can add fs_windows.cro and fs_linux.cro next to fs.cro.
Generics and interfaces
Generics compiled once for each type, constrained by interfaces. Dynamic calls only through interface parameters. No inheritance.
Exceptions that clean up
try, catch and finally, with every live object destroyed on the way out. Bugs are panics, not exceptions.
Tooling included
cromo fmt has one style and no options. cromo lsp gives VS Code errors as you type, hover, definitions and completion.
The compiler explains the rules
Strings, lists and maps own memory, so Cromo never copies them behind your back. When a value would be duplicated or used after a move, the error says how to fix it — and names the design decision behind the rule.
- readThe default for parameters: borrowed and read-only, never copied.
- refThe function can modify the caller's value. Two
refto the same place are an error. - moveThe function becomes the owner; the caller can no longer use the value.
void keep(move string text) {
println("kept {}", text)
}
i32 main() {
string name = "Ada"
string other = name
keep(move name)
println("{}", name)
ret 0
}
$ cromo build own.cro own.cro:7:20: error: values of type string are not copied implicitly (D23): write 'move name' or 'name.copy()' string other = name ^ own.cro:9:19: error: 'name' has been moved and can no longer be used (D23) println("{}", name) ^
Read it once, know what it does
Every example on this page compiles with the current version of cromo. Statements end at the end of the line; types come before names; ret returns.
interface Shape {
f64 area() const
}
struct Circle : Shape {
f64 radius
f64 area() const {
ret 3.14159 * radius * radius
}
}
// Compiled once for each type: no boxing, no virtual calls.
f64 total<T : Shape>(list<T> shapes) {
f64 sum = 0.0
for (T shape in shapes) {
sum += shape.area()
}
ret sum
}
i32 main() {
list<Circle> circles
for (i32 i = 1; i <= 3; i++) {
Circle c
c.radius = f64(i)
circles.add(c)
}
println("{:.2f}", total(circles))
ret 0
}
$ cromo run generics.cro 43.98
// A lambda captures by value, with ref or with move.
list<i64> select(list<i64> values, func(i64) bool test) {
list<i64> result
for (i64 v in values) {
if (test(v)) {
result.add(v)
}
}
ret result
}
i32 main() {
list<i64> values
for (i64 i = 1; i <= 10; i++) {
values.add(i)
}
i64 limit = 7
i64 sum = 0
list<i64> big = select(values, [limit](i64 x) bool { ret x > limit })
select(values, [ref sum](i64 x) bool {
sum += x
ret false
})
println("{} above {}, sum {}", big.len(), limit, sum)
ret 0
}
$ cromo run lambdas.cro 3 above 7, sum 55
i32 main() {
map<string, i32> stock
stock["apples"] = 12
stock["pears"] += 3 // a missing key starts from 0
i32[7] week // a fixed-size array, where it is declared
week[0] = stock["apples"]
for (string item, i32 count in stock) {
println("{:<8}{:>4}", item, count)
}
string report = format("{} items, monday {}", stock.len(), week[0])
println("{}", report)
ret 0
}
$ cromo run collections.cro apples 12 pears 3 2 items, monday 12
import std.fs
struct Config {
string path
i32 port
}
Config load(string path) {
Config config
config.path = path.copy()
config.port = i32(fs.readText(path))
ret config
}
i32 main() {
try {
Config config = load("port.txt")
println("listening on {}", config.port)
} catch (Exception e) {
eprintln("cannot start: {}", e.message())
ret 1
} finally {
println("bye")
}
ret 0
}
$ cromo run errors.cro cannot start: cannot read 'port.txt': no such file or folder bye
import unsafe
extern "user32" {
i32 MessageBoxA(addr window, addr text, addr title, u32 kind)
}
i32 main() {
addr none
string text = "Hello from Cromo"
MessageBoxA(none, text.cstring(), "cromo".cstring(), 0)
ret 0
}
$ cromo run c_interop.cro # a message box opens; user32.dll was checked for MessageBoxA at compile time
One command for everything
Build, run, format and edit with the same executable. No build system to configure: a folder with main.cro is a program.
$ cromo run hello.cro Hello, world! $ cromo build # main.cro of the folder $ cromo build --release # optimized $ cromo build --target linux # an ELF, from Windows $ cromo fmt . # one style, no options $ cromo lsp # for the editors $ cromo version cromo 0.27.0 (LLVM 23.1.4)
VS Code, from day one
The extension highlights the syntax and runs cromo lsp: errors while you type, the type of every name on hover, go to definition and the members after a dot.
As fast as C++, measured
Eleven small programs — allocation, recursion, floating point, strings, hash maps, threads — written in Cromo, C++, Go and Rust, compiled with the usual optimizations and run on the same machine. Every version of cromo is measured against the previous ones and against the references.
Run time
Geometric mean, relative to g++ -O2. Lower is faster.
Compile time
Geometric mean, relative to g++ -O2.
Run time of each program, in milliseconds
The median of 5 runs; the fastest of each row is green.
| benchmark | cromo 0.16 | g++ 14 | clang++ 18 | go 1.22 | rustc 1.96 |
|---|---|---|---|---|---|
| binarytrees | 999 | 956 | 968 | 1434 | 1713 |
| bits | 435 | 520 | 500 | 715 | 502 |
| collatz | 529 | 746 | 522 | 844 | 524 |
| fib | 561 | 360 | 503 | 1116 | 608 |
| mandelbrot | 832 | 820 | 812 | 831 | 843 |
| shapes | 481 | 470 | 489 | 753 | 491 |
| sort | 540 | 582 | 592 | 611 | 486 |
| spectralnorm | 513 | 503 | 515 | 515 | 521 |
| strings | 757 | 452 | 440 | 691 | 475 |
| threads | 111 | 112 | 111 | 113 | 112 |
| wordcount | 599 | 1181 | 685 | 346 | 705 |
AMD Ryzen 9 5950X, Ubuntu 20.04 in WSL 2; cromo build --release --target linux, g++ -O2, clang++ -O2, go build, rustc -C opt-level=3. Each program prints a checksum, and all the versions must print the same. The sources and the script are in bench/.
Built in the open, one milestone at a time
Every choice is decided first and written down with its reasons, then implemented with tests on Windows and Linux. Twenty-eight milestones, M0 to M27, are done.
Done M0 – M27
- M0–2LLVM backend, numbers, control flow, structs, RAII
- M3–6Ownership, generics, interfaces, lists, strings, enums, exceptions
- M7–10Modules, C interop without the SDK, Linux,
buildandrun - M11–13Formatter, language server, anonymous functions
- M14Arrays, maps, bytes, files, time, code for each system
- M15–16Threads and mutexes; TCP, UDP and DNS without libc
- M17TLS for clients and servers, and cryptography
- M18HTTP server and client, JSON from structs
- M19Lightweight tasks under the same API as threads
- M20Signed archives for Windows and Linux, made from the repository
- M21List literals, text functions, files and other programs
- M22Printable types,
{:?}for every value, generic methods - M23Channels and atomics for tasks and threads
- M24Stack traces, also in release, and debugging with PDB and DWARF
- M25
cromo test:[[test]]functions,assert, a process for each test - M26Values of interfaces everywhere, computed initial values of fields
- M27JSON of enums and arrays,
http.Client, gzip, WebSocket - 0.27The first public release: the one-line installer and the Cromo Guide
Next
- update
cromo update, with the signed manifests of the versions - packages
cromo get,cromo.modand versions from Git tags
Later self-hosting
- lastThe compiler written in Cromo, compiling itself
Hello, world
Cromo installs with one line, for your user and without administrator rights: cromo, the library and the VS Code extension, in ~/.cromo. Then the Cromo Guide takes you from the first program to a web service.
# Windows, in PowerShell > irm https://cromolang.org/install.ps1 | iex # Linux $ curl -sSf https://cromolang.org/install.sh | sh
i32 main() {
println("Hello, world!")
ret 0
}