Casting

A type cast converts a value of one type to another. Zig has Type Coercion for conversions that are known to be completely safe and unambiguous, and Explicit Casts for conversions that one would not want to happen on accident. There is also a third kind of type conversion called Peer Type Resolution for the case when a result type must be decided given multiple operand types.

Type Coercion

Type coercion occurs when one type is expected, but different type is provided:

test_type_coercion.zig

  1. test "type coercion - variable declaration" {
  2. var a: u8 = 1;
  3. var b: u16 = a;
  4. _ = b;
  5. }
  6. test "type coercion - function call" {
  7. var a: u8 = 1;
  8. foo(a);
  9. }
  10. fn foo(b: u16) void {
  11. _ = b;
  12. }
  13. test "type coercion - @as builtin" {
  14. var a: u8 = 1;
  15. var b = @as(u16, a);
  16. _ = b;
  17. }

Shell

  1. $ zig test test_type_coercion.zig
  2. 1/3 test.type coercion - variable declaration... OK
  3. 2/3 test.type coercion - function call... OK
  4. 3/3 test.type coercion - @as builtin... OK
  5. All 3 tests passed.

Type coercions are only allowed when it is completely unambiguous how to get from one type to another, and the transformation is guaranteed to be safe. There is one exception, which is C Pointers.

Type Coercion: Stricter Qualification

Values which have the same representation at runtime can be cast to increase the strictness of the qualifiers, no matter how nested the qualifiers are:

  • const - non-const to const is allowed
  • volatile - non-volatile to volatile is allowed
  • align - bigger to smaller alignment is allowed
  • error sets to supersets is allowed

These casts are no-ops at runtime since the value representation does not change.

test_no_op_casts.zig

  1. test "type coercion - const qualification" {
  2. var a: i32 = 1;
  3. var b: *i32 = &a;
  4. foo(b);
  5. }
  6. fn foo(_: *const i32) void {}

Shell

  1. $ zig test test_no_op_casts.zig
  2. 1/1 test.type coercion - const qualification... OK
  3. All 1 tests passed.

In addition, pointers coerce to const optional pointers:

test_pointer_coerce_const_optional.zig

  1. const std = @import("std");
  2. const expect = std.testing.expect;
  3. const mem = std.mem;
  4. test "cast *[1][*]const u8 to [*]const ?[*]const u8" {
  5. const window_name = [1][*]const u8{"window name"};
  6. const x: [*]const ?[*]const u8 = &window_name;
  7. try expect(mem.eql(u8, std.mem.sliceTo(@as([*:0]const u8, @ptrCast(x[0].?)), 0), "window name"));
  8. }

Shell

  1. $ zig test test_pointer_coerce_const_optional.zig
  2. 1/1 test.cast *[1][*]const u8 to [*]const ?[*]const u8... OK
  3. All 1 tests passed.

Type Coercion: Integer and Float Widening

Integers coerce to integer types which can represent every value of the old type, and likewise Floats coerce to float types which can represent every value of the old type.

test_integer_widening.zig

  1. const std = @import("std");
  2. const builtin = @import("builtin");
  3. const expect = std.testing.expect;
  4. const mem = std.mem;
  5. test "integer widening" {
  6. var a: u8 = 250;
  7. var b: u16 = a;
  8. var c: u32 = b;
  9. var d: u64 = c;
  10. var e: u64 = d;
  11. var f: u128 = e;
  12. try expect(f == a);
  13. }
  14. test "implicit unsigned integer to signed integer" {
  15. var a: u8 = 250;
  16. var b: i16 = a;
  17. try expect(b == 250);
  18. }
  19. test "float widening" {
  20. var a: f16 = 12.34;
  21. var b: f32 = a;
  22. var c: f64 = b;
  23. var d: f128 = c;
  24. try expect(d == a);
  25. }

Shell

  1. $ zig test test_integer_widening.zig
  2. 1/3 test.integer widening... OK
  3. 2/3 test.implicit unsigned integer to signed integer... OK
  4. 3/3 test.float widening... OK
  5. All 3 tests passed.

Type Coercion: Float to Int

A compiler error is appropriate because this ambiguous expression leaves the compiler two choices about the coercion.

  • Cast 54.0 to comptime_int resulting in @as(comptime_int, 10), which is casted to @as(f32, 10)
  • Cast 5 to comptime_float resulting in @as(comptime_float, 10.8), which is casted to @as(f32, 10.8)

test_ambiguous_coercion.zig

  1. // Compile time coercion of float to int
  2. test "implicit cast to comptime_int" {
  3. var f: f32 = 54.0 / 5;
  4. _ = f;
  5. }

