The Design of Onyx

Onyx started as a personal project during the 2020 Coronavirus Pandemic, inspired from languages like Jai, Odin and Zig. Over the course of several years, Onyx developed to fit the needs of many personal programming projects. The design of Onyx was not driven by simplicity or even an interesting academic principle. It was driven by solving real problems. For that reason, Onyx is not the simplest language. It is a language that has many useful features to make common programming tasks easier.

The design of Onyx today can be summarized in three words: efficient, procedural, and pragmatic.

Efficient

Performance is a cornerstone in all aspects of Onyx, from the compilation speed to the runtime of the end product.

Your development tools should not slow you down. If your build tools take over 5 seconds to compile your code, your brain has a chance to "context switch" to something else, which breaks your programming flow and concentration. For Onyx, under one second compile times are a requirement for projects less than quarter million lines of code. Onyx will never regress to a point where this is not true. Note, there are currently no codebases in Onyx that are at this scale, and likely never will be due to Onyx's compact syntax, but the guarantee stands regardless.

Onyx programs are very fast, due mostly to compiling to small WebAssembly modules. By leveraging its architecture independent yet low-level bytecode, WebAssembly engines are able to compile to native machine code ahead of time and achieve near-native performance. Onyx ships with either the Wasmer engine, or a custom WebAssembly engine written specifically for Onyx called OVM. Though OVM is noticeably slower than Wasmer, it has numerous benefits including debugging support and a minuscule binary size (under 1MB for the compiler and the engine).

Procedural

The Onyx programming model at its core is procedural. This paradigm is familiar to most programmers, so Onyx should not feel foreign to anyone with programming experience.

Building up complexity and reusable code in Onyx is done by creating procedures and types. Onyx has a rich type system that is able to model the state of any program.

Procedures are generally attached to types as "methods". A typical Onyx abstraction may look like this.

Connection :: struct {
    // ...
}

Connection.make :: () -> Connection {
    // ...
}

Connection.destroy :: (self: &#Self) {
    // ...
}

Connection.send :: (self: &#Self, data: [] u8) -> Result(u32, Error) {
    // ...
}

// ...

The consumer of this abstraction may choose to use the method call syntax (connection->send("...")), or call the procedures explicitly (Connection.send(&connection, "...")).

Pragmatic

Onyx is not a small language. There are several big topics to learn to understand everything about the language. This fact may be a turn-off to some, especially in today's golden age of minimalist languages like Gleam, Roc, Odin, and Zig. While these languages are fabulous, Onyx has a different design goal: to provide a language capable of expressing many programming styles.

Different problems require different solutions. For this reason, Onyx has support for multiple styles of programming. Everything from Elixir-inspired functional syntax using the pipe operator to an object-oriented method call syntax is possible. Sometimes, a functional pipeline style conveys the meaning of a computation better than an explicit for-loop. Sometimes, defining methods on a type reads better than a collection of related procedures. Onyx trusts the programmer to make good decisions on what works well for their problem and tries its best to get out of the way.

Learn more!

If any of this sounds interesting to you, learn more about Onyx from the docs!

© 2020-2024 Brendan Hansen