authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2019-02-26 22:46:35-05:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2019-02-26 22:57:01-05:00
log22dd0db9bf18e225799e8540ec27451fc48ea86e
tree0c0e93a75a4c6391d1a9eefdd2fd14cba9503bde
parent0d48011f5eb15d02b7caf9f5f01c3eecb5c4754f
signaturelock-open Commit is signed but in an unrecognized format.

improve docs for unions and switching on tagged unions

closes #1943

1 files changed, 143 insertions(+), 82 deletions(-)

doc/langref.html.in+143-82
...@@ -2046,6 +2046,13 @@ test "linked list" {...@@ -2046,6 +2046,13 @@ test "linked list" {
2046 assert(list2.first.?.data == 1234);2046 assert(list2.first.?.data == 1234);
2047}2047}
2048 {#code_end#}2048 {#code_end#}
2049
2050 {#header_open|extern struct#}
2051 <p>An {#syntax#}extern struct{#endsyntax#} has in-memory layout guaranteed to match the
2052 C ABI for the target.</p>
2053 {#see_also|extern union|extern enum#}
2054 {#header_close#}
2055
2049 {#header_open|packed struct#}2056 {#header_open|packed struct#}
2050 <p>2057 <p>
2051 Unlike normal structs, {#syntax#}packed{#endsyntax#} structs have guaranteed in-memory layout:2058 Unlike normal structs, {#syntax#}packed{#endsyntax#} structs have guaranteed in-memory layout:
...@@ -2412,12 +2419,32 @@ test "packed enum" {...@@ -2412,12 +2419,32 @@ test "packed enum" {
2412 {#see_also|@memberName|@memberCount|@tagName|@sizeOf#}2419 {#see_also|@memberName|@memberCount|@tagName|@sizeOf#}
2413 {#header_close#}2420 {#header_close#}
2414 {#header_open|union#}2421 {#header_open|union#}
2415 {#code_begin|test|union#}2422 <p>
2423 A bare {#syntax#}union{#endsyntax#} defines a set of possible types that a value
2424 can be as a list of fields. Only one field can be active at a time.
2425 The in-memory representation of bare unions is not guaranteed.
2426 Bare unions cannot be used to reinterpret memory. For that, use {#link|@ptrCast#},
2427 or use an {#link|extern union#} or a {#link|packed union#} which have
2428 guaranteed in-memory layout.
2429 {#link|Accessing the non-active field|Wrong Union Field Access#} is
2430 safety-checked {#link|Undefined Behavior#}:
2431 </p>
2432 {#code_begin|test_err|inactive union field#}
2433const Payload = union {
2434 Int: i64,
2435 Float: f64,
2436 Bool: bool,
2437};
2438test "simple union" {
2439 var payload = Payload{ .Int = 1234 };
2440 payload.Float = 12.34;
2441}
2442 {#code_end#}
2443 <p>You can activate another field by assigning the entire union:</p>
2444 {#code_begin|test#}
2416const std = @import("std");2445const std = @import("std");
2417const assert = std.debug.assert;2446const assert = std.debug.assert;
2418const mem = std.mem;
24192447
2420// A union has only 1 active field at a time.
2421const Payload = union {2448const Payload = union {
2422 Int: i64,2449 Int: i64,
2423 Float: f64,2450 Float: f64,
...@@ -2425,14 +2452,25 @@ const Payload = union {...@@ -2425,14 +2452,25 @@ const Payload = union {
2425};2452};
2426test "simple union" {2453test "simple union" {
2427 var payload = Payload{ .Int = 1234 };2454 var payload = Payload{ .Int = 1234 };
2428 // payload.Float = 12.34; // ERROR! field not active
2429 assert(payload.Int == 1234);2455 assert(payload.Int == 1234);
2430 // You can activate another field by assigning the entire union.
2431 payload = Payload{ .Float = 12.34 };2456 payload = Payload{ .Float = 12.34 };
2432 assert(payload.Float == 12.34);2457 assert(payload.Float == 12.34);
2433}2458}
2459 {#code_end#}
2460 <p>
2461 In order to use {#link|switch#} with a union, it must be a {#link|Tagged union#}.
2462 </p>
2463
2464 {#header_open|Tagged union#}
2465 <p>Unions can be declared with an enum tag type.
2466 This turns the union into a <em>tagged</em> union, which makes it eligible
2467 to use with {#link|switch#} expressions. One can use {#link|@TagType#} to
2468 obtain the enum type from the union type.
2469 </p>
2470 {#code_begin|test#}
2471const std = @import("std");
2472const assert = std.debug.assert;
24342473
2435// Unions can be given an enum tag type:
2436const ComplexTypeTag = enum {2474const ComplexTypeTag = enum {
2437 Ok,2475 Ok,
2438 NotOk,2476 NotOk,
...@@ -2442,56 +2480,68 @@ const ComplexType = union(ComplexTypeTag) {...@@ -2442,56 +2480,68 @@ const ComplexType = union(ComplexTypeTag) {
2442 NotOk: void,2480 NotOk: void,
2443};2481};
24442482
2445// Declare a specific instance of the union variant.2483test "switch on tagged union" {
2446test "declare union value" {2484 const c = ComplexType{ .Ok = 42 };
2447 const c = ComplexType{ .Ok = 0 };
2448 assert(ComplexTypeTag(c) == ComplexTypeTag.Ok);2485 assert(ComplexTypeTag(c) == ComplexTypeTag.Ok);
2486
2487 switch (c) {
2488 ComplexTypeTag.Ok => |value| assert(value == 42),
2489 ComplexTypeTag.NotOk => unreachable,
2490 }
2449}2491}
24502492
2451// @TagType can be used to access the enum tag type of a tagged union.
2452test "@TagType" {2493test "@TagType" {
2453 assert(@TagType(ComplexType) == ComplexTypeTag);2494 assert(@TagType(ComplexType) == ComplexTypeTag);
2454}2495}
2496 {#code_end#}
2497 <p>In order to modify the payload of a tagged union in a switch expression,
2498 place a {#syntax#}*{#endsyntax#} before the variable name to make it a pointer:
2499 </p>
2500 {#code_begin|test#}
2501const std = @import("std");
2502const assert = std.debug.assert;
24552503
2456// Unions can be made to infer the enum tag type.2504const ComplexTypeTag = enum {
2457const Foo = union(enum) {2505 Ok,
2458 String: []const u8,2506 NotOk,
2459 Number: u64,2507};
24602508const ComplexType = union(ComplexTypeTag) {
2461 // void can be omitted when inferring enum tag type.2509 Ok: u8,
2462 None,2510 NotOk: void,
2463};2511};
2464test "union variant switch" {
2465 const p = Foo{ .Number = 54 };
2466 const what_is_it = switch (p) {
2467 // Capture by reference
2468 Foo.String => |*x| blk: {
2469 break :blk "this is a string";
2470 },
24712512
2472 // Capture by value2513test "modify tagged union in switch" {
2473 Foo.Number => |x| blk: {2514 var c = ComplexType{ .Ok = 42 };
2474 assert(x == 54);2515 assert(ComplexTypeTag(c) == ComplexTypeTag.Ok);
2475 break :blk "this is a number";
2476 },
24772516
2478 Foo.None => blk: {2517 switch (c) {
2479 break :blk "this is a none";2518 ComplexTypeTag.Ok => |*value| value.* += 1,
2480 },2519 ComplexTypeTag.NotOk => unreachable,
2481 };2520 }
2482 assert(mem.eql(u8, what_is_it, "this is a number"));
2483}
24842521
2485// Unions can have methods just like structs and enums:2522 assert(c.Ok == 43);
2523}
2524 {#code_end#}
2525 <p>
2526 Unions can be made to infer the enum tag type.
2527 Further, unions can have methods just like structs and enums.
2528 </p>
2529 {#code_begin|test#}
2530const std = @import("std");
2531const assert = std.debug.assert;
24862532
2487const Variant = union(enum) {2533const Variant = union(enum) {
2488 Int: i32,2534 Int: i32,
2489 Bool: bool,2535 Bool: bool,
24902536
2537 // void can be omitted when inferring enum tag type.
2538 None,
2539
2491 fn truthy(self: Variant) bool {2540 fn truthy(self: Variant) bool {
2492 return switch (self) {2541 return switch (self) {
2493 Variant.Int => |x_int| x_int != 0,2542 Variant.Int => |x_int| x_int != 0,
2494 Variant.Bool => |x_bool| x_bool,2543 Variant.Bool => |x_bool| x_bool,
2544 Variant.None => false,
2495 };2545 };
2496 }2546 }
2497};2547};
...@@ -2503,38 +2553,34 @@ test "union method" {...@@ -2503,38 +2553,34 @@ test "union method" {
2503 assert(v1.truthy());2553 assert(v1.truthy());
2504 assert(!v2.truthy());2554 assert(!v2.truthy());
2505}2555}
2556 {#code_end#}
2557 <p>
2558 {#link|@tagName#} can be used to return a {#link|comptime#}
2559 {#syntax#}[]const u8{#endsyntax#} value representing the field name:
2560 </p>
2561 {#code_begin|test#}
2562const std = @import("std");
2563const assert = std.debug.assert;
25062564
2507const Small = union {
2508 A: i32,
2509 B: bool,
2510 C: u8,
2511};
2512
2513// @memberCount tells how many fields a union has:
2514test "@memberCount" {
2515 assert(@memberCount(Small) == 3);
2516}
2517
2518// @memberName tells the name of a field in an enum:
2519test "@memberName" {
2520 assert(mem.eql(u8, @memberName(Small, 1), "B"));
2521}
2522
2523// @tagName gives a []const u8 representation of an enum value,
2524// but only if the union has an enum tag type.
2525const Small2 = union(enum) {2565const Small2 = union(enum) {
2526 A: i32,2566 A: i32,
2527 B: bool,2567 B: bool,
2528 C: u8,2568 C: u8,
2529};2569};
2530test "@tagName" {2570test "@tagName" {
2531 assert(mem.eql(u8, @tagName(Small2.C), "C"));2571 assert(std.mem.eql(u8, @tagName(Small2.C), "C"));
2532}2572}
2533 {#code_end#}2573 {#code_end#}
2574 {#header_close#}
2575
2576 {#header_open|extern union#}
2534 <p>2577 <p>
2535 Unions with an enum tag are generated as a struct with a tag field and union field. Zig2578 An {#syntax#}extern union{#endsyntax#} has memory layout guaranteed to be compatible with
2536 sorts the order of the tag and union field by the largest alignment.2579 the target C ABI.
2537 </p>2580 </p>
2581 {#see_also|extern struct#}
2582 {#header_close#}
2583
2538 {#header_open|packed union#}2584 {#header_open|packed union#}
2539 <p>A {#syntax#}packed union{#endsyntax#} has well-defined in-memory layout and is eligible2585 <p>A {#syntax#}packed union{#endsyntax#} has well-defined in-memory layout and is eligible
2540 to be in a {#link|packed struct#}.2586 to be in a {#link|packed struct#}.
...@@ -2623,7 +2669,7 @@ test "switch simple" {...@@ -2623,7 +2669,7 @@ test "switch simple" {
26232669
2624 // Ranges can be specified using the ... syntax. These are inclusive2670 // Ranges can be specified using the ... syntax. These are inclusive
2625 // both ends.2671 // both ends.
2626 5 ... 100 => 1,2672 5...100 => 1,
26272673
2628 // Branches can be arbitrarily complex.2674 // Branches can be arbitrarily complex.
2629 101 => blk: {2675 101 => blk: {
...@@ -2649,14 +2695,47 @@ test "switch simple" {...@@ -2649,14 +2695,47 @@ test "switch simple" {
2649 assert(b == 1);2695 assert(b == 1);
2650}2696}
26512697
2652test "switch enum" {2698// Switch expressions can be used outside a function:
2699const os_msg = switch (builtin.os) {
2700 builtin.Os.linux => "we found a linux user",
2701 else => "not a linux user",
2702};
2703
2704// Inside a function, switch statements implicitly are compile-time
2705// evaluated if the target expression is compile-time known.
2706test "switch inside function" {
2707 switch (builtin.os) {
2708 builtin.Os.fuchsia => {
2709 // On an OS other than fuchsia, block is not even analyzed,
2710 // so this compile error is not triggered.
2711 // On fuchsia this compile error would be triggered.
2712 @compileError("fuchsia not supported");
2713 },
2714 else => {},
2715 }
2716}
2717 {#code_end#}
2718 <p>
2719 {#syntax#}switch{#endsyntax#} can be used to capture the field values
2720 of a {#link|Tagged union#}. Modifications to the field values can be
2721 done by placing a {#syntax#}*{#endsyntax#} before the capture variable name,
2722 turning it into a pointer.
2723 </p>
2724 {#code_begin|test#}
2725const assert = @import("std").debug.assert;
2726
2727test "switch on tagged union" {
2728 const Point = struct {
2729 x: u8,
2730 y: u8,
2731 };
2653 const Item = union(enum) {2732 const Item = union(enum) {
2654 A: u32,2733 A: u32,
2655 C: struct { x: u8, y: u8 },2734 C: Point,
2656 D,2735 D,
2657 };2736 };
26582737
2659 var a = Item { .A = 3 };2738 var a = Item{ .C = Point{ .x = 1, .y = 2 } };
26602739
2661 // Switching on more complex enums is allowed.2740 // Switching on more complex enums is allowed.
2662 const b = switch (a) {2741 const b = switch (a) {
...@@ -2674,27 +2753,8 @@ test "switch enum" {...@@ -2674,27 +2753,8 @@ test "switch enum" {
2674 Item.D => 8,2753 Item.D => 8,
2675 };2754 };
26762755
2677 assert(b == 3);2756 assert(b == 6);
2678}2757 assert(a.C.x == 2);
2679
2680// Switch expressions can be used outside a function:
2681const os_msg = switch (builtin.os) {
2682 builtin.Os.linux => "we found a linux user",
2683 else => "not a linux user",
2684};
2685
2686// Inside a function, switch statements implicitly are compile-time
2687// evaluated if the target expression is compile-time known.
2688test "switch inside function" {
2689 switch (builtin.os) {
2690 builtin.Os.fuchsia => {
2691 // On an OS other than fuchsia, block is not even analyzed,
2692 // so this compile error is not triggered.
2693 // On fuchsia this compile error would be triggered.
2694 @compileError("fuchsia not supported");
2695 },
2696 else => {},
2697 }
2698}2758}
2699 {#code_end#}2759 {#code_end#}
2700 {#see_also|comptime|enum|@compileError|Compile Variables#}2760 {#see_also|comptime|enum|@compileError|Compile Variables#}
...@@ -7630,6 +7690,7 @@ fn bar(f: *Foo) void {...@@ -7630,6 +7690,7 @@ fn bar(f: *Foo) void {
7630 f.float = 12.34;7690 f.float = 12.34;
7631}7691}
7632 {#code_end#}7692 {#code_end#}
7693 {#see_also|union|extern union#}
7633 {#header_close#}7694 {#header_close#}
76347695
7635 {#header_open|Out of Bounds Float to Integer Cast#}7696 {#header_open|Out of Bounds Float to Integer Cast#}