Shell

  1. $ zig test test_ambiguous_coercion.zig
  2. docgen_tmp/test_ambiguous_coercion.zig:3:23: error: ambiguous coercion of division operands 'comptime_float' and 'comptime_int'; non-zero remainder '4'
  3. var f: f32 = 54.0 / 5;
  4. ~~~~~^~~

Type Coercion: Slices, Arrays and Pointers

test_coerce_slices_arrays_and_pointers.zig

  1. const std = @import("std");
  2. const expect = std.testing.expect;
  3. // You can assign constant pointers to arrays to a slice with
  4. // const modifier on the element type. Useful in particular for
  5. // String literals.
  6. test "*const [N]T to []const T" {
  7. var x1: []const u8 = "hello";
  8. var x2: []const u8 = &[5]u8{ 'h', 'e', 'l', 'l', 111 };
  9. try expect(std.mem.eql(u8, x1, x2));
  10. var y: []const f32 = &[2]f32{ 1.2, 3.4 };
  11. try expect(y[0] == 1.2);
  12. }
  13. // Likewise, it works when the destination type is an error union.
  14. test "*const [N]T to E![]const T" {
  15. var x1: anyerror![]const u8 = "hello";
  16. var x2: anyerror![]const u8 = &[5]u8{ 'h', 'e', 'l', 'l', 111 };
  17. try expect(std.mem.eql(u8, try x1, try x2));
  18. var y: anyerror![]const f32 = &[2]f32{ 1.2, 3.4 };
  19. try expect((try y)[0] == 1.2);
  20. }
  21. // Likewise, it works when the destination type is an optional.
  22. test "*const [N]T to ?[]const T" {
  23. var x1: ?[]const u8 = "hello";
  24. var x2: ?[]const u8 = &[5]u8{ 'h', 'e', 'l', 'l', 111 };
  25. try expect(std.mem.eql(u8, x1.?, x2.?));
  26. var y: ?[]const f32 = &[2]f32{ 1.2, 3.4 };
  27. try expect(y.?[0] == 1.2);
  28. }
  29. // In this cast, the array length becomes the slice length.
  30. test "*[N]T to []T" {
  31. var buf: [5]u8 = "hello".*;
  32. const x: []u8 = &buf;
  33. try expect(std.mem.eql(u8, x, "hello"));
  34. const buf2 = [2]f32{ 1.2, 3.4 };
  35. const x2: []const f32 = &buf2;
  36. try expect(std.mem.eql(f32, x2, &[2]f32{ 1.2, 3.4 }));
  37. }
  38. // Single-item pointers to arrays can be coerced to many-item pointers.
  39. test "*[N]T to [*]T" {
  40. var buf: [5]u8 = "hello".*;
  41. const x: [*]u8 = &buf;
  42. try expect(x[4] == 'o');
  43. // x[5] would be an uncaught out of bounds pointer dereference!
  44. }
  45. // Likewise, it works when the destination type is an optional.
  46. test "*[N]T to ?[*]T" {
  47. var buf: [5]u8 = "hello".*;
  48. const x: ?[*]u8 = &buf;
  49. try expect(x.?[4] == 'o');
  50. }
  51. // Single-item pointers can be cast to len-1 single-item arrays.
  52. test "*T to *[1]T" {
  53. var x: i32 = 1234;
  54. const y: *[1]i32 = &x;
  55. const z: [*]i32 = y;
  56. try expect(z[0] == 1234);
  57. }

Shell

  1. $ zig test test_coerce_slices_arrays_and_pointers.zig
  2. 1/7 test.*const [N]T to []const T... OK
  3. 2/7 test.*const [N]T to E![]const T... OK
  4. 3/7 test.*const [N]T to ?[]const T... OK
  5. 4/7 test.*[N]T to []T... OK
  6. 5/7 test.*[N]T to [*]T... OK
  7. 6/7 test.*[N]T to ?[*]T... OK
  8. 7/7 test.*T to *[1]T... OK
  9. All 7 tests passed.

See also:

Type Coercion: Optionals

The payload type of Optionals, as well as null, coerce to the optional type.

test_coerce_optionals.zig

  1. const std = @import("std");
  2. const expect = std.testing.expect;
  3. test "coerce to optionals" {
  4. const x: ?i32 = 1234;
  5. const y: ?i32 = null;
  6. try expect(x.? == 1234);
  7. try expect(y == null);
  8. }

Shell

  1. $ zig test test_coerce_optionals.zig
  2. 1/1 test.coerce to optionals... OK
  3. All 1 tests passed.

