Beta Release 0.1.10

This release brings standard library uniformity, minor syntax changes, and JavaScript interop! You can now easily interface with the DOM and other JS APIs from directly within Onyx.

JavaScript Interop

Onyx now has a proper JavaScript interop layer, which makes using Onyx from JavaScript easier than ever. This interop comes in two parts: the #js directive and the core.js package.

The #js directive is used to include JS source code in the project. When any #js directive is encountered in the code, an additional JS file will be generated by the compiler.

#js has the following syntax.

// Include JS from string literal
#js """
    // Raw JS source code here

// Inlcude JS from file
#js #file "./source.js"

// Specify order of JS partial with an int literal
// Lower numbers are inserted first
#js #order 100 #file "./source.js"

This by itself does not help you interop with JavaScript, but it is required to have the compiler also generate the additional source code to make your project work. In fact, you may never have to use it directly, as it is more intended for library authors.

To actually interop with JS, there is now the core.js package. This package was inspired by Go's syscall/js, which provides a thin abstraction layer over JavaScript's common operations, like function calls, new and obj["foo"].

Here is a simple example/demo that creates a button on the page, and when it is clicked, alerts with a message.

use core.js

main :: () {
    document := js.Global->get("document");

    button := document->call("createElement", "button");
    button->set("innerHTML", "Click me!");
    button->call("addEventListener", "click", js.func((this, args) => {
        js.Global->call("alert", "Hello from Onyx!");

        return js.Undefined;

    document->get("body")->call("appendChild", button);

To use this in on your page, you can use the following JavaScript. It will load the JavaScript file generated by the compiler, and use the Onyx class to load and link the WebAssembly binary, from which you can call start to start the program.

<script type="module">
    import Onyx from "/out.wasm.js"
    (await Onyx.load("/out.wasm")).start()

Finally, compile the code with the -r js flag.

onyx build -r js program.onyx

While this is a very low-level interface to JavaScript, it does enable Onyx to be used in a whole new class of applications on the web. More auxiliary packages can be developed to provide specialized APIs for coming things such as the DOM, WebSockets, or WebGL.

Deprecation of #inject

As I have written more Onyx code, I have realized I use #inject all the time. It has become so ubiquitous in my code that I experimented with extending the parser so the #inject directive would not be required, and I loved it. For this reason, #inject is being deprecated.

Now you can simply add a new scoped binding like you would with any other binding.

Dog :: struct {}

// No #inject here!
Dog.speak :: (d: &Dog) {

Note that without the #inject keyword, there is no equivalent of the block form. Each scoped binding must be specified individually on a new line. I prefer this, as it prevents what I consider unnecessary indentation.

In a future release yet to be determined, the #inject directive will be removed entirely, but for now all programs with it will still compile.

Array and Slice structures

There has been a long running inconsistency in Onyx where dynamic arrays and slices were not like other types, in that they could not have methods and required you to use functions defined in the standard library from the core.array and core.slice packages.

This has finally been addressed with the addition of the Array and Slice builtin structures. They serve as "namespaces" for methods in dynamics arrays and slices respectively.

The core library now defines all dynamic array functionality as methods on Array and all slice functionality as methods on Slice. All existing definitions from core.array and core.slice still exist to maintain backwards compatibility, but the new locations of these definitions should be preferred.

// No need to use anything :)

main :: () {
    arr := make([..] i32);

    for i in 0 .. 10 {
        // Use as a method.

        // Use a static function on Array.
        Array.push(&arr, i);

    logf(.Info, "Array: {}", arr);

These changes represent a larger move towards standardizing the pattern that associated procedures for a type are defined as bindings within the type's scope. That way, the consumer of library can either choose to use the procedure as a "method", i.e. x->proc(y), or as a standard procedure prefix with the type name, i.e. X.proc(&x, y). When the procedure lives in a package, they are forced to use the standard procedure syntax.

Minor syntax additions

Auto-disposing locals

A very common pattern in Onyx is to create/allocate a resource, then defer the release/free of the resource on the next line. As an experiment, there is now a way to automate the freeing of a resource, called auto-disposing locals (I'm still working on the name...).

To mark a local variable as auto-disposing, simply place the use keyword in front of its declaration.

main :: () {
    use arr := make([..] i32);

This code will automatically insert a deferred call to the builtin overloaded procedure __dispose_used_local, which can be overloaded to define how to dispose of the resource. It has the delete procedure in the list of overloads, so anything you can call delete on, you can use. This is the equivalent code without use.

main :: () {
    arr := make([..] i32);
    defer __dispose_used_local(&arr);

As stated before, this feature is experimental and probably not perfect yet, but after using the using keyword in C#, I wanted to have something similar in Onyx.

Inclusive Range (..=)

As a small convenience, there is now an inclusive range operator, ..=. This operator is equivalent to x .. (y + 1).

While a small addition, it cleans up a lot of ugly + 1s around many codebases.

Revamped CLI

The command-line interface got a major face-lift in this update. It features new shorthand for common commands like build and run. It also includes colors on Linux and MacOS. This might not seem worth mentioning but these little improvements add to the overall developer experience and make Onyx feel more polished and production-ready.


To update to the newest version of Onyx simply use the same install script found on the homepage. It will automatically detect your previous install and will override it with the new version.

$ sh <(curl -sSfL)

In the future, you will be able to use the onyx self-upgrade command!

Full Changelog






© 2020-2024 Brendan Hansen