lib/preserves/src/domain.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 //! The rules for a type whose values a host program embeds inside data values, and one such type
2 //! that carries nothing. A value tree compares, orders, frees and copies the things embedded in it
3 //! along with everything else, so the type of the embedded values has to supply those four
4 //! operations. A generic in Zig checks a type's functions only at the line that calls them, so a
5 //! type missing one fails deep inside the value code with an error that names no rule. Many
6 //! programs embed nothing, and they still need a type for the embedded values. The
7 //! [Preserves](https://preserves.dev/) data language lets a host program place its own values
8 //! inside Preserves data as embedded values, and the package keeps them.
9 //!
10 //! The package has one generic value type (`Value`). That value type takes the type of its embedded
11 //! values (*domain*) as a compile-time parameter, so each program chooses what it embeds and the
12 //! compiler checks it. A compile-time check (`assertIsDomain`) runs first in every generic of the
13 //! package and stops compilation when the type lacks `eql`, `order`, `deinit` or `clone`, with a
14 //! message that names the missing function and the signature the package calls. The check looks for
15 //! the four names only, and the compiler checks their signatures later, where the value code calls
16 //! them. A `hash` function is optional: `Value.hash` mixes in the embedded value's hash only when
17 //! the type declares one, so without it every embedded value adds the same bytes. `NoEmbedded`
18 //! fills the parameter for programs that embed nothing: its one value carries no data, every two
19 //! are equal, and freeing and copying do nothing. `AnyEmbedded`, which the package root re-exports
20 //! from its `embedded_mod` namespace, is the other type the package ships, for payloads of the host
21 //! program.
22 const std = @import("std");
23 const Allocator = std.mem.Allocator;
24
25 /// Stops compilation unless `D` declares public functions named `eql`, `order`, `deinit` and
26 /// `clone`. Code that writes a generic over `Value` calls it first, as `Value` and every generic of
27 /// the package do, so a type of embedded values that breaks the rules fails. The check tests that
28 /// the names exist and reads no signature. The error names the missing function and the signature
29 /// the package calls: `eql(a: Self, b: Self) bool`, `order(a: Self, b: Self) std.math.Order`,
30 /// `deinit(self: *Self, allocator: Allocator) void` and
31 /// `clone(self: Self, allocator: Allocator) !Self`.
32 pub fn assertIsDomain(comptime D: type) void {
33 if (!@hasDecl(D, "eql")) {
34 @compileError("Domain " ++ @typeName(D) ++ " is missing `pub fn eql(a: Self, b: Self) bool`");
35 }
36 if (!@hasDecl(D, "order")) {
37 @compileError("Domain " ++ @typeName(D) ++ " is missing `pub fn order(a: Self, b: Self) std.math.Order`");
38 }
39 if (!@hasDecl(D, "deinit")) {
40 @compileError("Domain " ++ @typeName(D) ++ " is missing `pub fn deinit(self: *Self, allocator: Allocator) void`");
41 }
42 if (!@hasDecl(D, "clone")) {
43 @compileError("Domain " ++ @typeName(D) ++ " is missing `pub fn clone(self: Self, allocator: Allocator) !Self`");
44 }
45 }
46
47 /// A type of embedded values whose one value carries no data, for programs that embed nothing. Code
48 /// with nothing to embed passes it to `Value`, as the binary reader's tests do. Every two of its
49 /// values are equal.
50 pub const NoEmbedded = struct {
51 /// Returns `true` for any two values. `Value.eql` calls it for two embedded values of this
52 /// type.
53 pub fn eql(a: NoEmbedded, b: NoEmbedded) bool {
54 _ = a;
55 _ = b;
56 return true;
57 }
58
59 /// Returns `.eq` for any two values. `Value.compare` calls it for two embedded values of this
60 /// type.
61 pub fn order(a: NoEmbedded, b: NoEmbedded) std.math.Order {
62 _ = a;
63 _ = b;
64 return .eq;
65 }
66
67 /// Does nothing, since the value owns nothing. `Value.deinit` calls it for an embedded value of
68 /// this type.
69 pub fn deinit(self: *NoEmbedded, allocator: Allocator) void {
70 _ = self;
71 _ = allocator;
72 }
73
74 /// Returns the value itself and allocates nothing. `cloneValueDeep` calls it for an embedded
75 /// value of this type. The call never fails, though its type carries an error union for the
76 /// rule's signature.
77 pub fn clone(self: NoEmbedded, allocator: Allocator) !NoEmbedded {
78 _ = allocator;
79 return self;
80 }
81 };
82
83 test "NoEmbedded satisfies the Domain contract" {
84 comptime assertIsDomain(NoEmbedded);
85 }