It works nested inside the Error Union Type, too:

test_coerce_optional_wrapped_error_union.zig

  1. const std = @import("std");
  2. const expect = std.testing.expect;
  3. test "coerce to optionals wrapped in error union" {
  4. const x: anyerror!?i32 = 1234;
  5. const y: anyerror!?i32 = null;
  6. try expect((try x).? == 1234);
  7. try expect((try y) == null);
  8. }

Shell

  1. $ zig test test_coerce_optional_wrapped_error_union.zig
  2. 1/1 test.coerce to optionals wrapped in error union... OK
  3. All 1 tests passed.

Type Coercion: Error Unions

The payload type of an Error Union Type as well as the Error Set Type coerce to the error union type:

test_coerce_to_error_union.zig

  1. const std = @import("std");
  2. const expect = std.testing.expect;
  3. test "coercion to error unions" {
  4. const x: anyerror!i32 = 1234;
  5. const y: anyerror!i32 = error.Failure;
  6. try expect((try x) == 1234);
  7. try std.testing.expectError(error.Failure, y);
  8. }

Shell

  1. $ zig test test_coerce_to_error_union.zig
  2. 1/1 test.coercion to error unions... OK
  3. All 1 tests passed.

Type Coercion: Compile-Time Known Numbers

When a number is comptime-known to be representable in the destination type, it may be coerced:

test_coerce_large_to_small.zig

  1. const std = @import("std");
  2. const expect = std.testing.expect;
  3. test "coercing large integer type to smaller one when value is comptime-known to fit" {
  4. const x: u64 = 255;
  5. const y: u8 = x;
  6. try expect(y == 255);
  7. }

Shell

  1. $ zig test test_coerce_large_to_small.zig
  2. 1/1 test.coercing large integer type to smaller one when value is comptime-known to fit... OK
  3. All 1 tests passed.

Type Coercion: Unions and Enums

Tagged unions can be coerced to enums, and enums can be coerced to tagged unions when they are comptime-known to be a field of the union that has only one possible value, such as void:

test_coerce_unions_enums.zig

  1. const std = @import("std");
  2. const expect = std.testing.expect;
  3. const E = enum {
  4. one,
  5. two,
  6. three,
  7. };
  8. const U = union(E) {
  9. one: i32,
  10. two: f32,
  11. three,
  12. };
  13. const U2 = union(enum) {
  14. a: void,
  15. b: f32,
  16. fn tag(self: U2) usize {
  17. switch (self) {
  18. .a => return 1,
  19. .b => return 2,
  20. }
  21. }
  22. };
  23. test "coercion between unions and enums" {
  24. var u = U{ .two = 12.34 };
  25. var e: E = u; // coerce union to enum
  26. try expect(e == E.two);
  27. const three = E.three;
  28. var u_2: U = three; // coerce enum to union
  29. try expect(u_2 == E.three);
  30. var u_3: U = .three; // coerce enum literal to union
  31. try expect(u_3 == E.three);
  32. var u_4: U2 = .a; // coerce enum literal to union with inferred enum tag type.
  33. try expect(u_4.tag() == 1);
  34. // The following example is invalid.
  35. // error: coercion from enum '@TypeOf(.enum_literal)' to union 'test_coerce_unions_enum.U2' must initialize 'f32' field 'b'
  36. //var u_5: U2 = .b;
  37. //try expect(u_5.tag() == 2);
  38. }

Shell

  1. $ zig test test_coerce_unions_enums.zig
  2. 1/1 test.coercion between unions and enums... OK
  3. All 1 tests passed.

See also:

Type Coercion: undefined

undefined can be coerced to any type.

Type Coercion: Tuples to Arrays

Tuples can be coerced to arrays, if all of the fields have the same type.

test_coerce_tuples_arrays.zig

  1. const std = @import("std");
  2. const expect = std.testing.expect;
  3. const Tuple = struct{ u8, u8 };
  4. test "coercion from homogenous tuple to array" {
  5. const tuple: Tuple = .{5, 6};
  6. const array: [2]u8 = tuple;
  7. _ = array;
  8. }

Shell

  1. $ zig test test_coerce_tuples_arrays.zig
  2. 1/1 test.coercion from homogenous tuple to array... OK
  3. All 1 tests passed.

Explicit Casts

Explicit casts are performed via Builtin Functions. Some explicit casts are safe; some are not. Some explicit casts perform language-level assertions; some do not. Some explicit casts are no-ops at runtime; some are not.

Peer Type Resolution

Peer Type Resolution occurs in these places:

This kind of type resolution chooses a type that all peer types can coerce into. Here are some examples:

