- Casting
- Type Coercion
- Type Coercion: Stricter Qualification
- Type Coercion: Integer and Float Widening
- Type Coercion: Coercion Float to Int
- Type Coercion: Arrays and Pointers
- Type Coercion: Optionals
- Type Coercion: Error Unions
- Type Coercion: Compile-Time Known Numbers
- Type Coercion: unions and enums
- Type Coercion: Zero Bit Types
- Type Coercion: undefined
- Explicit Casts
- Peer Type Resolution
- Type Coercion
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.zig
test "type coercion - variable declaration" {
var a: u8 = 1;
var b: u16 = a;
}
test "type coercion - function call" {
var a: u8 = 1;
foo(a);
}
fn foo(b: u16) void {}
test "type coercion - @as builtin" {
var a: u8 = 1;
var b = @as(u16, a);
}
$ zig test test.zig
1/3 test "type coercion - variable declaration"... OK
2/3 test "type coercion - function call"... OK
3/3 test "type coercion - @as builtin"... OK
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 allowedvolatile
- non-volatile to volatile is allowedalign
- 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.zig
test "type coercion - const qualification" {
var a: i32 = 1;
var b: *i32 = &a;
foo(b);
}
fn foo(a: *const i32) void {}
$ zig test test.zig
1/1 test "type coercion - const qualification"... OK
All 1 tests passed.
In addition, pointers coerce to const optional pointers:
test.zig
const std = @import("std");
const expect = std.testing.expect;
const mem = std.mem;
test "cast *[1][*]const u8 to [*]const ?[*]const u8" {
const window_name = [1][*]const u8{"window name"};
const x: [*]const ?[*]const u8 = &window_name;
expect(mem.eql(u8, std.mem.spanZ(@ptrCast([*:0]const u8, x[0].?)), "window name"));
}
$ zig test test.zig
1/1 test "cast *[1][*]const u8 to [*]const ?[*]const u8"... OK
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.zig
const std = @import("std");
const expect = std.testing.expect;
const mem = std.mem;
test "integer widening" {
var a: u8 = 250;
var b: u16 = a;
var c: u32 = b;
var d: u64 = c;
var e: u64 = d;
var f: u128 = e;
expect(f == a);
}
test "implicit unsigned integer to signed integer" {
var a: u8 = 250;
var b: i16 = a;
expect(b == 250);
}
test "float widening" {
// Note: there is an open issue preventing this from working on aarch64:
// https://github.com/ziglang/zig/issues/3282
if (std.Target.current.cpu.arch == .aarch64) return error.SkipZigTest;
var a: f16 = 12.34;
var b: f32 = a;
var c: f64 = b;
var d: f128 = c;
expect(d == a);
}
$ zig test test.zig
1/3 test "integer widening"... OK
2/3 test "implicit unsigned integer to signed integer"... OK
3/3 test "float widening"... OK
All 3 tests passed.
Type Coercion: Coercion Float to Int
A compiler error is appropriate because this ambiguous expression leaves the compiler two choices about the coercion.
- Cast
54.0
tocomptime_int
resulting in@as(comptime_int, 10)
, which is casted to@as(f32, 10)
- Cast
5
tocomptime_float
resulting in@as(comptime_float, 10.8)
, which is casted to@as(f32, 10.8)
test.zig
// Compile time coercion of float to int
test "implicit cast to comptime_int" {
var f: f32 = 54.0 / 5;
}
$ zig test test.zig
./docgen_tmp/test.zig:3:18: error: float value 54.000000 cannot be coerced to type 'comptime_int'
var f: f32 = 54.0 / 5;
^
./docgen_tmp/test.zig:3:23: note: referenced here
var f: f32 = 54.0 / 5;
^
Type Coercion: Arrays and Pointers
coerce_arrays_and_ptrs.zig
const std = @import("std");
const expect = std.testing.expect;
// This cast exists primarily so that string literals can be
// passed to functions that accept const slices. However
// it is probably going to be removed from the language when
// https://github.com/ziglang/zig/issues/265 is implemented.
test "[N]T to []const T" {
var x1: []const u8 = "hello";
var x2: []const u8 = &[5]u8{ 'h', 'e', 'l', 'l', 111 };
expect(std.mem.eql(u8, x1, x2));
var y: []const f32 = &[2]f32{ 1.2, 3.4 };
expect(y[0] == 1.2);
}
// Likewise, it works when the destination type is an error union.
test "[N]T to E![]const T" {
var x1: anyerror![]const u8 = "hello";
var x2: anyerror![]const u8 = &[5]u8{ 'h', 'e', 'l', 'l', 111 };
expect(std.mem.eql(u8, try x1, try x2));
var y: anyerror![]const f32 = &[2]f32{ 1.2, 3.4 };
expect((try y)[0] == 1.2);
}
// Likewise, it works when the destination type is an optional.
test "[N]T to ?[]const T" {
var x1: ?[]const u8 = "hello";
var x2: ?[]const u8 = &[5]u8{ 'h', 'e', 'l', 'l', 111 };
expect(std.mem.eql(u8, x1.?, x2.?));
var y: ?[]const f32 = &[2]f32{ 1.2, 3.4 };
expect(y.?[0] == 1.2);
}
// In this cast, the array length becomes the slice length.
test "*[N]T to []T" {
var buf: [5]u8 = "hello".*;
const x: []u8 = &buf;
expect(std.mem.eql(u8, x, "hello"));
const buf2 = [2]f32{ 1.2, 3.4 };
const x2: []const f32 = &buf2;
expect(std.mem.eql(f32, x2, &[2]f32{ 1.2, 3.4 }));
}
// Single-item pointers to arrays can be coerced to
// unknown length pointers.
test "*[N]T to [*]T" {
var buf: [5]u8 = "hello".*;
const x: [*]u8 = &buf;
expect(x[4] == 'o');
// x[5] would be an uncaught out of bounds pointer dereference!
}
// Likewise, it works when the destination type is an optional.
test "*[N]T to ?[*]T" {
var buf: [5]u8 = "hello".*;
const x: ?[*]u8 = &buf;
expect(x.?[4] == 'o');
}
// Single-item pointers can be cast to len-1 single-item arrays.
test "*T to *[1]T" {
var x: i32 = 1234;
const y: *[1]i32 = &x;
const z: [*]i32 = y;
expect(z[0] == 1234);
}
$ zig test coerce_arrays_and_ptrs.zig
1/7 test "[N]T to []const T"... OK
2/7 test "[N]T to E![]const T"... OK
3/7 test "[N]T to ?[]const T"... OK
4/7 test "*[N]T to []T"... OK
5/7 test "*[N]T to [*]T"... OK
6/7 test "*[N]T to ?[*]T"... OK
7/7 test "*T to *[1]T"... OK
All 7 tests passed.
See also:
Type Coercion: Optionals
The payload type of Optionals, as well as null, coerce to the optional type.
test.zig
const std = @import("std");
const expect = std.testing.expect;
test "coerce to optionals" {
const x: ?i32 = 1234;
const y: ?i32 = null;
expect(x.? == 1234);
expect(y == null);
}
$ zig test test.zig
1/1 test "coerce to optionals"... OK
All 1 tests passed.
It works nested inside the Error Union Type, too:
test.zig
const std = @import("std");
const expect = std.testing.expect;
test "coerce to optionals wrapped in error union" {
const x: anyerror!?i32 = 1234;
const y: anyerror!?i32 = null;
expect((try x).? == 1234);
expect((try y) == null);
}
$ zig test test.zig
1/1 test "coerce to optionals wrapped in error union"... OK
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.zig
const std = @import("std");
const expect = std.testing.expect;
test "coercion to error unions" {
const x: anyerror!i32 = 1234;
const y: anyerror!i32 = error.Failure;
expect((try x) == 1234);
std.testing.expectError(error.Failure, y);
}
$ zig test test.zig
1/1 test "coercion to error unions"... OK
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.zig
const std = @import("std");
const expect = std.testing.expect;
test "coercing large integer type to smaller one when value is comptime known to fit" {
const x: u64 = 255;
const y: u8 = x;
expect(y == 255);
}
$ zig test test.zig
1/1 test "coercing large integer type to smaller one when value is comptime known to fit"... OK
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.zig
const std = @import("std");
const expect = std.testing.expect;
const E = enum {
one,
two,
three,
};
const U = union(E) {
one: i32,
two: f32,
three,
};
test "coercion between unions and enums" {
var u = U{ .two = 12.34 };
var e: E = u;
expect(e == E.two);
const three = E.three;
var another_u: U = three;
expect(another_u == E.three);
}
$ zig test test.zig
1/1 test "coercion between unions and enums"... OK
All 1 tests passed.
See also:
Type Coercion: Zero Bit Types
Zero Bit Types may be coerced to single-item Pointers, regardless of const.
TODO document the reasoning for this
TODO document whether vice versa should work and why
test.zig
test "coercion of zero bit types" {
var x: void = {};
var y: *void = x;
//var z: void = y; // TODO
}
$ zig test test.zig
1/1 test "coercion of zero bit types"... OK
All 1 tests passed.
Type Coercion: undefined
undefined can be cast to any type.
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.
- @bitCast - change type but maintain bit representation
- @alignCast - make a pointer have more alignment
- @boolToInt - convert true to 1 and false to 0
- @enumToInt - obtain the integer tag value of an enum or tagged union
- @errSetCast - convert to a smaller error set
- @errorToInt - obtain the integer value of an error code
- @floatCast - convert a larger float to a smaller float
- @floatToInt - obtain the integer part of a float value
- @intCast - convert between integer types
- @intToEnum - obtain an enum value based on its integer tag value
- @intToError - obtain an error code based on its integer value
- @intToFloat - convert an integer to a float value
- @intToPtr - convert an address to a pointer
- @ptrCast - convert between pointer types
- @ptrToInt - obtain the address of a pointer
- @truncate - convert between integer types, chopping off bits
Peer Type Resolution
Peer Type Resolution occurs in these places:
- switch expressions
- if expressions
- while expressions
- for expressions
- Multiple break statements in a block
- Some binary operations
This kind of type resolution chooses a type that all peer types can coerce into. Here are some examples:
peer_type_resolution.zig
const std = @import("std");
const expect = std.testing.expect;
const mem = std.mem;
test "peer resolve int widening" {
var a: i8 = 12;
var b: i16 = 34;
var c = a + b;
expect(c == 46);
expect(@TypeOf(c) == i16);
}
test "peer resolve arrays of different size to const slice" {
expect(mem.eql(u8, boolToStr(true), "true"));
expect(mem.eql(u8, boolToStr(false), "false"));
comptime expect(mem.eql(u8, boolToStr(true), "true"));
comptime expect(mem.eql(u8, boolToStr(false), "false"));
}
fn boolToStr(b: bool) []const u8 {
return if (b) "true" else "false";
}
test "peer resolve array and const slice" {
testPeerResolveArrayConstSlice(true);
comptime testPeerResolveArrayConstSlice(true);
}
fn testPeerResolveArrayConstSlice(b: bool) void {
const value1 = if (b) "aoeu" else @as([]const u8, "zz");
const value2 = if (b) @as([]const u8, "zz") else "aoeu";
expect(mem.eql(u8, value1, "aoeu"));
expect(mem.eql(u8, value2, "zz"));
}
test "peer type resolution: ?T and T" {
expect(peerTypeTAndOptionalT(true, false).? == 0);
expect(peerTypeTAndOptionalT(false, false).? == 3);
comptime {
expect(peerTypeTAndOptionalT(true, false).? == 0);
expect(peerTypeTAndOptionalT(false, false).? == 3);
}
}
fn peerTypeTAndOptionalT(c: bool, b: bool) ?usize {
if (c) {
return if (b) null else @as(usize, 0);
}
return @as(usize, 3);
}
test "peer type resolution: *[0]u8 and []const u8" {
expect(peerTypeEmptyArrayAndSlice(true, "hi").len == 0);
expect(peerTypeEmptyArrayAndSlice(false, "hi").len == 1);
comptime {
expect(peerTypeEmptyArrayAndSlice(true, "hi").len == 0);
expect(peerTypeEmptyArrayAndSlice(false, "hi").len == 1);
}
}
fn peerTypeEmptyArrayAndSlice(a: bool, slice: []const u8) []const u8 {
if (a) {
return &[_]u8{};
}
return slice[0..1];
}
test "peer type resolution: *[0]u8, []const u8, and anyerror![]u8" {
{
var data = "hi".*;
const slice = data[0..];
expect((try peerTypeEmptyArrayAndSliceAndError(true, slice)).len == 0);
expect((try peerTypeEmptyArrayAndSliceAndError(false, slice)).len == 1);
}
comptime {
var data = "hi".*;
const slice = data[0..];
expect((try peerTypeEmptyArrayAndSliceAndError(true, slice)).len == 0);
expect((try peerTypeEmptyArrayAndSliceAndError(false, slice)).len == 1);
}
}
fn peerTypeEmptyArrayAndSliceAndError(a: bool, slice: []u8) anyerror![]u8 {
if (a) {
return &[_]u8{};
}
return slice[0..1];
}
test "peer type resolution: *const T and ?*T" {
const a = @intToPtr(*const usize, 0x123456780);
const b = @intToPtr(?*usize, 0x123456780);
expect(a == b);
expect(b == a);
}
$ zig test peer_type_resolution.zig
1/7 test "peer resolve int widening"... OK
2/7 test "peer resolve arrays of different size to const slice"... OK
3/7 test "peer resolve array and const slice"... OK
4/7 test "peer type resolution: ?T and T"... OK
5/7 test "peer type resolution: *[0]u8 and []const u8"... OK
6/7 test "peer type resolution: *[0]u8, []const u8, and anyerror![]u8"... OK
7/7 test "peer type resolution: *const T and ?*T"... OK
All 7 tests passed.