Type ‣ Struct ᴾᴴᴾ - chung-leong/zigar GitHub Wiki
JavaScript | PHP
In the Zig language, a struct is a composite data type that hold multiple variables (referred to as "fields"). It's analogous to object in PHP. When a Zig struct is exported, it can be instantiated in PHP through its constructor. The constructor expects initial values for the struct fields given as named arguments. Initializers can be omitted for fields with default values.
pub const User = struct {
id: u64,
name: []const u8,
email: []const u8,
age: ?u32 = null,
popularity: i64 = -1,
};
<?php
$m = zigar_use(__DIR__ . '/struct-example-1.zig');
$user = new $m->User(
id: 1234,
name: "Bigus Dickus",
email: "[email protected]",
);
echo "{$user->id}\n";
echo "{$user->name}\n";
echo "{$user->email}\n";
echo "{$user->age}\n";
echo "{$user->popularity}\n";
1234
Bigus Dickus
[email protected]
-1
Initializers can also be provided in an associative array.
<?php
$m = zigar_use(__DIR__ . '/struct-example-1.zig');
$user = new $m->User([
'id' => 1234,
'name' => "Bigus Dickus",
'email' => "[email protected]",
]);
echo "{$user->id}\n";
echo "{$user->name}\n";
echo "{$user->email}\n";
echo "{$user->age}\n";
echo "{$user->popularity}\n";
1234
Bigus Dickus
[email protected]
-1
You can also assign an associative array to a struct:
const std = @import("std");
pub const Address = struct {
street: []const u8,
city: []const u8,
state: [2]u8,
zipCode: u32,
};
pub const User = struct {
id: u64,
name: []const u8,
email: []const u8,
age: ?u32 = null,
popularity: i64 = -1,
address: ?Address,
pub fn print(self: User) void {
std.debug.print("Name: {s}\n", .{self.name});
std.debug.print("E-mail: {s}\n", .{self.email});
if (self.age) |age| {
std.debug.print("Age: {d}\n", .{age});
}
std.debug.print("Popularity: {d}\n", .{self.popularity});
if (self.address) |address| {
std.debug.print("Street: {s}\n", .{address.street});
std.debug.print("City: {s}\n", .{address.city});
std.debug.print("State: {s}\n", .{address.state});
std.debug.print("ZIP code: {d}\n", .{address.zipCode});
}
}
};
<?php
$m = zigar_use(__DIR__ . '/struct-example-2.zig');
$user = new $m->User(
id: 1234,
name: "Bigus Dickus",
email: "[email protected]",
age: 32,
address: null,
);
$user->address = [
'street' => '1 Colosseum Sq.',
'city' => 'Rome',
'state' => 'NY',
'zipCode' => '10001',
];
$user->print();
Name: Bigus Dickus
E-mail: [email protected]
Age: 32
Popularity: -1
Street: 1 Colosseum Sq.
City: Rome
State: NY
ZIP code: 10001
You can also pass an associative array to a function expecting a struct as its first argument (i.e. "self"):
<?php
$m = zigar_use(__DIR__ . '/struct-example-2.zig');
$m->User->print([
'id' => 1234,
'name' => "Bigus Dickus",
'email' => "[email protected]",
'age' => 32,
'address' => [
'street' => '1 Colosseum Sq.',
'city' => 'Rome',
'state' => 'NY',
'zipCode' => '10001',
],
]);
Iterating through struct fields
You can use PHP's foreach construct to loop through a struct's fields programmatically:
const ResponseType = enum { normal, partial, bad };
const Response = struct {
type: ResponseType,
size: usize,
code: u32 = 200,
bytes: [8]u8,
};
pub fn getResponse() Response {
return .{
.type = .normal,
.size = 512,
.bytes = .{ 1, 2, 3, 4, 5, 6, 7, 8 },
};
}
<?php
$m = zigar_use(__DIR__ . '/struct-example-3.zig');
$response = $m->getResponse();
foreach ($response as $key => $value) {
echo "$key = $value\n";
}
type = normal
size = 512
code = 200
bytes =
The behavior is different when a next function is present. The struct itself would be treated as
an iterator in that case.
Extern struct
An extern struct differs from a regular struct in that it has a well-defined memory layout. Individual fields are placed where programs written in the C language expect them to be. Consider the following code:
pub const ExternStruct = extern struct {
small_int: i16,
big_int: i64,
};
pub const RegularStruct = struct {
small_int: i16,
big_int: i64,
};
pub const ext_struct: ExternStruct = .{ .small_int = 123, .big_int = 4567890123 };
pub const reg_struct: RegularStruct = .{ .small_int = 123, .big_int = 4567890123 };
<?php
$m = zigar_use(__DIR__ . '/extern-struct-example-1.zig');
echo "Extern:\n";
$a = unpack("sint16/a6padding/Qint64", $m->ext_struct->__bytes);
echo "{$a['int16']}\n";
echo "{$a['int64']}\n";
echo "Regular (wrong):\n";
$b = unpack("sint16/a6padding/Qint64", $m->reg_struct->__bytes);
echo "{$b['int16']}\n";
echo "{$b['int64']}\n";
Extern:
123
4567890123
Regular (wrong):
30923
123
The Zig compiler places small_int of ExternStruct at offset 0 because that's how a C compiler
would do it. big_int get placed at offset 8 because i64 has an alignment of 8 and 8 is the
nearest position.
For RegularStruct, the Zig compiler follows its own approach, which is to place fields requiring
larger alignment further up front. big_int therefore ends up at offset 0 while small_int gets
offset 8.
Extern struct is generally what you need when dealing with binary data stored in a file.
Packed struct
A packed struct is commonly used when a struct contains mostly boolean variables:
pub const PackedStruct = packed struct {
state_a: bool = false,
state_b: bool = true,
state_c: bool = false,
state_d: bool = false,
state_e: bool = false,
state_f: bool = false,
state_g: bool = false,
};
pub const RegularStruct = struct {
state_a: bool = false,
state_b: bool = true,
state_c: bool = false,
state_d: bool = false,
state_e: bool = false,
state_f: bool = false,
state_g: bool = false,
};
pub const pac_struct: PackedStruct = .{};
pub const reg_struct: RegularStruct = .{};
<?php
$m = zigar_use(__DIR__ . '/packed-struct-example-1.zig');
print_r($m->pac_struct);
print_r($m->reg_struct);
echo strlen($m->pac_struct->__bytes), "\n";
echo strlen($m->reg_struct->__bytes), "\n";
PackedStruct Object
(
[state_a] =>
[state_b] => 1
[state_c] =>
[state_d] =>
[state_e] =>
[state_f] =>
[state_g] =>
)
RegularStruct Object
(
[state_a] =>
[state_b] => 1
[state_c] =>
[state_d] =>
[state_e] =>
[state_f] =>
[state_g] =>
)
1
7
A bool only really needs a single bit in order to represent true/false. A full byte is typically
used however. This is why reg_struct.dataView.byteLength gives us 7. Meanwhile, pac_struct is
able to keep the same information in just a single byte.
Zigar is designed to handle packed structs holding booleans and very small integers. It can do so in an efficient manner. It can handle pathological cases too:
pub const WeirdStruct = packed struct {
state: bool = false,
number: u128 = 123456789000000,
};
pub const weird_struct: WeirdStruct = .{};
<?php
$m = zigar_use(__DIR__ . '/packed-struct-example-2.zig');
print_r($m->weird_struct);
WeirdStruct Object
(
[state] =>
[number] => GMP Object
(
[num] => 123456789000000
)
)
The code above places a 128-bit integer a single bit from the beginning of the struct. No one in his right mind would use such an arrangement. But as you can see, Zigar is still giving you the expected result. The scenario that Zigar isn't capable of handling is where you place a complex type like struct or optional into a packed union at a non-byte-aligned position.
Packed struct with backing integer
A packed struct with a backing integer can be casted into a number or bigint:
pub const StructA = packed struct(u32) {
apple: bool = false,
banana: bool = false,
cantaloupe: bool = false,
durian: bool = false,
_: u28 = 0,
};
pub const StructB = packed struct(u64) {
agnieszka: bool = false,
basia: bool = false,
celina: bool = false,
dagmara: bool = false,
_: u60 = 0,
};
<?php
$m = zigar_use(__DIR__ . '/packed-struct-example-3.zig');
$a = new $m->StructA(apple: true, durian: true);
$b = new $m->StructB(agnieszka: true, basia: true);
debug_zval_dump((int) $a);
debug_zval_dump((int) $b);
debug_zval_dump($a == 9);
debug_zval_dump($b == 3);
int(9)
int(3)
bool(true)
bool(true)
The constructor of such struct will also accept a number or bigint as initializer:
<?php
$m = zigar_use(__DIR__ . '/packed-struct-example-3.zig');
$a = new $m->StructA(9);
$b = new $m->StructB(3);
print_r($a);
print_r($b);
StructA Object
(
[apple] => 1
[banana] =>
[cantaloupe] =>
[durian] => 1
[_] => 0
)
StructB Object
(
[agnieszka] => 1
[basia] => 1
[celina] =>
[dagmara] =>
[_] => 0
)
Anonymous struct
An anonymous struct is a literal struct without a specific type:
pub const anonymous = .{
.hello = 123,
.world = 3.14,
.type = .unknown,
};
<?php
$m = zigar_use(__DIR__ . '/anonymous-struct-example-1.zig');
print_r($m->anonymous);
S0 Object
(
[hello] => 123
[world] => 3.14
[type] => unknown
)
The fields of an anonymous struct are all comptime fields. These can hold comptime-only types such as enum literal (as seen in example above).
Limitations
Zigar is currently unable to handle non-byte-aligned fields that aren't primitives. The pointer in the following code, for example, is not accessible:
const WeirdStruct = packed struct {
good: bool = false,
bad: bool = false,
ugly: bool = false,
ptr: *anyopaque,
};
The plan is to fix this eventually, even though such a struct is entirely pathological.