The using
Keyword
We take great inspiration from the language Jai, designed by Jonathan Blow. Our
is largely based on the design of the using
keyword in Jai, further overloaded for introducing implicit conversion functions.
using
using
Function Arguments
Let's begin with this snippet:
struct vec2 { x: f32; y: f32 }
struct Player { position: vec2 }
fn move(ref player: Player, dist: vec2) {
player.position.x += dist.x
player.position.y += dist.y
}
In a language like C++, if
were a method of move
, one would be able to abbreviate the repetitous Playerplayer.position.*
to just position.*
. We can achieve the same by
the using
argument to player
:
move
fn move(using ref player: Player, dist: vec2) {
position.x += dist.x
position.y += dist.y
}
You can be
as many function arguments as you need.
using
Any ambiguity is a hard error, so you won't be able to set
e.g. on multiple variables of the same type, or use it to shadow a local or global of the same name, etc.
using
using
Variable Declarations
works with any variable declaration, not just function arguments:
using
fn move(ref player: Player, dist: vec2) {
using ref pos = player.position
x += dist.x
y += dist.y
}
Since the new
local is never used by name, we can make it anonymous:
pos
fn move(ref player: Player, dist: vec2) {
using ref _ = player.position
x += dist.x
y += dist.y
}
... or skip the variable declaration altogether:
fn move(ref player: Player, dist: vec2) {
using player.position
x += dist.x
y += dist.y
}
using
Struct Members
In an object-oriented language, one might consider having
inherit from Player
to avoid having to type Position*.position
repeatedly. We can cleanly achieve the same by
the using
field in position
.
Player
struct Player { using position: vec2 }
fn move(using ref player: Player, dist: vec2) {
x += dist.x
y += dist.y
}
You can be
as many fields as you need, and there is no limit on using
depth. Aside from cutting down on typing, using
can help make code more refactorable, by making fields moveable between a set of related structs without a lot of rework elsewhere.
using
using
Functions
Imagine you don't control the definition for
above, or want to introduce the Player
-> Player
conversion temporarily, in some private scope.
Position
You can achieve the same by
a function:
using
struct Player { position: vec2 }
using fn getPlayerPosition(ref p: Player) p.position
fn move(ref player: Player, dist: vec2) {
player.x += dist.x
player.y += dist.y
}
Note that although here it doesn't make much of a difference, you can introduce this conversion edge in any scope:
fn move(ref player: Player, dist: vec2)
{
using fn getPlayerPosition(ref p: Player) p.position
player.x += dist.x
player.y += dist.y
}
Finally, to come full circle, yet another way to achieve the same thing would be by
a nullary function:
using
fn move(ref player: Player, dist: vec2)
{
using fn getPlayerPosition() player.position
x += dist.x
y += dist.y
}
Currently,
a function will invoke it every time it is needed because of a missing or mismatched argument at some callsite, which will reapply its side effects, if any. Thus, using
functions are best kept pure which helps with common subexpression elimination.
using