diff --git a/guide/language/anonymous-structs.zig b/guide/language/anonymous-structs.zig new file mode 100644 index 0000000..792c73e --- /dev/null +++ b/guide/language/anonymous-structs.zig @@ -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'); +} diff --git a/guide/language/arrays.zig b/guide/language/arrays.zig new file mode 100644 index 0000000..fd81908 --- /dev/null +++ b/guide/language/arrays.zig @@ -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}); +} diff --git a/guide/language/assignment.zig b/guide/language/assignment.zig new file mode 100644 index 0000000..3ed0083 --- /dev/null +++ b/guide/language/assignment.zig @@ -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 }); +} diff --git a/guide/language/comptime.zig b/guide/language/comptime.zig new file mode 100644 index 0000000..bd1b90b --- /dev/null +++ b/guide/language/comptime.zig @@ -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 })); +} diff --git a/guide/language/defer.zig b/guide/language/defer.zig new file mode 100644 index 0000000..a0e7f3f --- /dev/null +++ b/guide/language/defer.zig @@ -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. diff --git a/guide/language/enums.zig b/guide/language/enums.zig new file mode 100644 index 0000000..efa3bcb --- /dev/null +++ b/guide/language/enums.zig @@ -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); +} diff --git a/guide/language/errors.zig b/guide/language/errors.zig new file mode 100644 index 0000000..634925a --- /dev/null +++ b/guide/language/errors.zig @@ -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. diff --git a/guide/language/floats.zig b/guide/language/floats.zig new file mode 100644 index 0000000..91ec063 --- /dev/null +++ b/guide/language/floats.zig @@ -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); +} diff --git a/guide/language/for.zig b/guide/language/for.zig new file mode 100644 index 0000000..597b6cc --- /dev/null +++ b/guide/language/for.zig @@ -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) |_| {} +} diff --git a/guide/language/functions.zig b/guide/language/functions.zig new file mode 100644 index 0000000..2e30e0c --- /dev/null +++ b/guide/language/functions.zig @@ -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 diff --git a/guide/language/hello-world.zig b/guide/language/hello-world.zig new file mode 100644 index 0000000..cb18fd7 --- /dev/null +++ b/guide/language/hello-world.zig @@ -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"); +} diff --git a/guide/language/if-expressions.zig b/guide/language/if-expressions.zig new file mode 100644 index 0000000..83c0157 --- /dev/null +++ b/guide/language/if-expressions.zig @@ -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); +} diff --git a/guide/language/imports.zig b/guide/language/imports.zig new file mode 100644 index 0000000..8a86428 --- /dev/null +++ b/guide/language/imports.zig @@ -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); +} diff --git a/guide/language/inline-loops.zig b/guide/language/inline-loops.zig new file mode 100644 index 0000000..33d3858 --- /dev/null +++ b/guide/language/inline-loops.zig @@ -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. diff --git a/guide/language/integer-rules.zig b/guide/language/integer-rules.zig new file mode 100644 index 0000000..b5b6eca --- /dev/null +++ b/guide/language/integer-rules.zig @@ -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); +} diff --git a/guide/language/labelled-blocks.zig b/guide/language/labelled-blocks.zig new file mode 100644 index 0000000..cc2a030 --- /dev/null +++ b/guide/language/labelled-blocks.zig @@ -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; +// } diff --git a/guide/language/labelled-loops.zig b/guide/language/labelled-loops.zig new file mode 100644 index 0000000..e3e90f0 --- /dev/null +++ b/guide/language/labelled-loops.zig @@ -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); +} diff --git a/guide/language/loops-as-expressions.zig b/guide/language/loops-as-expressions.zig new file mode 100644 index 0000000..3188f35 --- /dev/null +++ b/guide/language/loops-as-expressions.zig @@ -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)); +} diff --git a/guide/language/many-item-pointers.zig b/guide/language/many-item-pointers.zig new file mode 100644 index 0000000..c788736 --- /dev/null +++ b/guide/language/many-item-pointers.zig @@ -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. diff --git a/guide/language/opaque.zig b/guide/language/opaque.zig new file mode 100644 index 0000000..887d036 --- /dev/null +++ b/guide/language/opaque.zig @@ -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. diff --git a/guide/language/optionals.zig b/guide/language/optionals.zig new file mode 100644 index 0000000..9491a8c --- /dev/null +++ b/guide/language/optionals.zig @@ -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. diff --git a/guide/language/payload-captures.zig b/guide/language/payload-captures.zig new file mode 100644 index 0000000..8dbe658 --- /dev/null +++ b/guide/language/payload-captures.zig @@ -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 })); +} diff --git a/guide/language/pointers.zig b/guide/language/pointers.zig new file mode 100644 index 0000000..d0bcb15 --- /dev/null +++ b/guide/language/pointers.zig @@ -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)); +} diff --git a/guide/language/run-test.zig b/guide/language/run-test.zig new file mode 100644 index 0000000..9397bfd --- /dev/null +++ b/guide/language/run-test.zig @@ -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); +// } diff --git a/guide/language/runtime-safety.zig b/guide/language/runtime-safety.zig new file mode 100644 index 0000000..bfbb59a --- /dev/null +++ b/guide/language/runtime-safety.zig @@ -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). diff --git a/guide/language/sentinel-termination.zig b/guide/language/sentinel-termination.zig new file mode 100644 index 0000000..ede30bf --- /dev/null +++ b/guide/language/sentinel-termination.zig @@ -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); +} diff --git a/guide/language/slices.zig b/guide/language/slices.zig new file mode 100644 index 0000000..bf5b1f1 --- /dev/null +++ b/guide/language/slices.zig @@ -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; +} diff --git a/guide/language/struct.zig b/guide/language/struct.zig new file mode 100644 index 0000000..c9aaff8 --- /dev/null +++ b/guide/language/struct.zig @@ -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); +} diff --git a/guide/language/switch.zig b/guide/language/switch.zig new file mode 100644 index 0000000..c848dca --- /dev/null +++ b/guide/language/switch.zig @@ -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. diff --git a/guide/language/unions.zig b/guide/language/unions.zig new file mode 100644 index 0000000..da0e221 --- /dev/null +++ b/guide/language/unions.zig @@ -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 }; diff --git a/guide/language/unreachable.zig b/guide/language/unreachable.zig new file mode 100644 index 0000000..ccccd81 --- /dev/null +++ b/guide/language/unreachable.zig @@ -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'); +} diff --git a/guide/language/vector.zig b/guide/language/vector.zig new file mode 100644 index 0000000..dba6111 --- /dev/null +++ b/guide/language/vector.zig @@ -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. diff --git a/guide/language/while.zig b/guide/language/while.zig new file mode 100644 index 0000000..6670eac --- /dev/null +++ b/guide/language/while.zig @@ -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); +} diff --git a/guide/standard-libaray/01-allocator.zig b/guide/standard-libaray/01-allocator.zig new file mode 100644 index 0000000..c046933 --- /dev/null +++ b/guide/standard-libaray/01-allocator.zig @@ -0,0 +1,122 @@ +const std = @import("std"); +const expect = std.testing.expect; + +// The Zig standard library provides a pattern for allocating memory, +// which allows the programmer to choose precisely how memory allocations are done within the standard library +// - no allocations happen behind your back in the standard library. +// Zig 표준 라이브러리는 메모리 할당 패턴을 하나만 제공하며, +// 이를 통해 프로그래머는 표준 라이브러리 내부에서 메모리 할당을 어떻게 할지 선택할 수 있습니다. +// 사용자가 모르는 사이에 메모리 할당이 발생하지 않습니다. + +// The most basic allocator is `std.heap.page_allocator`. +// Whenever this allocator makes an allocation, it will ask your OS for entire pages of memory; +// an allocation of a single byte will likely reserve multiple kibibytes(`KiB`). +// As asking the OS for memory requires a system call, this is also extremely inefficient for speed. +// 가장 기본적인 할당자는 `std.heap.page_allocator`입니다. 이 할당자는 메모리를 할당할 때 OS에게 전체 페이지의 메모리를 요청합니다. +// 하나의 바이트를 할당하더라도 여러 `키비바이트`(`KiB`)를 예약할 수 있습니다. +// OS에게 메모리를 요청하는 것은 시스템 호출을 필요로 하므로, 이는 매우 비효율적입니다. + +// Here, we allocate 100 bytes as a []u8. +// Notice how defer is used in conjunction with a free - this is a common pattern for memory management in Zig. +// 여기서는 []u8로 100바이트를 할당합니다. defer와 free가 함께 사용되는 것을 주목하세요. +// - 이는 Zig에서 메모리 관리에 일반적인 패턴입니다. + +test "allocation" { + const allocator = std.heap.page_allocator; + + const memory = try allocator.alloc(u8, 100); + defer allocator.free(memory); + + try expect(memory.len == 100); + try expect(@TypeOf(memory) == []u8); +} + +// The `std.heap.FixedBufferAllocator` is an allocator +// that allocates memory into a fixed buffer and does not make any heap allocations. +// This is useful when heap usage is not wanted, for example, when writing a kernel. +// It may also be considered for performance reasons. +// - It will give you the error `OutOfMemory` if it has run out of bytes. +// 이 할당자는 고정된 버퍼에 메모리를 할당하며, 힙 할당을 하지 않습니다. +// 이는 힙 사용을 원하지 않을 때, 예를 들어 커널을 작성할 때 유용합니다. +// - 성능 이유로 고려될 수 있습니다. 메모리가 부족할 경우 `OutOfMemory` 오류를 발생시킵니다. + +test "fixed buffer allocator" { + var buffer: [1000]u8 = undefined; + var fba = std.heap.FixedBufferAllocator.init(&buffer); + const allocator = fba.allocator(); + + const memory = try allocator.alloc(u8, 100); + defer allocator.free(memory); + + try expect(memory.len == 100); + try expect(@TypeOf(memory) == []u8); +} + +// `std.heap.ArenaAllocator` takes in a child allocator and allows you to allocate many times and only free once. +// Here, `.deinit()` is called on the arena, which frees all memory. +// Using allocator.free in this example would be a no-op (i.e. does nothing). +// `std.heap.ArenaAllocator`는 자식 할당자를 포함하고, 여러 번 할당을 할 수 있으며, 한 번에 해제할 수 있습니다. +// 여기서, `.deinit()`는 arena 할당자에서 호출하여 모든 메모리를 해제합니다. +// 이 예제에서는 `allocator.free`를 사용하면 아무 것도 하지 않습니다(`no-op`). +test "arena allocator" { + var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer arena.deinit(); + + const allocator = arena.allocator(); + + _ = try allocator.alloc(u8, 1); + _ = try allocator.alloc(u8, 10); + _ = try allocator.alloc(u8, 100); + // allocator.free(); // no-op, no declaration `allocator.free` at zig-1.15.2` +} + +// alloc and free are used for slices. For single items, consider using create and destroy. +// `alloc`과 `free`는 슬라이스에 사용됩니다. 단일 항목에 대해서는 `create`와 `destroy`를 사용하세요. +test "allocator create/destroy" { + const byte = try std.heap.page_allocator.create(u8); + defer std.heap.page_allocator.destroy(byte); + byte.* = 128; + + try expect(byte.* == 128); +} + +// The Zig standard library also has a general-purpose allocator. +// This is a safe allocator that can prevent double-free, use-after-free and can detect leaks. +// Safety checks and thread safety can be turned off via its configuration struct (left empty below). +// Zig's GPA is designed for safety over performance, but may still be many times faster than page_allocator. + +// Zig 표준 라이브러리에는 일반적인 목적의 할당자도 있습니다. +// 이는 이중 free, free 후 사용, 메모리 누수를 방지할 수 있는 안전한 할당자입니다. +// 안전성 검사와 스레드 안전성은 구성 구조체를 통해 끌 수 있습니다 (left empty below). +// - `std.heap.GeneralPurposeAllocator(.{}){};`, `{}`는 구성 구조체입니다. +// Zig의 GPA는 성능보다 안전성을 우선시하도록 설계되었지만, `page_allocator`보다 여러 배 더 빠를 수 있습니다. +const print = std.debug.print; +test "GPA" { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + const allocator = gpa.allocator(); + defer { + const deinit_status = gpa.deinit(); + //fail test; can't try in defer as defer is executed after we return + if (deinit_status == .leak) expect(false) catch @panic("TEST FAIL"); + } + + const bytes = try allocator.alloc(u8, 100); + // const sample_text = "Hello, GPA!"; + // for (bytes, 0..) |*byte, i| { + // byte.* = @as(u8, sample_text.ptr[i]); + // } + // bytes[sample_text.len] = @as(u8, 0); + // const bytes_slice = bytes[0 .. sample_text.len + 1 :@as(u8, 0)]; + // print("bytes: {s}\n", .{bytes_slice}); + defer allocator.free(bytes); +} + +// For high performance (but very few safety features!), std.heap.c_allocator may be considered. +// This,however, has the disadvantage of requiring linking `Libc`, which can be done with `-lc`. +// 높은 성능(하지만 매우 적은 안전성 기능!)을 위해 `std.heap.c_allocator`를 고려할 수 있습니다. +// 그러나, `Libc`를 링크해야 하는 단점이 있으며, `-lc`로 수행할 수 있습니다. + +// Benjamin Feng's talk `What's a Memory Allocator Anyway?` goes into more detail on this topic, +// and covers the implementation of allocators. +// Benjamin Feng의 토크 `What's a Memory Allocator Anyway?`는 이 주제에 대해 더 자세히 설명하고, +// 할당자의 구현을 다룹니다. diff --git a/guide/standard-libaray/02-array-list.zig b/guide/standard-libaray/02-array-list.zig new file mode 100644 index 0000000..9d764c7 --- /dev/null +++ b/guide/standard-libaray/02-array-list.zig @@ -0,0 +1,37 @@ +const std = @import("std"); +const expect = std.testing.expect; +// The `std.ArrayList` is commonly used throughout Zig, and serves as a buffer that can change in size. +// `std.ArrayList(T)` is similar to C++'s `std::vector` and Rust's `Vec`. +// The `deinit()` method frees all of the `std.ArrayList`'s memory. +// The memory can be read from and written to via its slice field - `.items`. +// `std.ArrayList` 는 Zig 에서 많이 사용되는 버퍼로, 크기를 변경할 수 있는 버퍼를 제공합니다. +// `std.ArrayList(T)` 는 C++의 `std::vector` 와 Rust의 `Vec` 와 유사합니다. +// `deinit()` 메서드는 `std.ArrayList`의 모든 메모리를 해제합니다. +// 메모리는 `.items` 필드를 통해 읽거나 쓸 수 있습니다. + +// Here we will introduce the usage of the testing allocator. +// This is a special allocator that only works in tests and can detect memory leaks. +// In your code, use whatever allocator is appropriate. +// 여기서는 테스트 할당자(testing allocator)의 사용법을 소개합니다. +// 이 할당자는 테스트에서만 동작하며 메모리 누수를 감지할 수 있는 특별한 할당자입니다. +// 실제 코드에서는 상황에 맞는 할당자를 사용하면 됩니다. +const eql = std.mem.eql; +const ArrayList = std.ArrayList; +const allocator = std.testing.allocator; + +test "arraylist" { + var list: ArrayList(u8) = .empty; + defer list.deinit(allocator); + try list.append(allocator, 'H'); + try list.append(allocator, 'e'); + try list.append(allocator, 'l'); + try list.append(allocator, 'l'); + try list.append(allocator, 'o'); + try list.appendSlice(allocator, " World!"); + + try expect(eql(u8, list.items, "Hello World!")); +} + +// Coming from C++? +// Zig's std.ArrayList is very comparable to C++'s std::vector. +// Zig의 std.ArrayList는 C++의 std::vector와 매우 비슷합니다. diff --git a/guide/standard-libaray/03-filesystem.zig b/guide/standard-libaray/03-filesystem.zig new file mode 100644 index 0000000..91480f6 --- /dev/null +++ b/guide/standard-libaray/03-filesystem.zig @@ -0,0 +1,75 @@ +const std = @import("std"); +const expect = std.testing.expect; +const eql = std.mem.eql; + +// Let's create and open a file in our current working directory, write to it, and then read from it. +// Here we have to use `.seekTo` to go back to the start of the file before reading what we have written. +// 현재 작업 디렉터리에서 파일을 생성하고 열어, 그 안에 내용을 기록한 후 다시 읽어 봅시다. +// 여기서 기록한 내용을 읽기 전에 파일의 시작 부분으로 돌아가기 위해 `.seekTo`를 사용해야 합니다. +test "createFile, write, seekTo, read" { + const file = try std.fs.cwd().createFile( + "junk_file.txt", + .{ .read = true }, + ); + defer file.close(); + + try file.writeAll("Hello File!"); + + var buffer: [100]u8 = undefined; + try file.seekTo(0); + const bytes_read = try file.readAll(&buffer); + + try expect(eql(u8, buffer[0..bytes_read], "Hello File!")); +} + +// The functions `std.fs.openFileAbsolute` and similar absolute functions exist, but we will not test them here. +// We can get various information about files by using `.stat()` on them. +// `Stat` also contains fields for `.inode` and `.mode`, but they are not tested here as they rely on the current OS' types. +// When the Enum type is known from context, it can be omitted, so we can compare `stat.kind` to `.file` instead of `Kind.file`. +// 함수 `std.fs.openFileAbsolute` 와 같은 절대 경로 함수가 존재하지만, 여기서는 테스트하지 않습니다. +// 파일에 대한 다양한 정보를 얻기 위해 `.stat()` 함수를 사용할 수 있습니다. +// `Stat` 에는 `.inode` 와 `.mode` 필드가 포함되어 있지만, 현재 OS의 타입에 의존하므로 여기서는 테스트하지 않습니다. +// Enum 타입이 컨텍스트에서 알려져 있을 때, 타입을 생략할 수 있고, `stat.kind` 를 `.file` 과 비교하는 대신 `Kind.file` 과 비교할 수 있습니다. +test "file stat" { + const file = try std.fs.cwd().createFile( + "junk_file2.txt", + .{ .read = true }, + ); + defer file.close(); + const stat = try file.stat(); + try expect(stat.size == 0); + try expect(stat.kind == .file); + try expect(stat.ctime <= std.time.nanoTimestamp()); + try expect(stat.mtime <= std.time.nanoTimestamp()); + try expect(stat.atime <= std.time.nanoTimestamp()); +} + +// We can make directories and iterate over their contents. +// Here we will use an iterator (discussed later). +// This directory (and its contents) will be deleted after this test finishes. +// 디렉터리를 생성하고 그 내용을 반복할 수 있습니다. +// 여기서는 이터레이터(나중에 설명)를 사용합니다. +// 이 디렉터리(와 그 내용)는 이 테스트가 완료된 후 삭제됩니다. +test "make dir" { + try std.fs.cwd().makeDir("test-tmp"); + var iter_dir = try std.fs.cwd().openDir( + "test-tmp", + .{ .iterate = true }, + ); + defer { + iter_dir.close(); + std.fs.cwd().deleteTree("test-tmp") catch unreachable; + } + + _ = try iter_dir.createFile("x", .{}); + _ = try iter_dir.createFile("y", .{}); + _ = try iter_dir.createFile("z", .{}); + + var file_count: usize = 0; + var iter = iter_dir.iterate(); + while (try iter.next()) |entry| { + if (entry.kind == .file) file_count += 1; + } + + try expect(file_count == 3); +}