authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-03-15 16:37:13-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-03-15 16:37:13-07:00
logcb419a1a8692cdbd612c6b63b65ba9a02e23c6c1
tree90fe5caf2ee9c529ed59ff51022a415658bb1c8c
parent5cd7fef17faa2a40c8da23f0ef2485df0af39ed4

langref: caution against default field values

closes #19169

1 files changed, 62 insertions(+), 5 deletions(-)

doc/langref.html.in+62-5
...@@ -3167,24 +3167,81 @@ test "linked list" {...@@ -3167,24 +3167,81 @@ test "linked list" {
31673167
3168 {#header_open|Default Field Values#}3168 {#header_open|Default Field Values#}
3169 <p>3169 <p>
3170 Each struct field may have an expression indicating the default field value. Such expressions3170 Each struct field may have an expression indicating the default field
3171 are executed at {#link|comptime#}, and allow the field to be omitted in a struct literal expression:3171 value. Such expressions are executed at {#link|comptime#}, and allow the
3172 field to be omitted in a struct literal expression:
3172 </p>3173 </p>
3173 {#code_begin|test|test_struct_default_field_values#}3174 {#code_begin|test|struct_default_field_values#}
3174const Foo = struct {3175const Foo = struct {
3175 a: i32 = 1234,3176 a: i32 = 1234,
3176 b: i32,3177 b: i32,
3177};3178};
31783179
3179test "default struct initialization fields" {3180test "default struct initialization fields" {
3180 const x = Foo{3181 const x: Foo = .{
3181 .b = 5,3182 .b = 5,
3182 };3183 };
3183 if (x.a + x.b != 1239) {3184 if (x.a + x.b != 1239) {
3184 @compileError("it's even comptime-known!");3185 comptime unreachable;
3185 }3186 }
3186}3187}
3187 {#code_end#}3188 {#code_end#}
3189 <p>
3190 Default field values are only appropriate when the data invariants of a struct
3191 cannot be violated by omitting that field from an initialization.
3192 </p>
3193 <p>
3194 For example, here is an inappropriate use of default struct field initialization:
3195 </p>
3196 {#code_begin|exe_err|bad_default_value#}
3197const Threshold = struct {
3198 minimum: f32 = 0.25,
3199 maximum: f32 = 0.75,
3200
3201 const Category = enum { low, medium, high };
3202
3203 fn categorize(t: Threshold, value: f32) Category {
3204 assert(t.maximum >= t.minimum);
3205 if (value < t.minimum) return .low;
3206 if (value > t.maximum) return .high;
3207 return .medium;
3208 }
3209};
3210
3211pub fn main() !void {
3212 var threshold: Threshold = .{
3213 .maximum = 0.20,
3214 };
3215 const category = threshold.categorize(0.90);
3216 try std.io.getStdOut().writeAll(@tagName(category));
3217}
3218
3219const std = @import("std");
3220const assert = std.debug.assert;
3221 {#code_end#}
3222 <p>
3223 Above you can see the danger of ignoring this principle. The default
3224 field values caused the data invariant to be violated, causing illegal
3225 behavior.
3226 </p>
3227 <p>
3228 To fix this, remove the default values from all the struct fields, and provide
3229 a named default value:
3230 </p>
3231 {#code_begin|syntax|struct_default_value#}
3232const Threshold = struct {
3233 minimum: f32,
3234 maximum: f32,
3235
3236 const default: Threshold = .{
3237 .minimum = 0.25,
3238 .maximum = 0.75,
3239 };
3240};
3241 {#code_end#}
3242 <p>If a struct value requires a runtime-known value in order to be initialized
3243 without violating data invariants, then use an initialization method that accepts
3244 those runtime values, and populates the remaining fields.</p>
3188 {#header_close#}3245 {#header_close#}
31893246
3190 {#header_open|extern struct#}3247 {#header_open|extern struct#}