Dyon, a scripting language for Rust game engines, is getting close to v0.8. For the v0.8 release, I want to polish the type checker a bit before publishing on crates.io, which might take some time. This is why I write this article about the features beforehand, so you know what to expect from the next release.

A summary of Dyon v0.7

Dyon does not use a garbage collector, but a lifetime checker. There is no borrow checker like in Rust. You annotate arguments with the lifetime of another argument that it outlives:

fn foo ( a : 'b , b ) { ... } // `a` outlives `b`

There is a 'return lifetime to tell that an argument outlives the return value:

fn foo ( a : 'return ) -> { ... }

You can assign to return like it was a normal local variable, then let the function exit the scope.

fn foo () -> { return = 3 }

Dyon has a similar object model to Javascript. You declare variables with := :

a : = [ 1 , 2 , 3 ] // array b : = { x : 0 , y : 0 } // object c : = true // bool d : = 5.3 // float with 64 bit precision

A variable with same name shadows the previous one:

a : = 0 a : = false println ( a ) // prints `false`

To mutate a variable, you use = which performs a check that the type is the same:

a : = 0 a = false // ERROR: Expected assigning to a `bool`

Objects can override the type of a property with := , which also inserts a key if it does not exists:

a : = {} a .x : = 0 // Inserts `x: 0`

There is no null , but you can use opt or res , which are similar to Option and Result in Rust:

a : = some ( 5.3 ) b : = ok ( 5.3 )

You can propagate errors with ? , which is similar to the try! macro in Rust:

fn foo () -> { x : = bar () ? // return error if something wrong happened in `bar` return ok ( x > 3 ) } _ : = unwrap ( foo ()) // Shows a trace if something went wrong.

Example of an error message:

--- ERROR --- main (source/test.dyon) unwrap Something went wrong! In function `foo` (source/test.dyon) 6,10: x := bar()? 6,10: ^ 2,17: _ := unwrap(foo()) 2,17: ^

Dyon supports declaring functions the same way as in mathematics:

f ( x ) = x / ( x - 1 ) pi () = 3.14

When mutating an argument, you put mut in front of it:

fn foo ( mut a ) { ... }

When calling a function that mutates an argument, you also put mut in front of the argument:

a : = [ 1 , 2 , 3 ] foo ( mut a )

Named argument syntax based on snake case:

foo ( bar : x ) // named argument syntax foo_bar ( x )

If expression:

a : = if b < c { 0 } else { 1 }

Traditional For loop:

for i : = 0 ; i < 10 ; i += 1 { ... }

Short For loop:

for i 10 { ... } for i [ 2 , 10 ) { ... } // with offset

Mathematical loops, with unicode symbol alternatives:

x : = sum i { list [ i ] } x : = ∑ i { list [ i ] } y : = any i { list [ i ] == 0 } y : = ∃ i { list [ i ] == 0 } z : = all i { list [ i ] > 0 } z : = ∀ i { list [ i ] > 0 } min_val : = min i { list [ i ] } max_val : = max i { list [ i ] }

Infinite loop, like in Rust:

loop { ... } 'name : loop { break 'name } // break out of loop `name`

Dynamic modules allows a flexible way of organizing code:

m : = unwrap ( load ( "script.dyon" )) call ( m , "main" , [])

Import modules to the prelude when loading a new module:

m : = unwrap ( load ( source : "script.dyon" , imports : [ window , graphics ]))

Optional type system, that complains when proven wrong:

fn could ( list : []) -> f64

Go-like coroutines for multi-threading, using the go keyword:

t : = go do_something ( a , b ) res : = unwrap ( join ( thread : t ))

4D vectors:

a : = ( x , y ) // same as `(x, y, 0, 0)` b : = # ff00aa // HTML hex color encoded as 4D vector

Upcoming features in v0.8

dobkeratops had a brilliant idea: What if For loops inferred the range from the body, just like in mathematical notation?

// No need to type `len(list)`, because Dyon figures it out by looking at the code. for i { foo ( list [ i ]) }

This gave me another idea, which was to pack loops of same kind together:

// Set random weights in neural network. for i , j , k { tensor [ i ][ j ][ k ] = random () }

Then to make ∃ / any and ∀ / all loops work nicely min / max , I added a feature called “secrets” that gives you the indices from any composition of these loops:

x : = ∃ i { max j { list [ i ][ j ] } < 0.5 } if x { pos : = why ( x ) // `[i, j]` ... }

A secret propagates from the left argument of binary operators.

There is a why(bool) -> [any] and where(f64) -> [any] .

You can add information to a secret with explain_why(bool, any) -> bool and explain_where(f64, any) -> bool :

x : = any i { explain_why ( did ( person : person [ i ], said : "Doh!" ), "Are you Homer Simpson?" ) } if x { pos : = why ( x ) // `[i, question]` ask ( person : person [ pos [ 0 ]], question : pos [ 1 ]) // Asks "Are you Homer Simpson?" }

Dyon does not have globals, but uses a ~ to mark variables as “current object” for its scope:

fn main () { ~ settings : = init_settings () foo () } // `foo` calls `bar` without knowing about `settings` fn foo () { bar () } fn bar () ~ settings { // Can use `settings` as if it was a function argument. }

To make code scale with size of project, but work nicely with dynamic modules, I added ad-hoc types:

// `Character` is not declared, but has an inner type `{}`. fn new_character ( name : str ) -> Character {} { return { name : name } } fn greet_character ( character : Character {}) { println ( "Hi " + character .name + "!" ) println ( "How are you doing?" ) } // Can pass values of the inner type. greet_character ({ name : "Homer" })

This works for checking physical units:

fn main () { println ( km ( 3 ) + m ( 5 )) } fn km ( val : f64 ) -> km f64 { val } fn m ( val : f64 ) -> m f64 { val }

Addition of same ad-hoc types are allowed, but multiplication is not allowed for same ad-hoc types, since this often changes the physical unit.

Type mismatch: Binary operator can not be used with `km f64` and `m f64` 2,21: println(km(3) + m(5)) 2,21: ^

Dyon has a link type, which stores bool , f64 and str efficiently. It also puts them together faster than using arrays.

a : = link { "hi" 5 " " true " man show" } println ( a ) // prints `hi5 true man show`

You can use head and tail to process a link :

fn main () { a : = link { "hi" 5 " " true " man show" } loop { head : = head ( a ) if head == none () { break } println ( typeof ( unwrap ( head ))) a = tail ( a ) } /* string number string boolean string */ }

A link inside a link gets flattened, which is useful when generating text documents.

For example, you could have a smart html sanitizing function that understood tags, and then you could generate a web page any way you liked, as long as you use separate tokens for tags. Perhaps a nice idea for a web framework?

fn main () { title : = "Welcome to my website" data : = get_data () loop { wait_for_request () respond ( web_page ( html ( link { "<html>" "<body>" "<h1>" title "</h1>" menu ( data ) overview ( data ) "</body>" "</html>" }))) } } // Sanitize html. fn html ( input : link ) -> link { ... } fn menu ( data : Data {}) -> link { ... } fn overview ( data : Data {}) -> link { ... }

4D vectors now has a “unpacking” feature:

fn main () { v : = ( 1 , 2 ) // Call `foo` with 2 arguments. foo ( xy v ) } fn foo ( x : f64 , y : f64 ) { ... }

This works well with snake_case named argument syntax:

fn main () { v : = ( 1 , 2 ) foo ( x_y : xy v ) } fn foo_x_y ( x : f64 , y : f64 ) {}

You can also swizzle vector components:

v : = ( 1 , 2 , 3 ) u : = ( zy v ,) // (3, 2)

And you can use vec2 / vec3 / vec4 un-loops to unroll the loop and set rest of components to 0:

v := vec3 i i+1 // `(1, 2, 3, 0)`

There is a s(v: vec4, ind: f64) -> f64 function that returns a component of a 4D vector.

I also added some macros to make embedding with Rust easier:

dyon_fn! { fn say_hello () { println! ( "hi!" ); }}

You still need to register functions, because Dyon needs type + lifetime information. I recommend checking out the functions example for more information.

That is all for now!