-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathcircular_reference.zig
57 lines (49 loc) · 1.76 KB
/
circular_reference.zig
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
const std = @import("std");
const testing = @import("std").testing;
const interface = @import("interface");
// The way this library works has a tendency to create 'unresolvable' circular references.
// However, there is a way around it.
// This is the interface
fn MI(comptime B: type) type {
return struct {
// FakeZ is the same as Z, but it doesn't reference the interface
pub fn iz(self: B, z: FakeZ) i32 {
return self.vtable.iz(self.object, z);
}
};
}
// Make the interface, HOWEVER if the implementer's functions aren't equal,
// if they are instead bitwise compatible (TODO: explain rules in more detail) then it's allowed.
const I = interface.MakeInterface(MI, .{ .allow_bitwise_compatibility = true });
const Impl = struct {
pub fn iz(self: *Impl, z: Z) i32 {
_ = self;
return z.int + 5;
}
};
// This is 'referenced' by and references the interface.
// It is extern so that its 'fake' type is guaranteed to have the same memory layout.
const Z = extern struct {
int: i32,
i: *I,
fn izo(self: *Z) i32 {
const prev = self.int;
// When the function is called, it is cast to the fake type.
// When the function pointer is called, it doesn't need to know it's given the wrong type,
// because the real and fake versions are bit-for-bit compatible.
self.int = self.i.iz(@bitCast(self.*));
return prev;
}
};
// A copy of Z that doesn't reference the interface.
const FakeZ = extern struct {
int: i32,
i: *anyopaque,
};
test "circular reference" {
var a = Impl{};
var i = I.initFromImplementer(Impl, &a);
var z = Z{ .int = 5, .i = &i };
try testing.expectEqual(@as(i32, 5), z.izo());
try testing.expectEqual(@as(i32, 10), z.int);
}