using a value
The most basic use of using is to bring the fields of a struct variable into the current scope. After using, you can refer to the struct's fields as if they were local variables:
Spaceship :: struct {
fuel_level := 85.0;
model := "falcon";
num_engines := 4;
hull_type := "titanium";
has_shields := false;
}
ship: Spaceship;
using ship;
num_engines = 6; // Same as ship.num_engines = 6
model = "interceptor"; // Same as ship.model = "interceptor"
Both the short form and the explicit dereference form refer to the same underlying data:
ship.fuel_level = 50;
fuel_level = 30; // Overwrites the value set on the line above.
print("%\n", ship.fuel_level); // 30
print("%\n", fuel_level); // 30
A common pattern is to scope the using inside a block so the imported names do not leak into the surrounding code:
other: Spaceship;
{
using other;
fuel_level = 100;
model = "cruiser";
num_engines = 12;
}
// fuel_level, model, etc. are no longer in scope here.
You can also put the using directly on the declaration:
using ship: Spaceship;
print("model is %, % engines.\n", model, num_engines);
using also works with enum declarations, bringing the enum's values into the current scope:
Planets :: enum {
MARS;
VENUS;
JUPITER;
}
using Planets;
x := MARS; // Same as Planets.MARS
using a pointer
using works on pointers to structs just as it does on values. The fields are accessed through the pointer transparently:
build_spaceship :: () -> *Spaceship {
result := New(Spaceship);
return result;
}
using ship := build_spaceship();
print("type_of(ship) is %\n", type_of(ship)); // *Spaceship
num_engines *= 2;
num_engines += 1;
print("engines: %\n", num_engines);
using an import
When you bind an #import to a name, the module's exported identifiers are accessed through that name. You can apply using to make those identifiers available directly:
// 'using' on the declaration itself:
using Random :: #import "Random";
// Or bind first, then 'using' separately:
Hash_Table :: #import "Hash_Table";
using Hash_Table;
// Bind without 'using':
Crc :: #import "Crc";
With the imports above, you can refer to identifiers from Random and Hash_Table with or without the module prefix. For Crc, because we did not apply using in the global scope, you must always write Crc.some_identifier:
a := Random.random_get(); // Explicit dereference.
b := random_get(); // Also works, because we used 'using'.
result := Crc.crc64("Hello, Sailor!"); // Must use prefix.
// result := crc64("Hello, Sailor!"); // Error: crc64 is not in scope here.
You can also using a named import inside a procedure to limit how far the names spread:
do_stuff :: () {
using Crc;
result := crc64("Hello"); // Works inside this procedure.
}
// crc64 is not available out here.
using in a procedure
You can put using on a procedure parameter, which brings the struct's fields into the procedure body. This gives a style similar to methods in object-oriented languages, where the procedure feels devoted to manipulating one particular data structure:
upgrade_spaceship :: (using ship: *Spaceship) {
num_engines *= 3; // Same as ship.num_engines *= 3
has_shields = true;
model = sprint("% mark II", model);
}
ship: Spaceship;
upgrade_spaceship(*ship);
print("ship is: %\n", ship);
using in a struct
Inside a struct declaration, using on a field promotes that field's members into the parent struct's namespace. This allows you to access the inner struct's fields directly through the outer struct:
Coordinate :: struct {
x, y, z: float;
}
Unit :: struct {
using location: Coordinate;
id: s64;
}
Robot :: struct {
using unit: Unit;
serial_code: string;
weight_in_kg: float;
}
bot: Robot;
bot.id = 1337; // Same as bot.unit.id
bot.x = 3; // Same as bot.unit.location.x
bot.y = 9; // Same as bot.unit.location.y
The using is multi-level: Robot uses Unit, and Unit uses Coordinate, so x, y, and z are all accessible directly from a Robot. All of the following refer to the same data:
print("%\n", bot.location); // The Coordinate
print("%\n", bot.unit.location); // Same thing
print("%, %, %\n", bot.x, bot.y, bot.z); // Same fields, accessed directly
The accepted forms for using in a struct are:
using var_name : Type;using #as var_name : Type;using,except(name_1, ..., name_n) var_name : Type;using,only(name_1, ..., name_n) var_name : Type;
This pattern provides composition-based reuse similar to inheritance in object-oriented languages. If you are familiar with subclassing, using in a struct gives you the ability to access a "parent" struct's fields directly, without requiring a class hierarchy. This can be combined with #as to also allow implicit casting from the outer type to the inner type:
Vehicle :: struct {
make: string;
}
Truck :: struct {
using #as v: Vehicle;
payload_capacity: float;
}
t: Truck;
t.make = "Ford";
v: Vehicle;
v = t; // Implicit cast from Truck to Vehicle, enabled by #as.
print("%\n", v); // {"Ford"}
Without #as, the implicit cast is not allowed:
Motorcycle :: struct {
using ve: Vehicle; // No #as
top_speed: float;
}
moto: Motorcycle;
moto.make = "Ducati"; // Field access works fine.
v: Vehicle;
// v = moto; // Error: Type mismatch. #as is required for this cast.
Dealing with Name Collisions
If you try to using two things that have fields with the same name, you get a compile-time error for the redeclared identifier:
Helicopter :: struct {
model := "Apache";
}
using ship: Spaceship;
// using heli: Helicopter; // Error: 'model' is already declared (from ship).
The same error occurs if you using two instances of the same type:
// using other: Spaceship; // Error: all field names collide with ship.
using,except
If you want to exclude specific field names from being imported, use the except modifier. The excluded fields are still accessible through explicit dereference, they just are not promoted:
using,except(model) heli: Helicopter; // Everything except 'model' is imported.
// model still accessible as heli.model
You can also do using,except on a named import to act like a regular import statement but have the ability to not import a certain identifier that you know will collide.
using,only
The opposite of except: only the listed names are imported:
using,only(pitch, yaw) rotation: Gyroscope; // Only pitch and yaw are promoted.