diff options
| author | Andrew Kelley <andrew@ziglang.org> | 2019-02-26 22:46:35 -0500 |
|---|---|---|
| committer | Andrew Kelley <andrew@ziglang.org> | 2019-02-26 22:57:01 -0500 |
| commit | 22dd0db9bf18e225799e8540ec27451fc48ea86e (patch) | |
| tree | 0c0e93a75a4c6391d1a9eefdd2fd14cba9503bde /doc | |
| parent | 0d48011f5eb15d02b7caf9f5f01c3eecb5c4754f (diff) | |
| download | zig-22dd0db9bf18e225799e8540ec27451fc48ea86e.tar.gz zig-22dd0db9bf18e225799e8540ec27451fc48ea86e.zip | |
improve docs for unions and switching on tagged unions
closes #1943
Diffstat (limited to 'doc')
| -rw-r--r-- | doc/langref.html.in | 225 |
1 files changed, 143 insertions, 82 deletions
diff --git a/doc/langref.html.in b/doc/langref.html.in index 7c1a1a81d3..9dc0a31b23 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -2046,6 +2046,13 @@ test "linked list" { assert(list2.first.?.data == 1234); } {#code_end#} + + {#header_open|extern struct#} + <p>An {#syntax#}extern struct{#endsyntax#} has in-memory layout guaranteed to match the + C ABI for the target.</p> + {#see_also|extern union|extern enum#} + {#header_close#} + {#header_open|packed struct#} <p> Unlike normal structs, {#syntax#}packed{#endsyntax#} structs have guaranteed in-memory layout: @@ -2412,12 +2419,32 @@ test "packed enum" { {#see_also|@memberName|@memberCount|@tagName|@sizeOf#} {#header_close#} {#header_open|union#} - {#code_begin|test|union#} + <p> + A bare {#syntax#}union{#endsyntax#} defines a set of possible types that a value + can be as a list of fields. Only one field can be active at a time. + The in-memory representation of bare unions is not guaranteed. + Bare unions cannot be used to reinterpret memory. For that, use {#link|@ptrCast#}, + or use an {#link|extern union#} or a {#link|packed union#} which have + guaranteed in-memory layout. + {#link|Accessing the non-active field|Wrong Union Field Access#} is + safety-checked {#link|Undefined Behavior#}: + </p> + {#code_begin|test_err|inactive union field#} +const Payload = union { + Int: i64, + Float: f64, + Bool: bool, +}; +test "simple union" { + var payload = Payload{ .Int = 1234 }; + payload.Float = 12.34; +} + {#code_end#} + <p>You can activate another field by assigning the entire union:</p> + {#code_begin|test#} const std = @import("std"); const assert = std.debug.assert; -const mem = std.mem; -// A union has only 1 active field at a time. const Payload = union { Int: i64, Float: f64, @@ -2425,14 +2452,25 @@ const Payload = union { }; test "simple union" { var payload = Payload{ .Int = 1234 }; - // payload.Float = 12.34; // ERROR! field not active assert(payload.Int == 1234); - // You can activate another field by assigning the entire union. payload = Payload{ .Float = 12.34 }; assert(payload.Float == 12.34); } + {#code_end#} + <p> + In order to use {#link|switch#} with a union, it must be a {#link|Tagged union#}. + </p> + + {#header_open|Tagged union#} + <p>Unions can be declared with an enum tag type. + This turns the union into a <em>tagged</em> union, which makes it eligible + to use with {#link|switch#} expressions. One can use {#link|@TagType#} to + obtain the enum type from the union type. + </p> + {#code_begin|test#} +const std = @import("std"); +const assert = std.debug.assert; -// Unions can be given an enum tag type: const ComplexTypeTag = enum { Ok, NotOk, @@ -2442,56 +2480,68 @@ const ComplexType = union(ComplexTypeTag) { NotOk: void, }; -// Declare a specific instance of the union variant. -test "declare union value" { - const c = ComplexType{ .Ok = 0 }; +test "switch on tagged union" { + const c = ComplexType{ .Ok = 42 }; assert(ComplexTypeTag(c) == ComplexTypeTag.Ok); + + switch (c) { + ComplexTypeTag.Ok => |value| assert(value == 42), + ComplexTypeTag.NotOk => unreachable, + } } -// @TagType can be used to access the enum tag type of a tagged union. test "@TagType" { assert(@TagType(ComplexType) == ComplexTypeTag); } + {#code_end#} + <p>In order to modify the payload of a tagged union in a switch expression, + place a {#syntax#}*{#endsyntax#} before the variable name to make it a pointer: + </p> + {#code_begin|test#} +const std = @import("std"); +const assert = std.debug.assert; -// Unions can be made to infer the enum tag type. -const Foo = union(enum) { - String: []const u8, - Number: u64, - - // void can be omitted when inferring enum tag type. - None, +const ComplexTypeTag = enum { + Ok, + NotOk, +}; +const ComplexType = union(ComplexTypeTag) { + Ok: u8, + NotOk: void, }; -test "union variant switch" { - const p = Foo{ .Number = 54 }; - const what_is_it = switch (p) { - // Capture by reference - Foo.String => |*x| blk: { - break :blk "this is a string"; - }, - // Capture by value - Foo.Number => |x| blk: { - assert(x == 54); - break :blk "this is a number"; - }, +test "modify tagged union in switch" { + var c = ComplexType{ .Ok = 42 }; + assert(ComplexTypeTag(c) == ComplexTypeTag.Ok); - Foo.None => blk: { - break :blk "this is a none"; - }, - }; - assert(mem.eql(u8, what_is_it, "this is a number")); -} + switch (c) { + ComplexTypeTag.Ok => |*value| value.* += 1, + ComplexTypeTag.NotOk => unreachable, + } -// Unions can have methods just like structs and enums: + assert(c.Ok == 43); +} + {#code_end#} + <p> + Unions can be made to infer the enum tag type. + Further, unions can have methods just like structs and enums. + </p> + {#code_begin|test#} +const std = @import("std"); +const assert = std.debug.assert; const Variant = union(enum) { Int: i32, Bool: bool, + // void can be omitted when inferring enum tag type. + None, + fn truthy(self: Variant) bool { return switch (self) { Variant.Int => |x_int| x_int != 0, Variant.Bool => |x_bool| x_bool, + Variant.None => false, }; } }; @@ -2503,38 +2553,34 @@ test "union method" { assert(v1.truthy()); assert(!v2.truthy()); } + {#code_end#} + <p> + {#link|@tagName#} can be used to return a {#link|comptime#} + {#syntax#}[]const u8{#endsyntax#} value representing the field name: + </p> + {#code_begin|test#} +const std = @import("std"); +const assert = std.debug.assert; -const Small = union { - A: i32, - B: bool, - C: u8, -}; - -// @memberCount tells how many fields a union has: -test "@memberCount" { - assert(@memberCount(Small) == 3); -} - -// @memberName tells the name of a field in an enum: -test "@memberName" { - assert(mem.eql(u8, @memberName(Small, 1), "B")); -} - -// @tagName gives a []const u8 representation of an enum value, -// but only if the union has an enum tag type. const Small2 = union(enum) { A: i32, B: bool, C: u8, }; test "@tagName" { - assert(mem.eql(u8, @tagName(Small2.C), "C")); + assert(std.mem.eql(u8, @tagName(Small2.C), "C")); } {#code_end#} + {#header_close#} + + {#header_open|extern union#} <p> - Unions with an enum tag are generated as a struct with a tag field and union field. Zig - sorts the order of the tag and union field by the largest alignment. + An {#syntax#}extern union{#endsyntax#} has memory layout guaranteed to be compatible with + the target C ABI. </p> + {#see_also|extern struct#} + {#header_close#} + {#header_open|packed union#} <p>A {#syntax#}packed union{#endsyntax#} has well-defined in-memory layout and is eligible to be in a {#link|packed struct#}. @@ -2623,7 +2669,7 @@ test "switch simple" { // Ranges can be specified using the ... syntax. These are inclusive // both ends. - 5 ... 100 => 1, + 5...100 => 1, // Branches can be arbitrarily complex. 101 => blk: { @@ -2649,14 +2695,47 @@ test "switch simple" { assert(b == 1); } -test "switch enum" { +// Switch expressions can be used outside a function: +const os_msg = switch (builtin.os) { + builtin.Os.linux => "we found a linux user", + else => "not a linux user", +}; + +// Inside a function, switch statements implicitly are compile-time +// evaluated if the target expression is compile-time known. +test "switch inside function" { + switch (builtin.os) { + builtin.Os.fuchsia => { + // On an OS other than fuchsia, block is not even analyzed, + // so this compile error is not triggered. + // On fuchsia this compile error would be triggered. + @compileError("fuchsia not supported"); + }, + else => {}, + } +} + {#code_end#} + <p> + {#syntax#}switch{#endsyntax#} can be used to capture the field values + of a {#link|Tagged union#}. Modifications to the field values can be + done by placing a {#syntax#}*{#endsyntax#} before the capture variable name, + turning it into a pointer. + </p> + {#code_begin|test#} +const assert = @import("std").debug.assert; + +test "switch on tagged union" { + const Point = struct { + x: u8, + y: u8, + }; const Item = union(enum) { A: u32, - C: struct { x: u8, y: u8 }, + C: Point, D, }; - var a = Item { .A = 3 }; + var a = Item{ .C = Point{ .x = 1, .y = 2 } }; // Switching on more complex enums is allowed. const b = switch (a) { @@ -2674,27 +2753,8 @@ test "switch enum" { Item.D => 8, }; - assert(b == 3); -} - -// Switch expressions can be used outside a function: -const os_msg = switch (builtin.os) { - builtin.Os.linux => "we found a linux user", - else => "not a linux user", -}; - -// Inside a function, switch statements implicitly are compile-time -// evaluated if the target expression is compile-time known. -test "switch inside function" { - switch (builtin.os) { - builtin.Os.fuchsia => { - // On an OS other than fuchsia, block is not even analyzed, - // so this compile error is not triggered. - // On fuchsia this compile error would be triggered. - @compileError("fuchsia not supported"); - }, - else => {}, - } + assert(b == 6); + assert(a.C.x == 2); } {#code_end#} {#see_also|comptime|enum|@compileError|Compile Variables#} @@ -7630,6 +7690,7 @@ fn bar(f: *Foo) void { f.float = 12.34; } {#code_end#} + {#see_also|union|extern union#} {#header_close#} {#header_open|Out of Bounds Float to Integer Cast#} |
