Commit cb419a1a86

Andrew Kelley <andrew@ziglang.org>
2024-03-16 00:37:13
langref: caution against default field values
closes #19169
1 parent 5cd7fef
Changed files (1)
doc/langref.html.in
@@ -3167,24 +3167,81 @@ test "linked list" {
 
       {#header_open|Default Field Values#}
       <p>
-      Each struct field may have an expression indicating the default field value. Such expressions
-      are executed at {#link|comptime#}, and allow the field to be omitted in a struct literal expression:
+      Each struct field may have an expression indicating the default field
+      value. Such expressions are executed at {#link|comptime#}, and allow the
+      field to be omitted in a struct literal expression:
       </p>
-      {#code_begin|test|test_struct_default_field_values#}
+      {#code_begin|test|struct_default_field_values#}
 const Foo = struct {
     a: i32 = 1234,
     b: i32,
 };
 
 test "default struct initialization fields" {
-    const x = Foo{
+    const x: Foo = .{
         .b = 5,
     };
     if (x.a + x.b != 1239) {
-        @compileError("it's even comptime-known!");
+        comptime unreachable;
     }
 }
       {#code_end#}
+      <p>
+      Default field values are only appropriate when the data invariants of a struct
+      cannot be violated by omitting that field from an initialization.
+      </p>
+      <p>
+      For example, here is an inappropriate use of default struct field initialization:
+      </p>
+      {#code_begin|exe_err|bad_default_value#}
+const Threshold = struct {
+    minimum: f32 = 0.25,
+    maximum: f32 = 0.75,
+
+    const Category = enum { low, medium, high };
+
+    fn categorize(t: Threshold, value: f32) Category {
+        assert(t.maximum >= t.minimum);
+        if (value < t.minimum) return .low;
+        if (value > t.maximum) return .high;
+        return .medium;
+    }
+};
+
+pub fn main() !void {
+    var threshold: Threshold = .{
+        .maximum = 0.20,
+    };
+    const category = threshold.categorize(0.90);
+    try std.io.getStdOut().writeAll(@tagName(category));
+}
+
+const std = @import("std");
+const assert = std.debug.assert;
+      {#code_end#}
+      <p>
+      Above you can see the danger of ignoring this principle. The default
+      field values caused the data invariant to be violated, causing illegal
+      behavior.
+      </p>
+      <p>
+      To fix this, remove the default values from all the struct fields, and provide
+      a named default value:
+      </p>
+      {#code_begin|syntax|struct_default_value#}
+const Threshold = struct {
+    minimum: f32,
+    maximum: f32,
+
+    const default: Threshold = .{
+        .minimum = 0.25,
+        .maximum = 0.75,
+    };
+};
+      {#code_end#}
+      <p>If a struct value requires a runtime-known value in order to be initialized
+      without violating data invariants, then use an initialization method that accepts
+      those runtime values, and populates the remaining fields.</p>
       {#header_close#}
 
       {#header_open|extern struct#}