test:zig
This commit is contained in:
56
guide/language/anonymous-structs.zig
Normal file
56
guide/language/anonymous-structs.zig
Normal file
@@ -0,0 +1,56 @@
|
||||
const expect = @import("std").testing.expect;
|
||||
|
||||
// The struct type may be omitted from a struct literal. These literals may coerce to other struct types.
|
||||
test "anonymous struct literal" {
|
||||
const Point = struct { x: i32, y: i32 };
|
||||
|
||||
const pt: Point = .{
|
||||
.x = 13,
|
||||
.y = 67,
|
||||
};
|
||||
try expect(pt.x == 13);
|
||||
try expect(pt.y == 67);
|
||||
}
|
||||
|
||||
// Anonymous structs may be completely anonymous i.e. without being coerced to another struct type.
|
||||
test "fully anonymous struct" {
|
||||
try dump(.{
|
||||
.int = @as(u32, 1234),
|
||||
.float = @as(f64, 12.34),
|
||||
.b = true,
|
||||
.s = "hi",
|
||||
});
|
||||
}
|
||||
|
||||
fn dump(args: anytype) !void {
|
||||
try expect(args.int == 1234);
|
||||
try expect(args.float == 12.34);
|
||||
try expect(args.b);
|
||||
try expect(args.s[0] == 'h');
|
||||
try expect(args.s[1] == 'i');
|
||||
}
|
||||
|
||||
// Anonymous structs without field names may be created and are referred to as tuples.
|
||||
// These have many of the properties that arrays do; tuples can be iterated over,
|
||||
// indexed, can be used with the `++` and `**` operators, and have a len field.
|
||||
// Internally, these have numbered field names starting at `"0"`,
|
||||
// which may be accessed with the special syntax `@"0"` which acts as an escape for the syntax
|
||||
// - things inside `@""` are always recognised as identifiers.
|
||||
|
||||
// An `inline` loop must be used to iterate over the tuple here, as the type of each tuple field may differ.
|
||||
test "tuple" {
|
||||
const values = .{
|
||||
@as(u32, 1234),
|
||||
@as(f64, 12.34),
|
||||
true,
|
||||
"hi",
|
||||
} ++ .{false} ** 2;
|
||||
try expect(values[0] == 1234);
|
||||
try expect(values[4] == false);
|
||||
inline for (values, 0..) |v, i| {
|
||||
if (i != 2) continue;
|
||||
try expect(v);
|
||||
}
|
||||
try expect(values.len == 6);
|
||||
try expect(values.@"3"[0] == 'h');
|
||||
}
|
||||
22
guide/language/arrays.zig
Normal file
22
guide/language/arrays.zig
Normal file
@@ -0,0 +1,22 @@
|
||||
const std = @import("std");
|
||||
const print = std.debug.print;
|
||||
|
||||
pub fn main() void {
|
||||
const a = [5]u8{ 'h', 'e', 'l', 'l', 'o' };
|
||||
const b = [_]u8{ 'w', 'o', 'r', 'l', 'd' };
|
||||
|
||||
for (a) |elem| {
|
||||
print("{c}", .{elem});
|
||||
}
|
||||
print("\n", .{});
|
||||
for (b) |elem| {
|
||||
print("{c}", .{elem});
|
||||
}
|
||||
print("\n", .{});
|
||||
|
||||
const array = [_]u8{ 'h', 'e', 'l', 'l', 'o' };
|
||||
const length = array.len;
|
||||
print("----------------------------------\n", .{});
|
||||
print("array: {s}\n", .{array});
|
||||
print("length: {d}\n", .{length});
|
||||
}
|
||||
22
guide/language/assignment.zig
Normal file
22
guide/language/assignment.zig
Normal file
@@ -0,0 +1,22 @@
|
||||
const std = @import("std");
|
||||
const print = std.debug.print;
|
||||
|
||||
pub fn main() !void {
|
||||
const constant: i32 = 5; // Immutable variable
|
||||
var variable: i32 = 5000; // Mutable variable
|
||||
|
||||
_ = constant;
|
||||
variable += 1;
|
||||
|
||||
const inferred_constant: i32 = @as(i32, 5);
|
||||
var inferred_variable: i32 = @as(i32, 5000);
|
||||
inferred_variable += 1;
|
||||
print("inferred_constant: {}, inferred_variable: {}\n", .{ inferred_constant, inferred_variable });
|
||||
|
||||
const a: i32 = undefined;
|
||||
const b: i32 = undefined;
|
||||
const c: u8 = undefined;
|
||||
const d: u8 = 170;
|
||||
|
||||
print("a: {}, b: {}, c: {}, d: {}\n", .{ a, b, c, d });
|
||||
}
|
||||
166
guide/language/comptime.zig
Normal file
166
guide/language/comptime.zig
Normal file
@@ -0,0 +1,166 @@
|
||||
const expect = @import("std").testing.expect;
|
||||
const print = @import("std").debug.print;
|
||||
|
||||
// Blocks of code may be forcibly executed at compile time using the `comptime` keyword.
|
||||
// In this example, the variables x and y are equivalent.
|
||||
fn fibonacci(n: u16) u16 {
|
||||
if (n == 0 or n == 1) return n;
|
||||
return fibonacci(n - 1) + fibonacci(n - 2);
|
||||
}
|
||||
|
||||
test "comptime blocks" {
|
||||
const x = comptime fibonacci(10);
|
||||
const y = comptime blk: {
|
||||
break :blk fibonacci(10);
|
||||
};
|
||||
try expect(y == 55);
|
||||
try expect(x == 55);
|
||||
}
|
||||
|
||||
// Integer literals are of the type `comptime_int`.
|
||||
// These are special in a way that they have no size (they cannot be used at runtime!), and they have arbitrary precision.
|
||||
// `comptime_int` values coerce to any integer type that can hold them. They also coerce to floats.
|
||||
// Character literals are of this type.
|
||||
test "comptime_int" {
|
||||
const a = 12;
|
||||
const b = a + 10;
|
||||
|
||||
const c: u4 = a;
|
||||
const d: f32 = b;
|
||||
|
||||
try expect(c == 12);
|
||||
try expect(d == 22);
|
||||
}
|
||||
|
||||
// Types in Zig are values of the type type.
|
||||
// These are available at compile time.
|
||||
// We have previously encountered them by checking `@TypeOf` and comparing with other types, but we can do more.
|
||||
test "branching on types" {
|
||||
const a = 5;
|
||||
const b: if (a < 10) f32 else i32 = 5;
|
||||
print("b: {}, {any}\n", .{ b, @TypeOf(b) });
|
||||
try expect(b == 5);
|
||||
try expect(@TypeOf(b) == f32);
|
||||
}
|
||||
|
||||
// Function parameters in Zig can be tagged as being `comptime`.
|
||||
// This means that the value passed to that function parameter must be known at compile time.
|
||||
// Let's make a function that returns a type.
|
||||
// Notice how this function is PascalCase, as it returns a type.
|
||||
fn Matrix(
|
||||
comptime T: type,
|
||||
comptime width: comptime_int,
|
||||
comptime height: comptime_int,
|
||||
) type {
|
||||
return [height][width]T;
|
||||
}
|
||||
|
||||
test "returning a type" {
|
||||
try expect(Matrix(f32, 4, 4) == [4][4]f32);
|
||||
}
|
||||
|
||||
// We can reflect upon types using the built-in `@typeInfo`, which takes in a type and returns a tagged union.
|
||||
// This tagged union type can be found in `std.builtin.Type` (info on how to make use of imports and `std` later).
|
||||
fn addSmallInts(comptime T: type, a: T, b: T) T {
|
||||
return switch (@typeInfo(T)) {
|
||||
// .ComptimeInt => a + b,
|
||||
// .Int => |info| if (info.bits <= 16) a + b else @compileError("ints too large"),
|
||||
// FIXME: To pass the test, we need to use the `int` tag instead of the `Int` tag and no `.Comptime` field.
|
||||
// - There should be no fields `.Int` and `.ComptimeInt` in `@typeinfo(u16 or i16)` on `zig-1.15.2` version.
|
||||
.int => |info| if (info.bits <= 16) a + b else @compileError("ints too large"),
|
||||
else => @compileError("only ints accepted"),
|
||||
};
|
||||
}
|
||||
|
||||
test "typeinfo switch" {
|
||||
const x = addSmallInts(u7, 20, 30);
|
||||
try expect(@TypeOf(x) == u7);
|
||||
try expect(x == 50);
|
||||
}
|
||||
|
||||
// We can use the `@Type` function to create a type from a `@typeInfo`.
|
||||
// `@Type` is implemented for most types but is notably unimplemented for enums, unions, functions, and structs.
|
||||
// Here anonymous struct syntax is used with
|
||||
// `.{}`, because the `T` in `T{}` can be inferred. Anonymous structs will be covered in detail later. In this example we will get a compile error if the Int tag isn't set.
|
||||
fn GetBiggerInt(comptime T: type) type {
|
||||
return @Type(.{
|
||||
// .Int = .{
|
||||
// .bits = @typeInfo(T).Int.bits + 1,
|
||||
// .signedness = @typeInfo(T).Int.signedness,
|
||||
// FIXME: To pass the test, we need to use the `Int` tag instead of the `int` tag and no `.Comptime` field.
|
||||
.int = .{
|
||||
.bits = @typeInfo(T).int.bits + 1,
|
||||
.signedness = @typeInfo(T).int.signedness,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
test "@Type" {
|
||||
try expect(GetBiggerInt(u8) == u9);
|
||||
try expect(GetBiggerInt(i31) == i32);
|
||||
}
|
||||
|
||||
// Returning a struct type is how you make generic data structures in Zig.
|
||||
// The usage of `@This` is required here, which gets the type of the innermost struct, union, or enum.
|
||||
// Here `std.mem.eql` is also used which compares two slices.
|
||||
fn Vec(
|
||||
comptime count: comptime_int,
|
||||
comptime T: type,
|
||||
) type {
|
||||
return struct {
|
||||
data: [count]T,
|
||||
const Self = @This();
|
||||
|
||||
fn abs(self: Self) Self {
|
||||
var tmp = Self{ .data = undefined };
|
||||
for (self.data, 0..) |elem, i| {
|
||||
tmp.data[i] = if (elem < 0)
|
||||
-elem
|
||||
else
|
||||
elem;
|
||||
}
|
||||
return tmp;
|
||||
}
|
||||
|
||||
fn init(data: [count]T) Self {
|
||||
return Self{ .data = data };
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const eql = @import("std").mem.eql;
|
||||
|
||||
test "generic vector" {
|
||||
const x = Vec(3, f32).init([_]f32{ 10, -10, 5 });
|
||||
const y = x.abs();
|
||||
try expect(eql(f32, &y.data, &[_]f32{ 10, 10, 5 }));
|
||||
}
|
||||
|
||||
// The types of function parameters can also be inferred by using `anytype` in place of a type.
|
||||
// `@TypeOf` can then be used on the parameter.
|
||||
fn plusOne(x: anytype) @TypeOf(x) {
|
||||
return x + 1;
|
||||
}
|
||||
|
||||
test "inferred function parameter" {
|
||||
try expect(plusOne(@as(u32, 1)) == 2);
|
||||
}
|
||||
|
||||
// `comptime` also introduces the operators `++` and `**` for concatenating and repeating arrays and slices.
|
||||
// These operators do not work at runtime.
|
||||
test "++" {
|
||||
const x: [4]u8 = undefined;
|
||||
const y = x[0..];
|
||||
|
||||
const a: [6]u8 = undefined;
|
||||
const b = a[0..];
|
||||
|
||||
const new = y ++ b;
|
||||
try expect(new.len == 10);
|
||||
}
|
||||
|
||||
test "**" {
|
||||
const pattern = [_]u8{ 0xCC, 0xAA };
|
||||
const memory = pattern ** 3;
|
||||
try expect(eql(u8, &memory, &[_]u8{ 0xCC, 0xAA, 0xCC, 0xAA, 0xCC, 0xAA }));
|
||||
}
|
||||
24
guide/language/defer.zig
Normal file
24
guide/language/defer.zig
Normal file
@@ -0,0 +1,24 @@
|
||||
const expect = @import("std").testing.expect;
|
||||
|
||||
test "defer" {
|
||||
var x: i16 = 5;
|
||||
{
|
||||
defer x += 2;
|
||||
try expect(x == 5);
|
||||
}
|
||||
try expect(x == 7);
|
||||
}
|
||||
|
||||
test "multi defer" {
|
||||
var x: f32 = 5;
|
||||
{
|
||||
// When there are multiple defers in a single block, they are executed in reverse order.
|
||||
defer x += 2;
|
||||
defer x /= 2;
|
||||
}
|
||||
try expect(x == 4.5);
|
||||
}
|
||||
|
||||
// Defer is useful to ensure that resources are cleaned up when they are no longer needed.
|
||||
// Instead of needing to remember to manually free up the resource,
|
||||
// you can add a defer statement right next to the statement that allocates the resource.
|
||||
58
guide/language/enums.zig
Normal file
58
guide/language/enums.zig
Normal file
@@ -0,0 +1,58 @@
|
||||
const expect = @import("std").testing.expect;
|
||||
|
||||
// Zig's enums allow you to define types with a restricted set of named values.
|
||||
// Let's declare an enum.
|
||||
const Direction = enum { north, south, east, west };
|
||||
|
||||
// Enums types may have specified (integer) tag types.
|
||||
const Value = enum(u2) { zero, one, two };
|
||||
|
||||
// Enum's ordinal values start at 0. They can be accessed with the built-in function `@intFromEnum`.
|
||||
test "enum ordinal value" {
|
||||
try expect(@intFromEnum(Value.zero) == 0);
|
||||
try expect(@intFromEnum(Value.one) == 1);
|
||||
try expect(@intFromEnum(Value.two) == 2);
|
||||
}
|
||||
|
||||
// Values can be overridden, with the next values continuing from there.
|
||||
const Value2 = enum(u32) {
|
||||
hundred = 100,
|
||||
thousand = 1000,
|
||||
million = 1000000,
|
||||
next,
|
||||
};
|
||||
|
||||
test "set enum ordinal value" {
|
||||
try expect(@intFromEnum(Value2.hundred) == 100);
|
||||
try expect(@intFromEnum(Value2.thousand) == 1000);
|
||||
try expect(@intFromEnum(Value2.million) == 1000000);
|
||||
try expect(@intFromEnum(Value2.next) == 1000001);
|
||||
}
|
||||
|
||||
// Enums can be given methods. These act as namespaced functions that can be called with the dot syntax.
|
||||
const Suit = enum {
|
||||
clubs,
|
||||
spades,
|
||||
diamonds,
|
||||
hearts,
|
||||
pub fn isClubs(self: Suit) bool {
|
||||
return self == Suit.clubs;
|
||||
}
|
||||
};
|
||||
|
||||
test "enum method" {
|
||||
try expect(Suit.spades.isClubs() == Suit.isClubs(.spades));
|
||||
}
|
||||
|
||||
// Enums can also be given var and const declarations.
|
||||
// These act as namespaced globals and their values are unrelated and unattached to instances of the enum type.
|
||||
const Mode = enum {
|
||||
var count: u32 = 0;
|
||||
on,
|
||||
off,
|
||||
};
|
||||
|
||||
test "hmm" {
|
||||
Mode.count += 1;
|
||||
try expect(Mode.count == 1);
|
||||
}
|
||||
111
guide/language/errors.zig
Normal file
111
guide/language/errors.zig
Normal file
@@ -0,0 +1,111 @@
|
||||
const print = @import("std").debug.print;
|
||||
const expect = @import("std").testing.expect;
|
||||
|
||||
// An error set is like an enum (details on Zig's enums later), where each error in the set is a value.
|
||||
// There are no exceptions in Zig; errors are values. Let's create an error set.
|
||||
|
||||
const FileOpenError = error{
|
||||
AccessDenied,
|
||||
OutOfMemory,
|
||||
FileNotFound,
|
||||
};
|
||||
|
||||
// Error sets coerce to their supersets.
|
||||
|
||||
const AllocationError = error{OutOfMemory};
|
||||
|
||||
test "coerce error from a subset to a superset" {
|
||||
const err: FileOpenError = AllocationError.OutOfMemory;
|
||||
try expect(err == FileOpenError.OutOfMemory);
|
||||
}
|
||||
|
||||
// An error set type and another type can be combined with the ! operator to form an error union type.
|
||||
// Values of these types may be an error value or a value of the other type.
|
||||
// Let's create a value of an error union type.
|
||||
// Here catch is used, which is followed by an expression which is evaluated when the value preceding it is an error.
|
||||
// The catch here is used to provide a fallback value,
|
||||
// but could instead be a `noreturn` - the type of return, `while (true)` and others.
|
||||
|
||||
test "error union" {
|
||||
const maybe_error: AllocationError!u16 = 10;
|
||||
const no_error = maybe_error catch 0;
|
||||
|
||||
try expect(@TypeOf(no_error) == u16);
|
||||
try expect(no_error == 10);
|
||||
}
|
||||
|
||||
// Functions often return error unions. Here's one using a catch,
|
||||
// where the |err| syntax receives the value of the error.
|
||||
// This is called payload capturing, and is used similarly in many places.
|
||||
// We'll talk about it in more detail later in the chapter.
|
||||
// Side note: some languages use similar syntax for lambdas - this is not true for Zig.
|
||||
|
||||
fn failingFunction() error{Oops}!void {
|
||||
return error.Oops;
|
||||
}
|
||||
|
||||
test "returning an error" {
|
||||
failingFunction() catch |err| {
|
||||
try expect(err == error.Oops);
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
// `try x` is a shortcut for `x catch |err| return err`,
|
||||
// and is commonly used where handling an error isn't appropriate.
|
||||
// Zig's `try` and `catch` are unrelated to try-catch in other languages.
|
||||
|
||||
fn failFn() error{Oops}!i32 {
|
||||
try failingFunction();
|
||||
return 12;
|
||||
}
|
||||
|
||||
test "try" {
|
||||
const v = failFn() catch |err| {
|
||||
try expect(err == error.Oops);
|
||||
return;
|
||||
};
|
||||
try expect(v == 12); // is never reached
|
||||
}
|
||||
|
||||
// `errdefer` works like `defer`,
|
||||
// but only executing when the function is returned from with an error inside of the `errdefer`'s block.
|
||||
|
||||
var problems: u32 = 98;
|
||||
|
||||
fn failFnCounter() error{Oops}!void {
|
||||
errdefer problems += 1;
|
||||
try failingFunction();
|
||||
}
|
||||
|
||||
test "errdefer" {
|
||||
failFnCounter() catch |err| {
|
||||
try expect(err == error.Oops);
|
||||
try expect(problems == 99);
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
// Error unions returned from a function can have their error sets inferred by not having an explicit error set.
|
||||
// This inferred error set contains all possible errors that the function may return.
|
||||
|
||||
fn createFile() !void {
|
||||
return error.AccessDenied;
|
||||
}
|
||||
|
||||
test "inferred error set" {
|
||||
//type coercion successfully takes place
|
||||
const x: error{AccessDenied}!void = createFile();
|
||||
|
||||
//Zig does not let us ignore error unions via _ = x;
|
||||
//we must unwrap it with "try", "catch", or "if" by any means
|
||||
_ = x catch {};
|
||||
}
|
||||
|
||||
// Error sets can be merged.
|
||||
const A = error{ NotDir, PathNotFound };
|
||||
const B = error{ OutOfMemory, PathNotFound };
|
||||
const C = A || B;
|
||||
|
||||
// `anyerror` is the global error set, which due to being the superset of all error sets,
|
||||
// can have an error from any set coerced to it. Its usage should be generally avoided.
|
||||
39
guide/language/floats.zig
Normal file
39
guide/language/floats.zig
Normal file
@@ -0,0 +1,39 @@
|
||||
const expect = @import("std").testing.expect;
|
||||
|
||||
// Zig's floats are strictly IEEE-compliant unless `@setFloatMode(.Optimized)` is used,
|
||||
// which is equivalent to GCC's `-ffast-math`.
|
||||
// Floats coerce to larger float types.
|
||||
|
||||
test "float widening" {
|
||||
const a: f16 = 0;
|
||||
const b: f32 = a;
|
||||
const c: f128 = b;
|
||||
try expect(c == @as(f128, a));
|
||||
}
|
||||
|
||||
// Floats support multiple kinds of literal.
|
||||
|
||||
const floating_point: f64 = 123.0E+77;
|
||||
const another_float: f64 = 123.0;
|
||||
const yet_another: f64 = 123.0e+77;
|
||||
|
||||
const hex_floating_point: f64 = 0x103.70p-5;
|
||||
const another_hex_float: f64 = 0x103.70;
|
||||
const yet_another_hex_float: f64 = 0x103.70P-5;
|
||||
|
||||
// Underscores may also be placed between digits.
|
||||
|
||||
const lightspeed: f64 = 299_792_458.000_000;
|
||||
const nanosecond: f64 = 0.000_000_001;
|
||||
const more_hex: f64 = 0x1234_5678.9ABC_CDEFp-10;
|
||||
|
||||
// Integers and floats may be converted using the built-in functions `@floatFromInt` and `@intFromFloat`.
|
||||
// `@floatFromInt` is always safe, whereas `@intFromFloat` is detectable illegal behaviour
|
||||
// if the float value cannot fit in the integer destination type.
|
||||
|
||||
test "int-float conversion" {
|
||||
const a: i32 = 0;
|
||||
const b = @as(f32, @floatFromInt(a));
|
||||
const c = @as(i32, @intFromFloat(b));
|
||||
try expect(c == a);
|
||||
}
|
||||
21
guide/language/for.zig
Normal file
21
guide/language/for.zig
Normal file
@@ -0,0 +1,21 @@
|
||||
const expect = @import("std").testing.expect;
|
||||
|
||||
test "for" {
|
||||
//character literals are equivalent to integer literals
|
||||
const string = [_]u8{ 'a', 'b', 'c' };
|
||||
|
||||
for (string, 0..) |character, index| {
|
||||
_ = character;
|
||||
_ = index;
|
||||
}
|
||||
|
||||
for (string) |character| {
|
||||
_ = character;
|
||||
}
|
||||
|
||||
for (string, 0..) |_, index| {
|
||||
_ = index;
|
||||
}
|
||||
|
||||
for (string) |_| {}
|
||||
}
|
||||
30
guide/language/functions.zig
Normal file
30
guide/language/functions.zig
Normal file
@@ -0,0 +1,30 @@
|
||||
const expect = @import("std").testing.expect;
|
||||
|
||||
fn addFive(x: u32) u32 {
|
||||
return x + 5;
|
||||
}
|
||||
|
||||
test "function" {
|
||||
const y = addFive(0);
|
||||
try expect(@TypeOf(y) == u32);
|
||||
try expect(y == 5);
|
||||
}
|
||||
|
||||
fn fibonacci(n: u16) u16 {
|
||||
if (n == 0 or n == 1) return n;
|
||||
return fibonacci(n - 1) + fibonacci(n - 2);
|
||||
}
|
||||
|
||||
test "function recursion" {
|
||||
const x = fibonacci(10);
|
||||
try expect(x == 55);
|
||||
}
|
||||
|
||||
// When recursion happens, the compiler is no longer able to work out the maximum stack size,
|
||||
// which may result in unsafe behaviour - a stack overflow.
|
||||
// Details on how to achieve safe recursion will be covered in the future.
|
||||
//
|
||||
// Values can be ignored using _ instead of a variable or const declaration.
|
||||
// This does not work at the global scope (i.e. it only works inside functions and blocks)
|
||||
// and is useful for ignoring the values returned from functions if you do not need them.
|
||||
// _ = 10; // ignore the value 10
|
||||
15
guide/language/hello-world.zig
Normal file
15
guide/language/hello-world.zig
Normal file
@@ -0,0 +1,15 @@
|
||||
const std = @import("std");
|
||||
const print = std.debug.print;
|
||||
|
||||
// $ zig run hello-world.zig
|
||||
pub fn main() void {
|
||||
print("Hello, {s}!\n", .{"World"});
|
||||
}
|
||||
|
||||
pub fn test_hello_world() []const u8 {
|
||||
return "Hello, Test!\n";
|
||||
}
|
||||
|
||||
test "hello-world" {
|
||||
try std.testing.expectEqualStrings(test_hello_world(), "Hello, Test!\n");
|
||||
}
|
||||
20
guide/language/if-expressions.zig
Normal file
20
guide/language/if-expressions.zig
Normal file
@@ -0,0 +1,20 @@
|
||||
// $ zig test if-expressions.zig
|
||||
const expect = @import("std").testing.expect;
|
||||
|
||||
test "if statement" {
|
||||
const a = true;
|
||||
var x: u16 = 0;
|
||||
if (a) {
|
||||
x += 1;
|
||||
} else {
|
||||
x += 2;
|
||||
}
|
||||
try expect(x == 1);
|
||||
}
|
||||
|
||||
test "if expression" {
|
||||
const a = true;
|
||||
var x: u16 = 0;
|
||||
x += if (a) 1 else 2;
|
||||
try expect(x == 1);
|
||||
}
|
||||
14
guide/language/imports.zig
Normal file
14
guide/language/imports.zig
Normal file
@@ -0,0 +1,14 @@
|
||||
const std = @import("std");
|
||||
const expect = std.testing.expect;
|
||||
const math = std.math;
|
||||
|
||||
// The built-in function `@import` takes in a file, and gives you a struct type based on that file.
|
||||
// All declarations labelled as `pub` (for public) will end up in this struct type, ready for use.
|
||||
|
||||
// `@import("std")` is a special case in the compiler, and gives you access to the standard library.
|
||||
// Other `@import`s will take in a file path, or a package name (more on packages in a later chapter).
|
||||
// We will explore more of the standard library in later chapters.
|
||||
|
||||
test "imports" {
|
||||
try expect(math.pow(f32, 2, 3) == 8.0);
|
||||
}
|
||||
14
guide/language/inline-loops.zig
Normal file
14
guide/language/inline-loops.zig
Normal file
@@ -0,0 +1,14 @@
|
||||
const expect = @import("std").testing.expect;
|
||||
|
||||
// inline loops are unrolled, and allow some things to happen that only work at compile time.
|
||||
// Here we use a for, but a while works similarly.
|
||||
|
||||
test "inline for" {
|
||||
const types = [_]type{ i32, f32, u8, bool };
|
||||
var sum: usize = 0;
|
||||
inline for (types) |T| sum += @sizeOf(T);
|
||||
try expect(sum == 10);
|
||||
}
|
||||
|
||||
// Using these for performance reasons is inadvisable unless you've tested that explicitly unrolling is faster;
|
||||
// the compiler tends to make better decisions here than you.
|
||||
54
guide/language/integer-rules.zig
Normal file
54
guide/language/integer-rules.zig
Normal file
@@ -0,0 +1,54 @@
|
||||
const expect = @import("std").testing.expect;
|
||||
|
||||
// Zig supports hex, octal and binary integer literals.
|
||||
|
||||
const decimal_int: i32 = 98222;
|
||||
const hex_int: u8 = 0xff;
|
||||
const another_hex_int: u8 = 0xFF;
|
||||
const octal_int: u16 = 0o755;
|
||||
const binary_int: u8 = 0b11110000;
|
||||
|
||||
// Underscores may also be placed between digits as a visual separator.
|
||||
|
||||
const one_billion: u64 = 1_000_000_000;
|
||||
const binary_mask: u64 = 0b1_1111_1111;
|
||||
const permissions: u64 = 0o7_5_5;
|
||||
const big_address: u64 = 0xFF80_0000_0000_0000;
|
||||
|
||||
// "Integer Widening" is allowed,
|
||||
// which means that integers of a type may coerce to an integer of another type,
|
||||
// providing that the new type can fit all of the values that the old type can.
|
||||
|
||||
test "integer widening" {
|
||||
const a: u8 = 250;
|
||||
const b: u16 = a;
|
||||
const c: u32 = b;
|
||||
try expect(c == a);
|
||||
}
|
||||
|
||||
// If you have a value stored in an integer that cannot coerce to the type that you want,
|
||||
// `@intCast` may be used to explicitly convert from one type to the other.
|
||||
// If the value given is out of the range of the destination type, this is detectable illegal behaviour.
|
||||
|
||||
test "@intCast" {
|
||||
const x: u64 = 200;
|
||||
const y = @as(u8, @intCast(x));
|
||||
try expect(@TypeOf(y) == u8);
|
||||
}
|
||||
|
||||
// Integers, by default, are not allowed to overflow. Overflows are detectable illegal behaviour.
|
||||
// Sometimes, being able to overflow integers in a well-defined manner is a wanted behaviour.
|
||||
// For this use case, Zig provides overflow operators.
|
||||
// | Normal Operator | Wrapping Operator |
|
||||
// | `+` | `+%` |
|
||||
// | `-` | `-%` |
|
||||
// | `*` | `*%` |
|
||||
// | `+=` | `+=%` |
|
||||
// | `-=` | `-=%` |
|
||||
// | `*=` | `*=%` |
|
||||
|
||||
test "well defined overflow" {
|
||||
var a: u8 = 255;
|
||||
a +%= 1;
|
||||
try expect(a == 0);
|
||||
}
|
||||
24
guide/language/labelled-blocks.zig
Normal file
24
guide/language/labelled-blocks.zig
Normal file
@@ -0,0 +1,24 @@
|
||||
const expect = @import("std").testing.expect;
|
||||
|
||||
// Blocks in Zig are expressions and can be given labels, which are used to yield values.
|
||||
// Here, we are using a label called `blk`.
|
||||
// Blocks yield values, meaning they can be used in place of a value.
|
||||
// The value of an empty block `{}` is a value of the type `void`.
|
||||
|
||||
test "labelled blocks" {
|
||||
const count = blk: {
|
||||
var sum: u32 = 0;
|
||||
var i: u32 = 0;
|
||||
while (i < 10) : (i += 1) sum += i;
|
||||
break :blk sum;
|
||||
};
|
||||
try expect(count == 45);
|
||||
try expect(@TypeOf(count) == u32);
|
||||
}
|
||||
|
||||
// This can be seen as being equivalent to C's `i++`.
|
||||
// blk: {
|
||||
// const tmp = i;
|
||||
// i += 1;
|
||||
// break :blk tmp;
|
||||
// }
|
||||
13
guide/language/labelled-loops.zig
Normal file
13
guide/language/labelled-loops.zig
Normal file
@@ -0,0 +1,13 @@
|
||||
const expect = @import("std").testing.expect;
|
||||
// Loops can be given labels, allowing you to `break` and `continue` to outer loops.
|
||||
|
||||
test "nested continue" {
|
||||
var count: usize = 0;
|
||||
outer: for ([_]i32{ 1, 2, 3, 4, 5, 6, 7, 8 }) |_| {
|
||||
for ([_]i32{ 1, 2, 3, 4, 5 }) |_| {
|
||||
count += 1;
|
||||
continue :outer;
|
||||
}
|
||||
}
|
||||
try expect(count == 8);
|
||||
}
|
||||
16
guide/language/loops-as-expressions.zig
Normal file
16
guide/language/loops-as-expressions.zig
Normal file
@@ -0,0 +1,16 @@
|
||||
const expect = @import("std").testing.expect;
|
||||
// Like `return`, `break` accepts a value. This can be used to yield a value from a loop.
|
||||
// Loops in Zig also have an `else` branch, which is evaluated when the loop is not exited with a `break`.
|
||||
|
||||
fn rangeHasNumber(begin: usize, end: usize, number: usize) bool {
|
||||
var i = begin;
|
||||
return while (i < end) : (i += 1) {
|
||||
if (i == number) {
|
||||
break true;
|
||||
}
|
||||
} else false;
|
||||
}
|
||||
|
||||
test "while loop expression" {
|
||||
try expect(rangeHasNumber(0, 10, 3));
|
||||
}
|
||||
50
guide/language/many-item-pointers.zig
Normal file
50
guide/language/many-item-pointers.zig
Normal file
@@ -0,0 +1,50 @@
|
||||
const expect = @import("std").testing.expect;
|
||||
const print = @import("std").debug.print;
|
||||
|
||||
// Most programs need to keep track of buffers which don't have compile-time known lengths.
|
||||
// Many-item pointers are used for these. These act similarly to their single-item counterparts,
|
||||
// using the syntax `[*]T` instead of `*T`.
|
||||
|
||||
// Here's a rundown of the differences between single and multi-item pointers.
|
||||
|
||||
// Single-item pointers
|
||||
// * dereferenceable by `ptr.*`
|
||||
// * not indexable
|
||||
// * not supports arithmetic
|
||||
// * item size is any size, including unknown
|
||||
// * does not coerce from an array pointer
|
||||
|
||||
// Multi-item pointers
|
||||
// * cannot be dereferenceable
|
||||
// * indexable by `ptr[0]`
|
||||
// * supports arithmetic
|
||||
// * item size is any size, including unknown
|
||||
// * coerces from an array pointer
|
||||
|
||||
// Many-item pointers can have all of the same attributes, such as const, as single-item pointers.
|
||||
// In this example code, we've written a function that can take in a buffer of any length.
|
||||
// Notice how a single-item pointer to an array of bytes coerces into a many-item pointer of bytes.
|
||||
|
||||
fn doubleAllManypointer(buffer: [*]u8, byte_count: usize) void {
|
||||
var i: usize = 0;
|
||||
while (i < byte_count) : (i += 1) buffer[i] *= 2;
|
||||
}
|
||||
|
||||
test "many-item pointers" {
|
||||
var buffer: [100]u8 = [_]u8{1} ** 100;
|
||||
const buffer_ptr: *[100]u8 = &buffer;
|
||||
|
||||
const buffer_many_ptr: [*]u8 = buffer_ptr;
|
||||
doubleAllManypointer(buffer_many_ptr, buffer.len);
|
||||
for (buffer) |byte| try expect(byte == 2);
|
||||
|
||||
const first_elem_ptr: *u8 = &buffer_many_ptr[0];
|
||||
const first_elem_ptr_2: *u8 = @ptrCast(buffer_many_ptr);
|
||||
try expect(first_elem_ptr == first_elem_ptr_2);
|
||||
}
|
||||
|
||||
// Think about what might happen if you passed that function the incorrect `byte_count`.
|
||||
// The programmer is expected to keep track of (or otherwise know) the length of these buffers.
|
||||
// It's worth noting that this function is effectively trusting us to pass us a valid length for the given buffer.
|
||||
// We can convert from a many-item pointer to a single-item pointer by either indexing an element and dereferencing that,
|
||||
// or by using `@ptrCast` to cast the pointer type. This is only valid when the buffer has a length of at least 1.
|
||||
45
guide/language/opaque.zig
Normal file
45
guide/language/opaque.zig
Normal file
@@ -0,0 +1,45 @@
|
||||
const expect = @import("std").testing.expect;
|
||||
const print = @import("std").debug.print;
|
||||
|
||||
// `opaque` types in Zig have an unknown (albeit non-zero) size and alignment.
|
||||
// Because of this these data types cannot be stored directly.
|
||||
// These are used to maintain type safety with pointers to types that we don't have information about.
|
||||
// ```zig
|
||||
// const Window = opaque {};
|
||||
// const Button = opaque {};
|
||||
|
||||
// extern fn show_window(*Window) callconv(.C) void;
|
||||
|
||||
// test "opaque" {
|
||||
// var main_window: *Window = undefined;
|
||||
// show_window(main_window);
|
||||
|
||||
// var ok_button: *Button = undefined;
|
||||
// show_window(ok_button);
|
||||
// }
|
||||
//```
|
||||
// ./test-c1.zig:653:17: error: expected type '*Window', found '*Button'
|
||||
// show_window(ok_button);
|
||||
// ^
|
||||
// ./test-c1.zig:653:17: note: pointer type child 'Button' cannot cast into pointer type child 'Window'
|
||||
// show_window(ok_button);
|
||||
// ^
|
||||
|
||||
// Opaque types may have declarations in their definitions (the same as structs, enums and unions).
|
||||
|
||||
// XXX: This is a C function that is not defined in the Zig code.
|
||||
extern fn show_window(*Window) callconv(.C) void;
|
||||
|
||||
const Window = opaque {
|
||||
fn show(self: *Window) void {
|
||||
show_window(self);
|
||||
}
|
||||
};
|
||||
|
||||
test "opaque with declarations" {
|
||||
var main_window: *Window = undefined;
|
||||
main_window.show();
|
||||
}
|
||||
|
||||
// The typical usecase of opaque is to maintain type safety
|
||||
// when interoperating with C code that does not expose complete type information.
|
||||
75
guide/language/optionals.zig
Normal file
75
guide/language/optionals.zig
Normal file
@@ -0,0 +1,75 @@
|
||||
const expect = @import("std").testing.expect;
|
||||
const print = @import("std").debug.print;
|
||||
|
||||
// Optionals use the syntax `?T` and are used to store the data `null`, or a value of type `T`.
|
||||
test "optional" {
|
||||
var found_index: ?usize = null;
|
||||
const data = [_]i32{ 1, 2, 3, 4, 5, 6, 7, 8, 12 };
|
||||
for (data, 0..) |v, i| {
|
||||
if (v == 10) found_index = i;
|
||||
}
|
||||
try expect(found_index == null);
|
||||
}
|
||||
|
||||
// Optionals support the `orelse` expression, which acts when the optional is null. This unwraps the optional to its child type.
|
||||
test "orelse" {
|
||||
const a: ?f32 = null;
|
||||
const fallback_value: f32 = 0;
|
||||
const b = a orelse fallback_value;
|
||||
try expect(b == 0);
|
||||
try expect(@TypeOf(b) == f32);
|
||||
}
|
||||
|
||||
// `.?` is a shorthand for `orelse unreachable`.
|
||||
// This is used for when you know it is impossible for an optional value to be `null`,
|
||||
// and using this to unwrap a `null` value is detectable illegal behaviour.
|
||||
test "orelse unreachable" {
|
||||
const a: ?f32 = 5;
|
||||
const b = a orelse unreachable;
|
||||
const c = a.?;
|
||||
try expect(b == c);
|
||||
try expect(@TypeOf(c) == f32);
|
||||
}
|
||||
|
||||
// Both `if` expressions and `while` loops support taking optional values as conditions,
|
||||
// allowing you to "capture" the inner non-null value.
|
||||
|
||||
// Here we use an if optional payload capture; a and b are equivalent here.
|
||||
// if (b) |value| captures the value of b (in the cases where b is not null), and makes it available as value.
|
||||
// As in the union example, the captured value is immutable,
|
||||
// but we can still use a pointer capture to modify the value stored in b.
|
||||
test "if optional payload capture" {
|
||||
const a: ?i32 = 5;
|
||||
if (a != null) {
|
||||
const value = a.?;
|
||||
_ = value;
|
||||
}
|
||||
|
||||
var b: ?i32 = 5;
|
||||
if (b) |*value| {
|
||||
value.* += 1;
|
||||
}
|
||||
try expect(b.? == 6);
|
||||
}
|
||||
|
||||
// And with while:
|
||||
var numbers_left: u32 = 4;
|
||||
fn eventuallyNullSequence() ?u32 {
|
||||
if (numbers_left == 0) return null;
|
||||
numbers_left -= 1;
|
||||
return numbers_left;
|
||||
}
|
||||
|
||||
test "while null capture" {
|
||||
var sum: u32 = 0;
|
||||
while (eventuallyNullSequence()) |value| {
|
||||
sum += value;
|
||||
}
|
||||
try expect(sum == 6); // 3 + 2 + 1
|
||||
}
|
||||
|
||||
// Optional pointer and optional slice types do not take up any extra memory compared to non-optional ones.
|
||||
// This is because internally they use the 0 value of the pointer for null.
|
||||
|
||||
// This is how null pointers in Zig work - they must be unwrapped to a non-optional before dereferencing,
|
||||
// which stops null pointer dereferences from happening accidentally.
|
||||
105
guide/language/payload-captures.zig
Normal file
105
guide/language/payload-captures.zig
Normal file
@@ -0,0 +1,105 @@
|
||||
const expect = @import("std").testing.expect;
|
||||
|
||||
// Payload captures use the syntax `|value|` and appear in many places, some of which we've seen already.
|
||||
// Wherever they appear, they are used to "capture" the value from something.
|
||||
|
||||
// With if statements and optionals.
|
||||
test "optional-if" {
|
||||
const maybe_num: ?usize = 10;
|
||||
if (maybe_num) |n| {
|
||||
try expect(@TypeOf(n) == usize);
|
||||
try expect(n == 10);
|
||||
} else {
|
||||
unreachable;
|
||||
}
|
||||
}
|
||||
|
||||
// With if statements and error unions. The else with the error capture is required here.
|
||||
test "error union if" {
|
||||
const ent_num: error{UnknownEntity}!u32 = 5;
|
||||
if (ent_num) |entity| {
|
||||
try expect(@TypeOf(entity) == u32);
|
||||
try expect(entity == 5);
|
||||
} else |err| {
|
||||
_ = err catch {};
|
||||
unreachable;
|
||||
}
|
||||
}
|
||||
|
||||
// With while loops and optionals. This may have an else block.
|
||||
test "while optional" {
|
||||
const sequence = [_]?u8{ 0xFF, 0xCC, 0x00, null };
|
||||
|
||||
var i: usize = 0;
|
||||
while (sequence[i]) |num| : (i += 1) {
|
||||
try expect(@TypeOf(num) == u8);
|
||||
}
|
||||
|
||||
try expect(i == 3);
|
||||
try expect(sequence[i] == null);
|
||||
}
|
||||
|
||||
// With while loops and error unions. The else with the error capture is required here.
|
||||
var numbers_left2: u32 = undefined;
|
||||
|
||||
fn eventuallyErrorSequence() !u32 {
|
||||
return if (numbers_left2 == 0) error.ReachedZero else blk: {
|
||||
numbers_left2 -= 1;
|
||||
break :blk numbers_left2;
|
||||
};
|
||||
}
|
||||
|
||||
test "while error union capture" {
|
||||
var sum: u32 = 0;
|
||||
numbers_left2 = 3;
|
||||
while (eventuallyErrorSequence()) |value| {
|
||||
sum += value;
|
||||
} else |err| {
|
||||
try expect(err == error.ReachedZero);
|
||||
}
|
||||
}
|
||||
|
||||
// For loops.
|
||||
test "for capture" {
|
||||
const x = [_]i8{ 1, 5, 120, -5 };
|
||||
for (x) |v| try expect(@TypeOf(v) == i8);
|
||||
}
|
||||
|
||||
// Switch cases on tagged unions.
|
||||
const Info = union(enum) {
|
||||
a: u32,
|
||||
b: []const u8,
|
||||
c,
|
||||
d: u32,
|
||||
};
|
||||
|
||||
test "switch capture" {
|
||||
const b = Info{ .a = 10 };
|
||||
const x = switch (b) {
|
||||
.b => |str| blk: {
|
||||
try expect(@TypeOf(str) == []const u8);
|
||||
break :blk 1;
|
||||
},
|
||||
.c => 2,
|
||||
//if these are of the same type, they
|
||||
//may be inside the same capture group
|
||||
.a, .d => |num| blk: {
|
||||
try expect(@TypeOf(num) == u32);
|
||||
break :blk num * 2;
|
||||
},
|
||||
};
|
||||
try expect(x == 20);
|
||||
}
|
||||
|
||||
// As we saw in the Union and Optional sections above,
|
||||
// values captured with the `|val|` syntax are immutable (similar to function arguments),
|
||||
// but we can use pointer capture to modify the original values.
|
||||
// This captures the values as pointers that are themselves still immutable,
|
||||
// but because the value is now a pointer, we can modify the original value by dereferencing it:
|
||||
const eql = @import("std").mem.eql;
|
||||
|
||||
test "for with pointer capture" {
|
||||
var data = [_]u8{ 1, 2, 3 };
|
||||
for (&data) |*byte| byte.* += 1;
|
||||
try expect(eql(u8, &data, &[_]u8{ 2, 3, 4 }));
|
||||
}
|
||||
49
guide/language/pointers.zig
Normal file
49
guide/language/pointers.zig
Normal file
@@ -0,0 +1,49 @@
|
||||
const expect = @import("std").testing.expect;
|
||||
const print = @import("std").debug.print;
|
||||
|
||||
// Normal pointers in Zig cannot have 0 or null as a value. They follow the syntax *T, where T is the child type.
|
||||
// Referencing is done with &variable, and dereferencing is done with variable.*.
|
||||
fn increment(num: *u8) void {
|
||||
num.* += 1;
|
||||
}
|
||||
|
||||
test "pointers" {
|
||||
var x: u8 = 1;
|
||||
increment(&x);
|
||||
try expect(x == 2);
|
||||
}
|
||||
|
||||
// Trying to set a *T to the value 0 is detectable illegal behaviour.
|
||||
test "naughty pointer" {
|
||||
var x: u16 = 5;
|
||||
x -= 5;
|
||||
var y: *u8 = @ptrFromInt(x);
|
||||
y = y;
|
||||
}
|
||||
|
||||
// Test [23/126] test.naughty pointer... thread 21598 panic: cast causes pointer to be null
|
||||
// ./test-c1.zig:252:18: 0x260a91 in test.naughty pointer (test)
|
||||
// var y: *u8 = @ptrFromInt(x);
|
||||
// ^
|
||||
|
||||
// Zig also has const pointers, which cannot be used to modify the referenced data.
|
||||
// Referencing a const variable will yield a const pointer.
|
||||
|
||||
test "const pointers" {
|
||||
// const x: u8 = 1;
|
||||
// var y = &x;
|
||||
// y.* += 1;
|
||||
}
|
||||
|
||||
// error: cannot assign to constant
|
||||
// y.* += 1;
|
||||
// ^
|
||||
// A *T coerces to a *const T.
|
||||
|
||||
// Pointer Sized Integers
|
||||
// `usize` and `isize` are given as unsigned and signed integers which are the same size as pointers.
|
||||
|
||||
test "usize" {
|
||||
try expect(@sizeOf(usize) == @sizeOf(*u8));
|
||||
try expect(@sizeOf(isize) == @sizeOf(*u8));
|
||||
}
|
||||
10
guide/language/run-test.zig
Normal file
10
guide/language/run-test.zig
Normal file
@@ -0,0 +1,10 @@
|
||||
const std = @import("std");
|
||||
const expect = std.testing.expect;
|
||||
|
||||
test "always passes" {
|
||||
try expect(true);
|
||||
}
|
||||
|
||||
// test "always fails" {
|
||||
// try expect(false);
|
||||
// }
|
||||
36
guide/language/runtime-safety.zig
Normal file
36
guide/language/runtime-safety.zig
Normal file
@@ -0,0 +1,36 @@
|
||||
// Zig provides a level of safety, where problems may be found during execution.
|
||||
// Safety can be left on, or turned off.
|
||||
// Zig has many cases of so-called `detectable illegal behaviour`,
|
||||
// meaning that illegal behaviour will be caught (causing a panic) with safety on,
|
||||
// but will result in undefined behaviour with safety off.
|
||||
// Users are strongly recommended to develop and test their software with safety on, despite its speed penalties.
|
||||
|
||||
// For example, runtime safety protects you from out of bounds indices.
|
||||
|
||||
test "out of bounds" {
|
||||
const a = [3]u8{ 1, 2, 3 };
|
||||
var index: u8 = 5;
|
||||
const b = a[index];
|
||||
|
||||
_ = b;
|
||||
index = index;
|
||||
}
|
||||
|
||||
// test "out of bounds"...index out of bounds
|
||||
// .\tests.zig:43:14: 0x7ff698cc1b82 in test "out of bounds" (test.obj)
|
||||
// const b = a[index];
|
||||
// ^
|
||||
|
||||
// The user may disable runtime safety for the current block using the built-in function `@setRuntimeSafety`.
|
||||
test "out of bounds, no safety" {
|
||||
@setRuntimeSafety(true);
|
||||
// defer @setRuntimeSafety(false);
|
||||
const a = [3]u8{ 1, 2, 3 };
|
||||
var index: u8 = 5;
|
||||
const b = a[index]; // no panic
|
||||
|
||||
_ = b;
|
||||
index = index; // no panic
|
||||
}
|
||||
|
||||
// Safety is off for some build modes (to be discussed later).
|
||||
78
guide/language/sentinel-termination.zig
Normal file
78
guide/language/sentinel-termination.zig
Normal file
@@ -0,0 +1,78 @@
|
||||
const expect = @import("std").testing.expect;
|
||||
|
||||
// Arrays, slices and many pointers may be terminated by a value of their child type.
|
||||
// This is known as sentinel termination. These follow the syntax `[N:t]T`, `[:t]T`, and `[*:t]T`,
|
||||
// where `t` is a value of the child type `T`.
|
||||
|
||||
// An example of a sentinel terminated array. The built-in `@ptrCast` is used to perform an unsafe type conversion.
|
||||
// This shows us that the last element of the array is followed by a 0 byte.
|
||||
test "sentinel termination" {
|
||||
const terminated = [3:0]u8{ 3, 2, 1 };
|
||||
try expect(terminated.len == 3);
|
||||
try expect(@as(*const [4]u8, @ptrCast(&terminated))[3] == 0);
|
||||
}
|
||||
|
||||
// The types of string literals is `*const [N:0]u8`, where N is the length of the string.
|
||||
// This allows string literals to coerce to sentinel terminated slices, and sentinel terminated many pointers.
|
||||
// Note: string literals are UTF-8 encoded.
|
||||
test "string literal" {
|
||||
try expect(@TypeOf("hello") == *const [5:0]u8);
|
||||
}
|
||||
|
||||
// `[*:0]u8` and `[*:0]const u8` perfectly model C's strings.
|
||||
const eql = @import("std").mem.eql;
|
||||
test "C string" {
|
||||
const c_string: [*:0]const u8 = "hello";
|
||||
var array: [5]u8 = undefined;
|
||||
|
||||
var i: usize = 0;
|
||||
while (c_string[i] != 0) : (i += 1) {
|
||||
array[i] = c_string[i];
|
||||
}
|
||||
|
||||
// XXX: Maybe we should use `c_string[0..i]` instead of `c_string[0..5]`?
|
||||
// try expect(eql(u8, &array, c_string[0..i]));
|
||||
}
|
||||
|
||||
// Sentinel terminated types coerce to their non-sentinel-terminated counterparts.
|
||||
test "coercion" {
|
||||
const a: [*:0]u8 = undefined;
|
||||
const b: [*]u8 = a;
|
||||
|
||||
const c: [5:0]u8 = undefined;
|
||||
const d: [5]u8 = c;
|
||||
|
||||
const e: [:0]f32 = undefined;
|
||||
const f: []f32 = e;
|
||||
|
||||
_ = .{ b, d, f }; //ignore unused
|
||||
}
|
||||
|
||||
// Sentinel terminated slicing is provided
|
||||
// which can be used to create a sentinel terminated slice with the syntax `x[n..m:t]`,
|
||||
// where `t` is the terminator value.
|
||||
// Doing this is an assertion from the programmer that the memory is terminated where it should be
|
||||
// - getting this wrong is detectable illegal behaviour.
|
||||
test "sentinel terminated slicing" {
|
||||
var x = [_:0]u8{255} ** 3;
|
||||
const y = x[0..3 :0];
|
||||
_ = y;
|
||||
}
|
||||
|
||||
// My tests here to understand the concept better
|
||||
const print = @import("std").debug.print;
|
||||
test "sentianel checking" {
|
||||
const x = [_:0]u8{ 'h', 'i', @as(u8, 0), 'w', 'o', 'r', 'l', 'd', @as(u8, 0) };
|
||||
// To validate the memory is terminated where it should be, we can use the `x[n..m:t]` syntax,
|
||||
const y = x[0..2 :@as(u8, 0)];
|
||||
|
||||
// print("\n", .{});
|
||||
// print("x: {any}\n", .{x});
|
||||
// print("y: {any}\n", .{y});
|
||||
|
||||
try expect(x[2] == 0);
|
||||
try expect(y.len == 2);
|
||||
try expect(y[0] == 'h');
|
||||
try expect(y[1] == 'i');
|
||||
try expect(y[2] == 0);
|
||||
}
|
||||
38
guide/language/slices.zig
Normal file
38
guide/language/slices.zig
Normal file
@@ -0,0 +1,38 @@
|
||||
const expect = @import("std").testing.expect;
|
||||
|
||||
// Slices can be thought of many-item pointers (`[*]T`) with a length (usize). These use the syntax `[]T`.
|
||||
// Slices are easier to use safely and more convinient than many-item pointers,
|
||||
// as they store the valid length of the buffer with them.
|
||||
// Slices are sometimes referred to as "fat pointers" as they're typically double the size of a normal pointer.
|
||||
// Slices are the most common way to pass around buffers in Zig.
|
||||
|
||||
// * COMMING FROM GO?
|
||||
// Slicing in Zig is similar to slicing in Go, but you replace array[start:end] with array[start..end].
|
||||
// Moreover, in Go, there is no explicit ownership or memory management, meaning that slices point to memory owned by the garbage collector. However in Zig, slices point to manually-managed memory; slices are not tied to memory allocation. This has important implications:
|
||||
// - The validity and lifetime of the backing memory is in the hands of the programmer.
|
||||
// - Zig slices do not have a Cap field, as they do not resize.
|
||||
// For a resizeable/appendable buffer with ownership, have a look at ArrayList.
|
||||
|
||||
fn total(values: []const u8) usize {
|
||||
var sum: usize = 0;
|
||||
for (values) |v| sum += v;
|
||||
return sum;
|
||||
}
|
||||
|
||||
test "slices" {
|
||||
const array = [_]u8{ 1, 2, 3, 4, 5 };
|
||||
const slice = array[0..3];
|
||||
try expect(total(slice) == 6);
|
||||
}
|
||||
|
||||
test "slices 2" {
|
||||
const array = [_]u8{ 1, 2, 3, 4, 5 };
|
||||
const slice = array[0..3];
|
||||
try expect(@TypeOf(slice) == *const [3]u8);
|
||||
}
|
||||
|
||||
test "slices 3" {
|
||||
var array = [_]u8{ 1, 2, 3, 4, 5 };
|
||||
const slice = array[0..];
|
||||
_ = slice;
|
||||
}
|
||||
66
guide/language/struct.zig
Normal file
66
guide/language/struct.zig
Normal file
@@ -0,0 +1,66 @@
|
||||
const expect = @import("std").testing.expect;
|
||||
|
||||
// Structs are Zig's most common kind of composite data type,
|
||||
// allowing you to define types that can store a fixed set of named fields.
|
||||
// Zig gives no guarantees about the in-memory order of fields in a struct or its size.
|
||||
// Like arrays, structs are also neatly constructed with T{} syntax.
|
||||
// Here is an example of declaring and filling a struct.
|
||||
|
||||
const Vec3 = struct { x: f32, y: f32, z: f32 };
|
||||
|
||||
test "struct usage" {
|
||||
const my_vector = Vec3{
|
||||
.x = 0,
|
||||
.y = 100,
|
||||
.z = 50,
|
||||
};
|
||||
_ = my_vector;
|
||||
}
|
||||
|
||||
// Struct fields cannot be implicitly uninitialised:
|
||||
test "missing struct field" {
|
||||
const my_vector = Vec3{
|
||||
// .y = 100,
|
||||
.x = 0,
|
||||
.z = 50,
|
||||
};
|
||||
_ = my_vector;
|
||||
}
|
||||
|
||||
// error: missing field: 'y'
|
||||
// const my_vector = Vec3{
|
||||
// ^
|
||||
|
||||
// Fields may be given defaults:
|
||||
const Vec4 = struct { x: f32 = 0, y: f32 = 0, z: f32 = 0, w: f32 = 0 };
|
||||
|
||||
test "struct defaults" {
|
||||
const my_vector = Vec4{
|
||||
.x = 25,
|
||||
.y = -50,
|
||||
};
|
||||
_ = my_vector;
|
||||
}
|
||||
|
||||
// Like enums, structs may also contain functions and declarations.
|
||||
// Structs have the unique property that when given a pointer to a struct,
|
||||
// one level of dereferencing is done automatically when accessing fields.
|
||||
// Notice how, in this example, self.x and self.y are accessed in the swap function
|
||||
// without needing to dereference the self pointer.
|
||||
|
||||
const Stuff = struct {
|
||||
x: i32,
|
||||
y: i32,
|
||||
fn swap(self: *Stuff) void {
|
||||
const tmp = self.x;
|
||||
self.x = self.y;
|
||||
self.y = tmp;
|
||||
}
|
||||
};
|
||||
|
||||
test "automatic dereference" {
|
||||
var thing = Stuff{ .x = 10, .y = 20 };
|
||||
thing.swap();
|
||||
try expect(thing.x == 20);
|
||||
try expect(thing.y == 10);
|
||||
}
|
||||
36
guide/language/switch.zig
Normal file
36
guide/language/switch.zig
Normal file
@@ -0,0 +1,36 @@
|
||||
const expect = @import("std").testing.expect;
|
||||
|
||||
// Zig's switch works as both a statement and an expression.
|
||||
// The types of all branches must coerce to the type which is being switched upon.
|
||||
// All possible values must have an associated branch - values cannot be left out.
|
||||
// Cases cannot fall through to other branches.
|
||||
|
||||
// An example of a switch statement. The else is required to satisfy the exhaustiveness of this switch.
|
||||
test "switch statement" {
|
||||
var x: i8 = 10;
|
||||
switch (x) {
|
||||
-1...1 => {
|
||||
x = -x;
|
||||
},
|
||||
10, 100 => {
|
||||
//special considerations must be made
|
||||
//when dividing signed integers
|
||||
x = @divExact(x, 10);
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
try expect(x == 1);
|
||||
}
|
||||
|
||||
// Here is the former, but as a switch expression.
|
||||
test "switch expression" {
|
||||
var x: i8 = 10;
|
||||
x = switch (x) {
|
||||
-1...1 => -x,
|
||||
10, 100 => @divExact(x, 10),
|
||||
else => x,
|
||||
};
|
||||
try expect(x == 1);
|
||||
}
|
||||
|
||||
// Now is the perfect time to use what you've learned and solve a problem together.
|
||||
49
guide/language/unions.zig
Normal file
49
guide/language/unions.zig
Normal file
@@ -0,0 +1,49 @@
|
||||
const expect = @import("std").testing.expect;
|
||||
|
||||
// Zig's unions allow you to define types that store one value of many possible typed fields;
|
||||
// only one field may be active at one time.
|
||||
|
||||
// Bare union types do not have a guaranteed memory layout.
|
||||
// Because of this, bare unions cannot be used to reinterpret memory.
|
||||
// Accessing a field in a union that is not active is detectable illegal behaviour.
|
||||
|
||||
const Result = union {
|
||||
int: i64,
|
||||
float: f64,
|
||||
bool: bool,
|
||||
};
|
||||
|
||||
test "simple union" {
|
||||
var result = Result{ .int = 1234 };
|
||||
result.float = 12.34;
|
||||
|
||||
// test "simple union"...access of inactive union field
|
||||
// .\tests.zig:342:12: 0x7ff62c89244a in test "simple union" (test.obj)
|
||||
// result.float = 12.34;
|
||||
// ^
|
||||
}
|
||||
|
||||
// Tagged unions are unions that use an enum to detect which field is active.
|
||||
// Here we make use of payload capturing again,
|
||||
// to switch on the tag type of a union while also capturing the value it contains.
|
||||
// Here we use a pointer capture; captured values are immutable,
|
||||
// but with the `|*value|` syntax, we can capture a pointer to the values instead of the values themselves.
|
||||
// This allows us to use dereferencing to mutate the original value.
|
||||
const Tag = enum { a, b, c };
|
||||
const Tagged = union(Tag) { a: u8, b: f32, c: bool };
|
||||
|
||||
test "switch on tagged union" {
|
||||
var value = Tagged{ .b = 1.5 };
|
||||
switch (value) {
|
||||
.a => |*byte| byte.* += 1,
|
||||
.b => |*float| float.* *= 2,
|
||||
.c => |*b| b.* = !b.*,
|
||||
}
|
||||
try expect(value.b == 3);
|
||||
}
|
||||
|
||||
// The tag type of a tagged union can also be inferred. This is equivalent to the Tagged type above.
|
||||
// const Tagged = union(enum) { a: u8, b: f32, c: bool };
|
||||
|
||||
// `void` member types can have their type omitted from the syntax. Here, `none` is of type `void`.
|
||||
const Tagged2 = union(enum) { a: u8, b: f32, c: bool, none };
|
||||
30
guide/language/unreachable.zig
Normal file
30
guide/language/unreachable.zig
Normal file
@@ -0,0 +1,30 @@
|
||||
const expect = @import("std").testing.expect;
|
||||
|
||||
// `unreachable` is an assertion to the compiler that this statement will not be reached.
|
||||
// It can tell the compiler that a branch is impossible, which the optimiser can then take advantage of.
|
||||
// Reaching an `unreachable` is detectable illegal behaviour.
|
||||
// As it is of the type `noreturn`, it is compatible with all other types. Here it coerces to `u32`.
|
||||
test "unreachable" {
|
||||
const x: i32 = 1;
|
||||
const y: u32 = if (x == 2) 5 else unreachable;
|
||||
_ = y;
|
||||
}
|
||||
|
||||
// test "unreachable"...reached unreachable code
|
||||
// .\tests.zig:211:39: 0x7ff7e29b2049 in test "unreachable" (test.obj)
|
||||
// const y: u32 = if (x == 2) 5 else unreachable;
|
||||
// ^
|
||||
|
||||
// Here is an unreachable being used in a switch.
|
||||
fn asciiToUpper(x: u8) u8 {
|
||||
return switch (x) {
|
||||
'a'...'z' => x + 'A' - 'a',
|
||||
'A'...'Z' => x,
|
||||
else => unreachable,
|
||||
};
|
||||
}
|
||||
|
||||
test "unreachable switch" {
|
||||
try expect(asciiToUpper('a') == 'A');
|
||||
try expect(asciiToUpper('A') == 'A');
|
||||
}
|
||||
63
guide/language/vector.zig
Normal file
63
guide/language/vector.zig
Normal file
@@ -0,0 +1,63 @@
|
||||
const expect = @import("std").testing.expect;
|
||||
|
||||
// Zig provides vector types for SIMD.
|
||||
// These are not to be conflated with vectors in a mathematical sense,
|
||||
// or vectors like C++'s `std::vector` (for this, see "Arraylist" in chapter 2).
|
||||
// Vectors may be created using the `@Type` built-in we used earlier, and `std.meta.Vector` provides a shorthand for this.
|
||||
|
||||
// Vectors can only have child types of booleans, integers, floats and pointers.
|
||||
|
||||
// Operations between vectors with the same child type and length can take place.
|
||||
// These operations are performed on each of the values in the vector.`std.meta.eql` is used here
|
||||
// to check for equality between two vectors (also useful for other types like structs).
|
||||
|
||||
const meta = @import("std").meta;
|
||||
|
||||
test "vector add" {
|
||||
const x: @Vector(4, f32) = .{ 1, -10, 20, -1 };
|
||||
const y: @Vector(4, f32) = .{ 2, 10, 0, 1 };
|
||||
const z = x + y;
|
||||
try expect(meta.eql(z, @Vector(4, f32){ 3, 0, 20, 0 }));
|
||||
}
|
||||
|
||||
// Vectors are indexable.
|
||||
test "vector indexing" {
|
||||
const x: @Vector(4, u8) = .{ 255, 0, 255, 0 };
|
||||
try expect(x[0] == 255);
|
||||
}
|
||||
|
||||
// The built-in function `@splat` may be used to construct a vector where all of the values are the same.
|
||||
// Here we use it to multiply a vector by a scalar.
|
||||
test "vector * scalar" {
|
||||
const x: @Vector(3, f32) = .{ 12.5, 37.5, 2.5 };
|
||||
const y = x * @as(@Vector(3, f32), @splat(2)); // as to @Vector(3, f32) = .{ 2, 2, 2 }
|
||||
try expect(meta.eql(y, @Vector(3, f32){ 25, 75, 5 }));
|
||||
}
|
||||
|
||||
// Vectors do not have a len field like arrays, but may still be looped over.
|
||||
test "vector looping" {
|
||||
const x = @Vector(4, u8){ 255, 0, 255, 0 };
|
||||
const sum = blk: {
|
||||
var tmp: u10 = 0;
|
||||
var i: u8 = 0;
|
||||
while (i < 4) : (i += 1) tmp += x[i];
|
||||
break :blk tmp;
|
||||
};
|
||||
try expect(sum == 510);
|
||||
}
|
||||
|
||||
// Vectors coerce to their respective arrays.
|
||||
const arr: [4]f32 = @Vector(4, f32){ 1, 2, 3, 4 };
|
||||
// XXX: My tests
|
||||
test "vector coercion" {
|
||||
const x: @Vector(4, f32) = .{ 1, 2, 3, 4 };
|
||||
const y: [4]f32 = x;
|
||||
try expect(y[0] == 1);
|
||||
try expect(y[1] == 2);
|
||||
try expect(y[2] == 3);
|
||||
try expect(y[3] == 4);
|
||||
try expect(y.len == 4);
|
||||
}
|
||||
|
||||
// It is worth noting that using explicit vectors may result in slower software if you do not make the right decisions
|
||||
// - the compiler's auto-vectorisation is fairly smart as-is.
|
||||
41
guide/language/while.zig
Normal file
41
guide/language/while.zig
Normal file
@@ -0,0 +1,41 @@
|
||||
// $ zig test while.zig
|
||||
// const expect = @import("std").testing.expect;
|
||||
const std = @import("std");
|
||||
const expect = std.testing.expect;
|
||||
|
||||
test "while" {
|
||||
var i: u8 = 2;
|
||||
while (i < 100) {
|
||||
i *= 2;
|
||||
}
|
||||
try expect(i == 128);
|
||||
}
|
||||
|
||||
test "while with continue expression" {
|
||||
var sum: u8 = 0;
|
||||
var i: u8 = 1;
|
||||
while (i <= 10) : (i += 1) {
|
||||
sum += i;
|
||||
}
|
||||
try expect(sum == 55);
|
||||
}
|
||||
|
||||
test "while with continue" {
|
||||
var sum: u8 = 0;
|
||||
var i: u8 = 0;
|
||||
while (i <= 3) : (i += 1) {
|
||||
if (i == 2) continue;
|
||||
sum += i;
|
||||
}
|
||||
try expect(sum == 4);
|
||||
}
|
||||
|
||||
test "while with break" {
|
||||
var sum: u8 = 0;
|
||||
var i: u8 = 0;
|
||||
while (i <= 3) : (i += 1) {
|
||||
if (i == 2) break;
|
||||
sum += i;
|
||||
}
|
||||
try expect(sum == 1);
|
||||
}
|
||||
Reference in New Issue
Block a user