test_peer_type_resolution.zig

  1. const std = @import("std");
  2. const expect = std.testing.expect;
  3. const mem = std.mem;
  4. test "peer resolve int widening" {
  5. var a: i8 = 12;
  6. var b: i16 = 34;
  7. var c = a + b;
  8. try expect(c == 46);
  9. try expect(@TypeOf(c) == i16);
  10. }
  11. test "peer resolve arrays of different size to const slice" {
  12. try expect(mem.eql(u8, boolToStr(true), "true"));
  13. try expect(mem.eql(u8, boolToStr(false), "false"));
  14. try comptime expect(mem.eql(u8, boolToStr(true), "true"));
  15. try comptime expect(mem.eql(u8, boolToStr(false), "false"));
  16. }
  17. fn boolToStr(b: bool) []const u8 {
  18. return if (b) "true" else "false";
  19. }
  20. test "peer resolve array and const slice" {
  21. try testPeerResolveArrayConstSlice(true);
  22. try comptime testPeerResolveArrayConstSlice(true);
  23. }
  24. fn testPeerResolveArrayConstSlice(b: bool) !void {
  25. const value1 = if (b) "aoeu" else @as([]const u8, "zz");
  26. const value2 = if (b) @as([]const u8, "zz") else "aoeu";
  27. try expect(mem.eql(u8, value1, "aoeu"));
  28. try expect(mem.eql(u8, value2, "zz"));
  29. }
  30. test "peer type resolution: ?T and T" {
  31. try expect(peerTypeTAndOptionalT(true, false).? == 0);
  32. try expect(peerTypeTAndOptionalT(false, false).? == 3);
  33. comptime {
  34. try expect(peerTypeTAndOptionalT(true, false).? == 0);
  35. try expect(peerTypeTAndOptionalT(false, false).? == 3);
  36. }
  37. }
  38. fn peerTypeTAndOptionalT(c: bool, b: bool) ?usize {
  39. if (c) {
  40. return if (b) null else @as(usize, 0);
  41. }
  42. return @as(usize, 3);
  43. }
  44. test "peer type resolution: *[0]u8 and []const u8" {
  45. try expect(peerTypeEmptyArrayAndSlice(true, "hi").len == 0);
  46. try expect(peerTypeEmptyArrayAndSlice(false, "hi").len == 1);
  47. comptime {
  48. try expect(peerTypeEmptyArrayAndSlice(true, "hi").len == 0);
  49. try expect(peerTypeEmptyArrayAndSlice(false, "hi").len == 1);
  50. }
  51. }
  52. fn peerTypeEmptyArrayAndSlice(a: bool, slice: []const u8) []const u8 {
  53. if (a) {
  54. return &[_]u8{};
  55. }
  56. return slice[0..1];
  57. }
  58. test "peer type resolution: *[0]u8, []const u8, and anyerror![]u8" {
  59. {
  60. var data = "hi".*;
  61. const slice = data[0..];
  62. try expect((try peerTypeEmptyArrayAndSliceAndError(true, slice)).len == 0);
  63. try expect((try peerTypeEmptyArrayAndSliceAndError(false, slice)).len == 1);
  64. }
  65. comptime {
  66. var data = "hi".*;
  67. const slice = data[0..];
  68. try expect((try peerTypeEmptyArrayAndSliceAndError(true, slice)).len == 0);
  69. try expect((try peerTypeEmptyArrayAndSliceAndError(false, slice)).len == 1);
  70. }
  71. }
  72. fn peerTypeEmptyArrayAndSliceAndError(a: bool, slice: []u8) anyerror![]u8 {
  73. if (a) {
  74. return &[_]u8{};
  75. }
  76. return slice[0..1];
  77. }
  78. test "peer type resolution: *const T and ?*T" {
  79. const a: *const usize = @ptrFromInt(0x123456780);
  80. const b: ?*usize = @ptrFromInt(0x123456780);
  81. try expect(a == b);
  82. try expect(b == a);
  83. }

Shell

  1. $ zig test test_peer_type_resolution.zig
  2. 1/7 test.peer resolve int widening... OK
  3. 2/7 test.peer resolve arrays of different size to const slice... OK
  4. 3/7 test.peer resolve array and const slice... OK
  5. 4/7 test.peer type resolution: ?T and T... OK
  6. 5/7 test.peer type resolution: *[0]u8 and []const u8... OK
  7. 6/7 test.peer type resolution: *[0]u8, []const u8, and anyerror![]u8... OK
  8. 7/7 test.peer type resolution: *const T and ?*T... OK
  9. All 7 tests passed.