---
title: The Onyx Programming Language
location: Moscow, Russia
---
In the [previous article](/posts/2019-08-01-system-programming-in-2k20) I successfully justified my desire to build yet another system programming language.
Unlike _others_, I want to do it right from the very beginning.
Meet Onyx!
Table of Contents
${toc}
## The Onyx programming language

Onyx is a general-purpose statically typed programming language suitable both for application and system programming.
### Top-Down Features
Onyx syntax is inspired by **C-family** languages, such as Ruby, C++ and even Rust I'm sorry.
Onyx imposes powerful **inference** mechanisms.
The rule is generally "infer unless ambiguous", with meaningful defaults.
Onyx is a **memory-safe** language.
There are multiple levels of enforced safety, with `unsafe` being the minimum safety level allowing for extreme optimizations.
This opens great opportuinites for powerful abstractions.
Unlike [_other_](https://www.rust-lang.org/){.secret-link} languages, Onyx treats **pointer arithmetic** as a first-class use-case, but with memory safety.
The concept is expressed in raw but typed pointers containing a memory scope.
For example, it is not possible to safely pass a local pointer to an outer scope.
Moreover, pointers preserve low-level features like address spacing and alignment.
It is extremely easy to do **interoperability** in Onyx.
In fact, Onyx is similar to C++ in this sense: C code is considered a part of the program (with minor differences), and it is simple to export Onyx code as a shared library.
Programs written in Onyx are **cross-platform** by default in the sense of that there are no target-dependent features in the language itself: no threading, no memory control.
But fear not, thanks to powerful abstractions, these are likely to be already implemented by someone else!
Onyx introduces **powerful macros** written in Lua.
It allows to re-use existing Lua code and have full access to the compilation context thanks to easy debugging with Lua.
Onyx has **[simple-to-understand](https://twitter.com/vladfaust/status/1299116755596566528){.secret-link} lifetime and moving** concepts naturally built into the language.
Instead of fighting with a borrow checker, simply get an address of a variable: a compiler would not allow you to mis-use it.
Classes may have a finalizer defined and thus have **automatic resource control**.
Onyx implements real **traits** as [composable units of behaviour](https://en.wikipedia.org/wiki/Trait_(computer_programming)) thanks to powerful function management tools like aliasing, implementation transferring, un-declaring and renaming.
Classes and traits together impose **object-oriented** capatibilites of the language.
Onyx has a concept of **generics**.
Specializations of generic types may have different members, and evaluate delayed macros.
Specializations of functions with generic arguments may return differnt values and also evaluate delayed macros.
Functions may be **overloaded** by arguments and return values.
Onyx has a concept of **annotations**, which may be applied to variables and functions.
The language defines a set of now-commonly used **arithmetic types**, including SIMD vectors, matrices and tensors, floating and fixed binary and decimal numbers, brain and tensor floats, ranges and ratios.
Onyx contains a number of **utility types**, such as unions, variants, tuples, anonymous structs, lambdas and runnable blocks of code.
Exceptions are designed to be truly **zero-cost** to enable exception flow in Onyx programs.
### Examples
Let's jump into some code samples.
This would give a brief overview of how Onyx programs look like.
#### Hello, world!
This is a very basic program written in Onyx:
```text
import "stdio.h"
export int main() {
final msg = "Hello, world!\0"
unsafe! $puts(&msg as $char*)
return 0
}
```
In the example, a C header named `"stdio.h"` was imported into the Onyx program.
Now Onyx is aware of assembly functions declared by this header.
Later on, one of these functions, `puts` is called directly from Onyx.
An Onyx compiler can not give any safety guarantees in regard to called assembly functions, therefore the call must be wrapped in an `unsafe!` statement.
If something weird happens, a developer may simply `grep` the program source code for `unsafe!` statement to quickly narrow to potentially hazardous areas of code.
Imported entities are referenced with preceding `$` symbol to distinguish them from those declared in Onyx context.
A constant named `msg` is defined by the `final` statement.
The type of `msg` is inferred to be `String`, i.e. a [UTF-8](https://en.wikipedia.org/wiki/UTF-8)-encoded array of [code units](https://en.wikipedia.org/wiki/Code_unit) containing 14 elements.
Then, address of the `msg` constant is taken.
The resulting object of taking an address would be `String*lr0`, which is a shortcut to `Pointer, Scope: :local, Readable: true, Writeable: false, Space: 0>`.
Do not be intimidated, though!
Thanks to inference, shortucts and meaningful defaults, you'll rarely have to use full types.
The pointer to `msg` is then coerced to C type `char*`.
Such a coercion would be unsafe, and a compiler would normally panic.
However, the coercion is already within unsafe context itself thanks to the wrapping `unsafe!` statement.
No need to write `unsafe!` again.
The program above is normally compiled by an Onyx compiler, such as [`fnxc`](https://github.com/fancysofthq/fnxc), into an object file.
The object file declares the exported `int main(void)` prototype, which must be pointed to at as a entry function by a system linker.
Thankfully, this tedious operation is likely to be automatically handled by a higher-level build tool, such as [`fnx`](https://github.com/fancysofthq/fnx).
Note that Onyx does not have any implicit `__onyx_main` function, which effectively restricts non-trivial automatic static variable initialization and finalization.
But in return it makes the emitted code predictable and portable.
#### Using a standard library
An Onyx compiler is not required to implement any sort of OS-specific standard library.
Instead, the standard library Standard is specified elsewhere (spoiler alert: by [the Onyx Software Foundation](#the-onyx-software-foundation)).
A standard library is ought to be used as a common package and required as any other from your code.
Again, by default an Onyx program does not depend on any OS-specific features.
The example above could be abstracted into this when using a standard library implementation:
```text
require "std"
export void main() {
let msg = "Hello, world!\0"
try
Std.puts(&msg)
catch
Std.exit(1)
end
}
```
Now, the code is perfectly safe.
Even passing of `&msg` is allowed, because `Std.puts` has an overload accepting a `String*cr`, i.e. a read-only pointer with _caller_ scope, and a pointer with _local_ scope may be safely cast to to _caller_ scope upon passing to a function!
Also note that `msg` is now a variable, as it is defined with `let` statement.
Taking address of `msg` would return `String*lrw0`.
Notice the `w` part?
The pointer is now writeable.
And it is perfectly legit to pass a writeable pointer as a read-only argument: it would be coerced down to a read-only pointer within the callee.
#### Exceptions
We had to wrap the `Std.puts` call into the `try` block, as it could throw some system exception.
The cause is that an `export`ed function must guarantee to never throw an exception, that's why we wrapped it.
The `Std.exit` function is declared as `nothrow`, so we can leave it as-is.
But what if we wanted to inspect the backtrace of the possible exception?
Well, the language Standard states that a backtrace object must implement `Endful` trait.
This is a truncated source code of the trait:
```text
struct Location
val path : String*sr # A static pointer
val row, col : UBin32
end
trait Endful<T>
decl push(value: T)
decl pop() : T
decl pop?() : T?
end
```
Let's implement some `Stack` type to hold the backtrace.
::: spoiler ⚠️ A big chunk of code!
```text
# A stack growing upwards in memory.
#
# The `~ %n` part means "accept a natural
# number literal as a generic argument".
class Stack<Type: T, Size: Z ~ %n>
# This class derives from this trait.
derive Endful<T>;
# Define two empty structs.
struct Overflow;
struct Underflow;
# Do not finalize this variable
# in the end of stack lifetime.
#
# `@[NoFinalize]` is application
# of an unsafe annotation.
unsafe! @[NoFinalize]
final array = unsafe! uninitialized T[Z]
# A getter makes the variable read-only
# from outside, but writeable inside.
get size : Size = 0
# A class, unlike a struct, does
# not have a default initializer.
def initialize();
# But class allows to
# define a finalizer!
def finalize()
# Only those stack elements which
# are alive shall be finalized.
size.times() -> unsafe! @finalize(array[&])
end
# Push a value into the stack.
# Throws in case of stack overflow.
# It implements `Endful<T>:push`.
impl push(val)
# The scary operator is expanded to
# `size <<= size ^+ 1`, meaning
# "push-assign to `size` a saturated
# sum of it with 1".
#
# Push-assignment returns the old
# value instead of the new one.
if (size ^+<<= 1) < Z
# `<<-` moves the value from `val`
# into the array, "ejecting" the
# old array value. But at this point,
# the old array value is already
# finalized, so we explicitly disable
# it finalization here.
unsafe!
@nofinalize(array[size - 1] <<- val)
end
else
throw Overflow()
end
end
# Pop a value from the stack.
# Throws in case of stack underflow.
#
# Note the alternative syntax to
# reference the declaration.
impl ~Endful<T>:pop()
return pop?() ||
throw Underflow()
end
# Pop a value from the stack
# if it is not empty.
# Returns `Void` otherwise.
impl nothrow pop?()
# Expands to `size <<= size ^- 1`.
if size ^-<<= 1 > 0
# We don't copy the array element,
# but move it from the array.
#
# A copy of bytes of the element are
# preserved in the array, but we
# consider it already dead, a corpse.
#
# That's why we don't finalize it
# in the `push` implementation.
return unsafe! <-array[size]
else
return Void
end
end
end
```
Oof, sorry for such a seemingly complex piece of code.
But I had to do it, sooner or later!
:::
But wait!
Thankfully, the language already comes with a `Stack` implementation, so we don't need to write it in our code.
A `try` statement has optional `with` clause accepting a pointer to a `Endful` implementation.
The example above may be rewritten like this:
```text
require "std"
export void main() {
let msg = "Hello, world!\0"
final backtrace = Stack<Location, 32>()
try with &backtrace
Std.puts(&msg)
catch
while final loc = backtrace.pop?()
# Could've also made use of
# `loc.row` and `loc.col`...
Std.puts(loc.path)
end
catch
# An unrecoverable error ☹️
Std.exit(1)
end
}
```
Now we can inspect the exception backtrace!
#### An HTTP server example
It is considred a good tone to demonstrate on how to build a simple echoing HTTP web server in your language.
This would also be my "sorry" for the big-ass `Stack` implementation above.
The thing is that running a web server is architecturally different on different target platforms.
An implementation on Linux could make use of raw sockets, an implementation on Windows could make use of the win32 `"http.h"` header etc.
Therefore, the standard library would not contain a web server implementation.
Instead, some third-party package should be used, which would inevitably be a plenty of!
Let's imagine we've found one satisfying our needs.
That's how it could look like:
```text
require "std"
require "http" from "mypkg"
export int main () {
final backtrace = Stack<Location, 32>()
try with &backtrace
final server = HTTP::Server()
server.get("/") ~> do |env|
# Read the request into a
# local `Std::Twine` instance.
final body = env.request.read()
# Write the twine
# into the response.
env.response << body
end
server.listen("localost", 3000)
catch |e|
Std.puts("Caught \@{{ e }}\n")
while final loc = backtrace.pop?()
Std.cout << "At " <<
loc.path << ":" <<
loc.row << ":" << "\n"
end
Std.exit(1)
catch
# Unrecoverable error 👿
Std.exit(2)
end
}
```
Thanks to powerful abstractions and type inference, you won't need to manualy use `socket` each time you want to spin up a web server on Linux!
Oh, by the way, did you notice the `\@{{ e }}`\* thing?
It was me, Dio macro!
\* TODO: Fix this HTML weirdness.
#### Macros
TODO: Example on macros.
Simple macros.
Breakpoints in compile-time, code generation based on external configurations, e.g. of ORM models from SQL migration files.
## The Onyx Software Foundation
TODO: List all the standards.
Official standard development process with RFSs, voting, community champions.
Canonical package hosting platform with built-in funding based on the source-on-demand model.