Optional values
In the examples with while loops, we already saw this syntax:
const name: ?type;
A question mark ? before the data type indicates that it is an optional value - that is, a potentially null value.
We can turn any type of value into an optional by placing this question mark in front of it, as we do in this case:
|
n_efficiency_per_meter: ?f32, // optional value |
We use the orelse expression to assign a specific value when we hit a null.
|
self.n_efficiency_per_meter orelse HydroTower.N_EFFICIENCY_PER_METER, |
Combining optional values and the orelse expression allows us to define default values for parameters:
|
const o_tower = HydroTower{ .n_height = 4.5, // no specific efficiency is set - optional value .n_efficiency_per_meter = null, }; |
The path the water had to travel from a small weir in the river up to the castle, to the engineer’s misfortune, was not a straight line. The inventor, after many calculations, tests, and adjustments, drawing on his knowledge, designed the system as a series of towers. These operated solely using the force of the water itself.
It was a very complex task, since any increase in water loss in one tower affected the entire system. Fortunately, the inventor had prior experience in precision machinery, having been the king’s relojero (clockmaker).
hydrotower_system.zig |
|
const std = @import("std"); const print = std.debug.print; const ArrayList = std.ArrayList; // a tower with oscillating buckets const HydroTower = struct { const N_EFFICIENCY_PER_METER: f32 = 0.98; n_height: f32, fn lift_water(self: *const HydroTower, n_input: ?f32) struct { f32, f32 } { return .{ (n_input orelse 0) * std.math.pow( f32, HydroTower.N_EFFICIENCY_PER_METER, self.n_height, ), self.n_height, }; } // simulate a tower height built between 3.5 and 5m fn get_height_btw(n_min: f32, n_max: f32) f32 { return round2(get_rand(n_min, n_max)); } }; // river flow carried to the tower const WaterFlow = struct { n_liters_per_second: f32, fn get_fps(self: *const WaterFlow) f32 { const n_min = self.n_liters_per_second * 0.90; return round2(get_rand(n_min, self.n_liters_per_second)); } }; // inline function to round to 2 decimals inline fn round2(n_x: f32) f32 { return @floor(100 * n_x) / 100; } // inline function to get a random number between n_min and n_max inline fn get_rand(n_min: f32, n_max: f32) f32 { return std.Random.float(std.crypto.random, f32) * (n_max - n_min) + n_min; } pub fn main() !void { print("~~~~~\n", .{}); // flow of 2L per second const o_river = WaterFlow{ .n_liters_per_second = 2 }; var gpa = std.heap.GeneralPurposeAllocator(.{}){}; const o_alloc = gpa.allocator(); // initialize the towers var a_towers:ArrayList(HydroTower) = .empty; defer a_towers.deinit(o_alloc); // unnecessary in main const n_castle_at: f32 = 90; var n_total_height: f32 = 0; var n_i: usize = 0; while (n_total_height < n_castle_at) : (n_i += 1) { // depending on the terrain, the height will vary const n_tower_height = HydroTower.get_height_btw(3.0, 5.0); n_total_height += n_tower_height; const o_tower = HydroTower{ .n_height = n_tower_height, }; // add a tower to the list try a_towers.append(o_alloc, o_tower); } print("Built {} towers\n", .{a_towers.items.len}); var n_riverflow: f32 = o_river.get_fps(); var n_total_h: f32 = 0; for (a_towers.items, 0..) |o_tower, n_ix| { n_riverflow, const n_ht = o_tower.lift_water(n_riverflow); n_total_h += n_ht; print("{}: {}L at {}m\n", .{ n_ix + 1, round2(n_riverflow), round2(n_total_h) }); } } |
|
$ zig run hydrotower_system.zig |
|
~~~~~ Built 21 towers 1: 1.7L at 3.48m ... 21: 0.29L at 90.18m |