From 645144a966e060b8a973a8d0e77271664ab08e67 Mon Sep 17 00:00:00 2001
From: "Mr. Paul"
Date: Sat, 11 Sep 2021 18:42:17 +0700
Subject: [PATCH 1/2] langref: Explain Zig Test and Containers
This commit explains Zig's testing capabilities and the meaning of containers.
---
doc/langref.html.in | 954 ++++++++++++++++++++++++++++++++++++++------
1 file changed, 842 insertions(+), 112 deletions(-)
diff --git a/doc/langref.html.in b/doc/langref.html.in
index a5dfa5c9278a..6ef2fc7a3dc3 100644
--- a/doc/langref.html.in
+++ b/doc/langref.html.in
@@ -389,8 +389,8 @@ pub fn main() !void {
with the {#syntax#}!void{#endsyntax#} return type. This return type is known as an {#link|Error Union Type#}.
This syntax tells the Zig compiler that the function will either return an
error or a value. An error union type combines an {#link|Error Set Type#} and any other data type
- (e.g. a {#link|Primitive Type|Primitive Types#} or a user-defined type such as a {#link|struct#}, {#link|enum#}, or {#link|union#}).
- The full form of an error union type is
+ (e.g. a {#link|Primitive Type|Primitive Types#} or a {#link|user-defined type|Containers#} such as a
+ {#link|struct#}, {#link|enum#}, or {#link|union#}). The full form of an error union type is
<error set type>{#syntax#}!{#endsyntax#}<any data type>. In the code
sample, the error set type is not explicitly written on the left side of the {#syntax#}!{#endsyntax#} operator.
When written this way, the error set type is an {#link|inferred error set type|Inferred Error Sets#}. The
@@ -426,7 +426,7 @@ pub fn main() !void {
purposely written to show how to perform {#link|string|String Literals and Unicode Code Point Literals#}
substitution in the {#syntax#}print{#endsyntax#} function. The curly-braces inside of the first argument
are substituted with the compile-time known value inside of the second argument
- (known as an {#link|anonymous struct literal|Anonymous Struct Literals#}). The \n
+ (known as a tuple which is type of an {#link|anonymous struct literal|Anonymous Struct Literals#}). The \n
inside of the double-quotes of the first argument is the {#link|escape sequence|Escape Sequences#} for the
newline character. The {#link|try#} expression evaluates the result of {#syntax#}stdout.print{#endsyntax#}.
If the result is an error, then the {#syntax#}try{#endsyntax#} expression will return from
@@ -458,79 +458,319 @@ pub fn main() void {
{#header_close#}
{#header_open|Zig Test#}
- zig test is a tool that can be used to quickly build and run Zig code
- to make sure behavior meets expectations. {#syntax#}@import("builtin").is_test{#endsyntax#}
- is available for code to detect whether the current build is a test build.
+ In addition to writing Zig code to create programs or libraries, Zig code can also be written to verify that the
+ code used for making programs or libraries are functioning correctly. Test code is written within one or more
+ {#syntax#}test{#endsyntax#} declarations as demonstrated in the introducing_zig_test.zig
+ code sample below.
+ In the code sample below, the introducing_zig_test.zig is shown again with comments
+ to explain parts of the code. The lines starting with {#syntax#}//{#endsyntax#} and {#syntax#}///{#endsyntax#} are
+ {#link|Comments#} that are useful for sharing information with other people.
+
+ {#code_begin|test|introducing_zig_test#}
+// Using @import("std") causes the Standard Library to be represented as a structure (struct).
+// The constant identifier `std` provides access to the structure's data and methods.
+const std = @import("std");
+
+// Tests can only be declared at the top level of a container type (see Containers).
+// Tests can be given an optional name or description written between double-quotes.
+// Test code is written between the required open-curly brace and close-curly brace.
+// Any valid Zig code can be written inside of a test.
+test "expect addOne adds one to 41 correctly" {
+
+ // The Standard Library contains useful functions to help create tests.
+ // `expect` is a function that verifies its argument is true.
+ // It will return an error if its argument is false to indicate a failure.
+ // `try` is used to return an error to the test runner to notify it that the test failed.
+ // Errors and `try` are explained in another section of this documentation.
+ // The code below demonstrates a common way to verify something is true.
+
+ try std.testing.expect(addOne(41) == 42);
+}
+
+/// The function `addOne` adds one to the number given as its argument.
+fn addOne(number: i32) i32 {
+ return number + 1;
+}
+ {#code_end#}
+
+ zig test is a tool that runs test code. It packages the given Zig source file into the default test runner,
+ builds a test runner executable program, and then runs the test runner's executable program. The default test
+ runner is part of the {#link|Zig Standard Library#}. When the test runner is being built, it is provided with
+ all of the tests that the Zig compiler found while {#link|resolving|Root Source File#} the given Zig source file.
+
+
+
+ The shell output shown above displays the line Test [1/1] test "expect addOne adds one to 41 correctly"....
+ This line was printed to standard error (stderr) by the test runner program. (When the test runner program's
+ standard error is redirected to the terminal, these lines are cleared when a test succeeds.)
+ Lines like this indicate which test, out of the total number of tests, is being run. In this case, [1/1]
+ indicates that the first test, out of a total of one test, is being run. The next line, All 1 tests passed.,
+ is also printed to standard error. It indicates the total number of tests that have passed.
+
+
+ The code sample in introducing_zig_test.zig tests the {#link|function|Functions#}
+ {#syntax#}addOne{#endsyntax#} to verify that it correctly adds one to {#syntax#}41{#endsyntax#} to
+ demonstrate the use of a Zig {#syntax#}test{#endsyntax#} declaration. From this test's perspective, the
+ {#syntax#}addOne{#endsyntax#} function is said to be the program/library's "code under test".
+ More comprehensive tests ought to be written to verify that the {#syntax#}addOne{#endsyntax#} function behaves as expected.
+
+ {#header_open|Test Declarations#}
+
+ Test declarations contain the {#link|keyword|Keyword Reference#} {#syntax#}test{#endsyntax#} followed by an
+ optional description written between double-quotation marks followed by a {#link|block|blocks#} containing test code.
+ Test declarations can be written in the same files where code under test is written or in a separate Zig source file.
+ If the test declarations are in the same file as the code under test, then the location of the test declarations can
+ go before or after the code under test. Concretely, test declarations can only be declared at the top-level of a
+ {#link|Container|Containers#}; hence, they are called top-level test declarations.
+
+
+ Tests are similar to {#link|Functions#}, they have a return type and a block of code. The implicit
+ return type of {#syntax#}test{#endsyntax#} is the {#link|Error Union Type#} {#syntax#}anyerror!void{#endsyntax#}.
+ (See {#link|The Global Error Set#} to learn more about {#syntax#}anyerror{#endsyntax#}.) When a
+ Zig source file is not built using the zig test tool, the tests are omitted from the program/library.
+
+
+ zig test will only pass the given source file's top-level test declarations to the test runner.
+ To run {#link|nested container|Nesting Containers#} tests, the nested containers need to
+ be referenced from any of the given file's top-level test declarations. The code sample below uses
+ {#syntax#}std.testing.refAllDecls(@This()){#endsyntax#} function call that will reference all of the
+ {#link|Containers#} that are in the file. This includes the test declarations located in imported,
+ local Zig source files. The code sample also shows an alternative way to reference containers
+ using the {#syntax#}_ = T;{#endsyntax#} syntax. This syntax tells the compiler to ignore the result of
+ the expression on the right side of the assignment operator.
+
+ {#code_begin|test|testdecl_container_top_level#}
const std = @import("std");
-const builtin = @import("builtin");
const expect = std.testing.expect;
-test "builtin.is_test" {
- try expect(builtin.is_test);
+// Locally imported source files will be tested when referenced
+// (see comments further below)
+const local_import = @import("introducing_zig_test.zig");
+
+// The test's name is optional
+// Non-named tests should be used when referencing other tests.
+// Non-named tests are run regardless of the `--test-filter` parameter.
+test {
+ // `zig test` will only resolve a file's top-level test declarations (like this one).
+ // To run nested-containers' tests, either, call `refAllDecls` which will
+ // reference all declarations located in the given argument.
+
+ // `@This()` is a builtin function that returns the innermost container it is called from.
+ // In this example, the innermost container is this file (implicitly a struct).
+ std.testing.refAllDecls(@This());
+
+ // or, reference each container individually from a top-level test declaration.
+ // The `_ = T;` syntax is a no-op reference to the identifier `T`.
+ _ = S;
+ _ = E;
+ _ = U;
+ _ = O;
+
+ // Alternative to `refAllDecls`, other local source files containing tests can
+ // also be referenced like this
+ _ = @import("introducing_zig_test.zig");
}
+
+const S = struct {
+ test "S demo test" {
+ try expect(true);
+ }
+};
+
+const E = enum {
+ V,
+
+ test "E demo test" {
+ try expect(true);
+ }
+};
+
+const U = union {
+ s: US,
+
+ const US = struct {
+ test "U.US demo test" {
+ // This test is a top-level test declaration for the struct.
+ // The struct is nested (declared) inside of a union.
+ try expect(true);
+ }
+ };
+
+ test "U demo test" {
+ try expect(true);
+ }
+};
+
+const O = opaque {
+ test "O demo test" {
+ try expect(true);
+ }
+};
{#code_end#}
+ {#header_close#}
+ {#header_open|Test Failure#}
- Zig has lazy top level declaration analysis, which means that if a function is not called,
- or otherwise used, it is not analyzed. This means that there may be an undiscovered
- compile error in a function because it is never called.
+ When the default test runner runs a test, it checks to see if the test returns an {#link|error|Errors#}.
+ When a test returns an error, the test is considered a failure and the failure will be reported after all
+ test have been run. When a test fails, the default test runner will output the test that failed along with the
+ {#link|Error Return Traces#}.
- Note that, while in {#link|Debug#} and {#link|ReleaseSafe#} modes, {#link|unreachable#} emits a
- call to {#link|@panic#}, in {#link|ReleaseFast#} and {#link|ReleaseSmall#} modes, it is really
- undefined behavior. The implementation of {#syntax#}std.debug.assert{#endsyntax#} is as
- simple as:
+ If a {#syntax#}test{#endsyntax#} returns the error {#syntax#}error.SkipZigTest{#endsyntax#}, the
+ default test runner will consider the test as being skipped. After all of the tests are complete,
+ the number of skipped tests is reported.
- {#code_begin|syntax|assert#}
-pub fn assert(ok: bool) void {
- if (!ok) unreachable;
+ {#code_begin|test#}
+test "this will be skipped" {
+ return error.SkipZigTest;
}
{#code_end#}
- This means that when testing in ReleaseFast or ReleaseSmall mode, {#syntax#}assert{#endsyntax#}
- is not sufficient to check the result of a computation:
+ The default test runner also skips tests containing a {#link|suspend point|Async Functions#} and the
+ test is running using the default, blocking IO mode.
+ (The evented IO mode is enabled using the --test-evented-io command line parameter.)
+ In the code sample below, the test would not be skipped in blocking IO mode if the {#syntax#}nosuspend{#endsyntax#}
+ keyword was used (see {#link|Async and Await#}).
- {#code_begin|syntax|assert_release_fast_mode#}
+ {#code_begin|test|async_skip#}
const std = @import("std");
-const assert = std.debug.assert;
-test "assert in release fast mode" {
- assert(false);
+// This test is skipped because a suspend point exists
+// and the `io_mode`` is set to blocking (the default).
+test "async skip test" {
+ var frame = async func();
+ const result = await frame;
+ try std.testing.expect(result == 1);
+}
+
+fn func() i32 {
+ suspend {
+ resume @frame();
+ }
+ return 1;
}
{#code_end#}
- When compiling this test in {#link|ReleaseFast#} mode, it invokes unchecked
- {#link|Undefined Behavior#}. Since that could do anything, this documentation
- cannot show you the output.
+ Another way to skip tests is to filter them out by using the zig test command line parameter
+ --test-filter [text]. This makes the test build only include tests whose name contains the
+ supplied filter text. Note, non-named tests are run even when using the --test-filter [text]
+ command line parameter.
- Better practice for checking the output when testing is to use {#syntax#}std.testing.expect{#endsyntax#}:
+ When you use code that allocates {#link|Memory#}, use the {#link|Zig Standard Library#} testing allocator
+ {#syntax#}std.testing.allocator{#endsyntax#} to help find memory leaks. When using {#syntax#}std.testing.allocator{#endsyntax#},
+ the default test runner will report any leaks that are found from using this allocator.
- {#code_begin|test_err|test "expect in release fast mode"... FAIL (TestUnexpectedResult)#}
- {#code_release_fast#}
+ {#code_begin|test_err#}
+const std = @import("std");
+
+test "detect leak" {
+ var list = std.ArrayList(u8).init(std.testing.allocator);
+ // missing `defer list.deinit();` (see `defer` in another part of this documentation)
+ try list.append('Z');
+ try list.append('i');
+ try list.append('g');
+
+ try std.testing.expect(list.items.len == 3);
+}
+ {#code_end#}
+ {#see_also|defer|Memory#}
+ {#header_close#}
+ {#header_open|Detecting Test Build#}
+
+ When you need to write code that is aware it is being run by the zig test tool, use the
+ {#link|compile variable|Compile Variables#}, {#syntax#}@import("builtin").is_test{#endsyntax#}.
+
+ The Zig Standard Library's testing {#link|namespace|Container Namespace#} contains useful functions to help
+ you build tests. A few are listed in the following code sample.
+
+ {#code_begin|test|testing_functions#}
+const std = @import("std");
+
+test "expect demo" {
+ try std.testing.expect(true);
+}
+
+test "expectEqual demo" {
+ const expected: i32 = 42;
+ const actual = 42;
+
+ // The first argument to `expectEqual` is the known, expected, result.
+ // The second argument is the result of some expression.
+ // The actual's type is casted to the type of expected.
+ try std.testing.expectEqual(expected, actual);
+}
+
+test "expectError demo" {
+ const expected_error = error.DemoError;
+ const actual_error_union: anyerror!void = error.DemoError;
+
+ // See the Errors section in this documentation to learn about errors.
+ // `expectError` will fail when the actual error is different than
+ // the expected error.
+ try std.testing.expectError(expected_error, actual_error_union);
}
{#code_end#}
-
See the rest of the {#syntax#}std.testing{#endsyntax#} namespace for more available functions.
+
The Zig Standard Library also contains functions to compare {#link|Slices#}, strings, and more. See the rest of the
+ {#syntax#}std.testing{#endsyntax#} namespace in the {#link|Zig Standard Library#} for more available functions.
- zig test has a few command line parameters which affect the compilation. See
- zig --help for a full list. The most interesting one is --test-filter [text].
- This makes the test build only include tests whose name contains the supplied filter text.
- Again, thanks to lazy analysis, this can allow you to narrow a build to only a few functions in
- isolation.
+ zig test has a few command line parameters which affect the compilation. See zig --help for a full list.
{#header_close#}
+ {#header_close#}
{#header_open|Comments#}
{#code_begin|test|comments#}
const expect = @import("std").testing.expect;
@@ -582,10 +822,10 @@ const Timestamp = struct {
in the middle of an expression, or just before a non-doc comment.
User documentation that doesn't belong to whatever
immediately follows it, like package-level documentation, goes
- in top-level doc comments. A top-level doc comment is one that
+ in {#link|top-level|Top-Level#} doc comments. A top-level doc comment is one that
begins with two slashes and an exclamation point:
{#syntax#}//!{#endsyntax#}.
{#code_begin|syntax|tldoc_comments#}
@@ -1062,7 +1302,7 @@ test "init with undefined" {
{#header_open|Container Level Variables#}
- Container level variables have static lifetime and are order-independent and lazily analyzed.
+ {#link|Container level|Top-Level#} variables have static lifetime and are order-independent and lazily analyzed.
The initialization value of container level variables is implicitly
{#link|comptime#}. If a container level variable is {#syntax#}const{#endsyntax#} then its value is
{#syntax#}comptime{#endsyntax#}-known, otherwise it is runtime-known.
@@ -1084,7 +1324,7 @@ const std = @import("std");
const expect = std.testing.expect;
{#code_end#}
- Container level variables may be declared inside a {#link|struct#}, {#link|union#}, or {#link|enum#}:
+ {#link|Container level|Top-Level#} variables may be declared inside a {#link|struct#}, {#link|union#}, or {#link|enum#}:
- It is also possible to have local variables with static lifetime by using containers inside functions.
+ It is also possible to have local variables with static lifetime by using {#link|Containers#} inside functions.
+ In the context of the Zig programming language, a container is a generic term used for
+ describing a language feature that groups related data and functionality.
+
+
Containers are used for creating user-defined types.
+ {#header_open|Types of Containers#}
+
+
A file containing Zig code is a type of container. Implicitly, a Zig source file is a {#link|structure (struct)|struct#}.
+ When a container is given a name, it provides a namespace for the identifiers it contains. Identifiers
+ that are namespaced are unique.
+
+
+ There are three ways to name a container:
+
+
+
The file name (without extension) of a Zig source file is the name of the implicit struct it represents. See {#link|@import#}.
+
When a container is assigned to an identifier, the container's name is the identifier.
+
When a container is in the {#syntax#}return{#endsyntax#} expression of a function, the
+ container's name is the function's name.
+
+
+ Otherwise, the container is considered to be anonymous.
+
+ {#code_begin|exe|container_namespace#}
+const std = @import("std");
+
+// The following three functions return a `type` of a container.
+// The containers are given the name of the function.
+fn StructFun(comptime T: type) type {
+ return struct { f: T };
+}
+
+fn EnumFun(comptime T: type) type {
+ return enum(T) { f };
+}
+
+fn UnionFun(comptime T: type) type {
+ return union { f: T };
+}
+
+pub fn main() void {
+ // The name of an import
+ std.debug.print("As import: {s}\n\n", .{@typeName(@import("hello.zig"))});
+
+ // In this example, containers are declared in three ways:
+ // 1. Declared and stored in a constant local identifier
+ // 2. By using a function to declare the container at compile-time
+ // 3. By declaring the container anonymously
+ //
+ // The name of the containers are printed using the code below.
+
+ const S = struct { f: i32 };
+ const sFun = StructFun(i32);
+ std.debug.print("As identifier: {s}\n", .{@typeName(S)});
+ std.debug.print("As function: {s}\n", .{@typeName(sFun)});
+ std.debug.print("As anonymous: {s}\n\n", .{@typeName(struct { f: i32 })});
+
+ const E = enum { f };
+ const eFun = EnumFun(u8);
+ std.debug.print("As identifier: {s}\n", .{@typeName(E)});
+ std.debug.print("As function: {s}\n", .{@typeName(eFun)});
+ std.debug.print("As anonymous: {s}\n\n", .{@typeName(enum { f })});
+
+ const U = union { f: bool };
+ const uFun = UnionFun(bool);
+ std.debug.print("As identifier: {s}\n", .{@typeName(U)});
+ std.debug.print("As function: {s}\n", .{@typeName(uFun)});
+ std.debug.print("As anonymous: {s}\n\n", .{@typeName(union { f: bool })});
+}
+ {#code_end#}
+ {#header_open|usingnamespace#}
+
+ {#syntax#}usingnamespace{#endsyntax#} is a declaration that mixes all the public
+ declarations of the operand, which must be a {#link|struct#}, {#link|union#}, {#link|enum#},
+ or {#link|opaque#}, into the immediate container's namespace:
+
+ {#syntax#}usingnamespace{#endsyntax#} has an important use case when organizing the public
+ API of a file or package. For example, one might have c.zig with all of the
+ {#link|C imports|Import from C Header File#}:
+
+ The above example demonstrates using {#syntax#}pub{#endsyntax#} to qualify the
+ {#syntax#}usingnamespace{#endsyntax#} additionally makes the imported declarations
+ {#syntax#}pub{#endsyntax#}. This can be used to forward declarations, giving precise control
+ over what declarations a given file exposes.
+
+ The term top-level (also spelled "top level") refers to direct child of a container.
+
+
+ Direct children of a container are called container level members, container level,
+ container members, top-level members, top-level or just members.
+
+
+ Let's revisit the source code for the Hello, world! program again, as shown below. The
+ hello_again_again.zig source file is a container. Its first whole line of code
+ is its {#link|top-level variable declaration|Container Level Variables#}. The code starting at the third line until the end
+ of the file is the hello_again_again container's top-level {#link|function|Functions#} declaration.
+ In summary, the hello_again_again container has two top-level members.
+
+ Now, let's look at nesting containers and point out top-level language constructs. In the following code sample,
+ nested_containers_top_levels.zig is a container. The rest will be explained after the
+ code sample. Focus on the form of the code and ignore the meaning of the code.
+
When Container Documentation Comments are used, they are always the first member of a container.
+ That is, they are put inside of the container before any of the other top-level members.
+
{#link|Top-Level Declarations#}
+
Top-Level Declarations introduce identifiers and functionality.
+
{#link|Container Fields#}
+
Container Fields are properties or variants of a container.
+
+ {#header_open|Top-Level Declarations#}
+
The following are top-level declarations:
+
+
{#link|Test Declarations#}
+
{#link|Compile-Time Expressions#}
+
{#link|Function Declarations and Prototypes|Functions#}
+
{#link|Variable Declarations|Variables#}
+
{#link|usingnamespace#} Declarations
+
+
+ Note, {#link|Compile-Time Expressions#} and {#link|Variable Declarations|Variables#} can be located in places other than
+ the top-level position as well. See their documentation to learn more.
+
+ Container fields are also called variants or tags depending on the
+ {#link|type of container|Types of Containers#} being used. The number of fields a container can have
+ also depends on the type of container being used.
+
+
+ When fields are declared in a container, they must be grouped together. No other type of top-level member
+ is allowed to be placed between container fields. The following code samples demonstrate the three valid
+ ways to arrange top-level declarations and fields.
+
+ Top-level declarations are resolved at compile-time. When a top-level member uses an identifier, the compiler verifies
+ that the identifier has been declared.
+
+
Use of undeclared identifiers is a compile error caused by a top-level member being compile-time resolved.
+ Zig does lazy, top-level declaration analysis, which means that if a top-level member is not called,
+ or otherwise used, it is not analyzed with exception to the {#link|Top-Level Compile-Time Resolution#} rules.
+
+ {#code_begin|test|unused_fn#}
+// Although this function returns the wrong type, no compile error
+// because this function is never called.
+fn unused() i32 {
+ return "wrong return type";
+}
+test "unused function" { }
+ {#code_end#}
+ {#header_close#}
+ {#header_open|Self-Referential Containers#}
+
+ When a container is given a name, the body of a container can contain references to itself
+ as long as it doesn't directly depend on itself. To get around self-dependency, use a
+ {#link|pointer|Pointers#}, or pointer-like, type as shown in the {#syntax#}Node{#endsyntax#} and
+ {#syntax#}JsonType{#endsyntax#} examples below.
+
+ Containers can only self-reference if they are named by a top-level declaration.
+ The following sample demonstrates a compile error when trying to self-reference
+ containers from non-top-level declarations.
+
+ {#code_begin|test_err#}
+const std = @import("std");
+
+fn deepThought() i32 {
+ const Node = struct { // This is not named by a top-level declaration
+ child: ?*Node, // This container cannot self-reference
+ data: i32,
+ };
+ var leaf = Node{
+ .child = null,
+ .data = 42,
+ };
+ const root = Node{
+ .child = &leaf,
+ .data = 0,
+ };
+ return root.child.?.data;
+}
+
+fn ultimateChoice() bool {
+ const Choices = enum { // This is not named by a top-level declaration
+ B,
+ C,
+ Zig,
+ const default = Choices.Zig; // This container cannot self-reference
+ };
+
+ return .Zig == Choices.default;
+}
+
+test "cannot self-reference" {
+ std.testing.expect(deepThought() == 42);
+ std.testing.expect(ultimateChoice());
+}
+ {#code_end#}
+
+ To have the same affect as self-referencing, the code sample above can use the {#syntax#}@This(){#endsyntax#}
+ {#link|built-in function|@This#} as shown in the code sample below.
+
- In {#syntax#}Debug{#endsyntax#} and {#syntax#}ReleaseSafe{#endsyntax#} mode, and when using zig test,
- {#syntax#}unreachable{#endsyntax#} emits a call to {#syntax#}panic{#endsyntax#} with the message reached unreachable code.
+ In {#link|Debug#} mode, {#link|ReleaseSafe#} mode, and when using {#link|Zig Test#} in any mode,
+ {#syntax#}unreachable{#endsyntax#} emits a call to {#link|@panic#} with the message reached unreachable code.
- In {#syntax#}ReleaseFast{#endsyntax#} mode, the optimizer uses the assumption that {#syntax#}unreachable{#endsyntax#} code
- will never be hit to perform optimizations. However, zig test even in {#syntax#}ReleaseFast{#endsyntax#} mode
- still emits {#syntax#}unreachable{#endsyntax#} as calls to {#syntax#}panic{#endsyntax#}.
+ In {#link|ReleaseFast#} mode and {#link|ReleaseSmall#} mode, reaching {#syntax#}unreachable{#endsyntax#} is
+ {#link|undefined behavior|Reaching Unreachable Code#}.
{#header_open|Basics#}
{#code_begin|test|test_unreachable#}
-// unreachable is used to assert that control flow will never happen upon a
+// `unreachable` is used to assert that control flow will never happen upon a
// particular location:
test "basic math" {
const x = 1;
@@ -5917,42 +6688,6 @@ test "@intToPtr for pointer to zero bit type" {
{#header_close#}
- {#header_open|usingnamespace#}
-
- {#syntax#}usingnamespace{#endsyntax#} is a declaration that mixes all the public
- declarations of the operand, which must be a {#link|struct#}, {#link|union#}, {#link|enum#},
- or {#link|opaque#}, into the namespace:
-
- {#syntax#}usingnamespace{#endsyntax#} has an important use case when organizing the public
- API of a file or package. For example, one might have c.zig with all of the
- {#link|C imports|Import from C Header File#}:
-
- The above example demonstrates using {#syntax#}pub{#endsyntax#} to qualify the
- {#syntax#}usingnamespace{#endsyntax#} additionally makes the imported declarations
- {#syntax#}pub{#endsyntax#}. This can be used to forward declarations, giving precise control
- over what declarations a given file exposes.
-
- {#header_close#}
-
{#header_open|comptime#}
@@ -6282,7 +7017,7 @@ test "fibonacci" {
- At container level (outside of any function), all expressions are implicitly
+ At the {#link|container|Containers#} level (outside of any function), all expressions are implicitly
{#syntax#}comptime{#endsyntax#} expressions. This means that we can use functions to
initialize complex static data. For example:
@@ -6360,8 +7095,8 @@ fn List(comptime T: type) type {
the anonymous struct.
- To keep the language small and uniform, all aggregate types in Zig are anonymous. To give a type
- a name, we assign it to a constant:
+ To keep the language small and uniform, all {#link|container types|Types of Containers#} in Zig are anonymous by default.
+ To give a container type a {#link|name|Container Namespace#}, we assign it to a constant:
- This works because all top level declarations are order-independent, and as long as there isn't
- an actual infinite regression, values can refer to themselves, directly or indirectly. In this case,
- {#syntax#}Node{#endsyntax#} refers to itself as a pointer, which is not actually an infinite regression, so
- it works fine.
+ This works because container types can {#link|self-reference|Self-Referential Containers#}.
- When an assembly expression occurs in a container level {#link|comptime#} block, this is
+ When an assembly expression occurs in a {#link|container|Containers#} level {#link|comptime#} block, this is
global assembly.
@@ -9927,8 +10659,7 @@ test "string literal to constant slice" {
dereferencing them becomes unchecked {#link|Undefined Behavior#}.
- {#syntax#}var{#endsyntax#} declarations at the top level or in {#link|struct#} declarations are stored in the global
- data section.
+ {#link|Top-Level#} {#syntax#}var{#endsyntax#} declarations are stored in the global data section.
The location of memory allocated with {#syntax#}allocator.alloc{#endsyntax#} or
@@ -10058,8 +10789,8 @@ const separator = if (builtin.os.tag == builtin.Os.windows) '\\' else '/';
TODO: pub fn main
TODO: pub fn panic
TODO: if linking with libc you can use export fn main
-
TODO: order independent top level declarations
-
TODO: lazy analysis
+
{#link|Top-Level Order Independent Declarations#}
+
{#link|Lazy Top-Level Declaration Analysis#}
TODO: using comptime { _ = @import() }
{#header_close#}
{#header_open|Zig Build System#}
@@ -10339,10 +11070,9 @@ pub fn main() void {
can be called so long as the linker is aware of the compiled function.
- {#syntax#}@compileError{#endsyntax#} is used when top-level definitions (global variables,
- function prototypes, macros) cannot be translated or demoted. Since Zig uses lazy analysis for
- top-level declarations, untranslatable entities will not cause a compile error in your code unless
- you actually use them.
+ {#syntax#}@compileError{#endsyntax#} is used when {#link|top-level|Top-Level#} definitions cannot be
+ translated or demoted. Since Zig uses {#link|Lazy Top-Level Declaration Analysis#}, untranslatable entities will
+ not cause a compile error in your code unless you actually use them.
{#see_also|opaque|extern|@compileError#}
{#header_close#}
@@ -10889,7 +11619,7 @@ coding style.
If {#syntax#}x{#endsyntax#} is a {#syntax#}type{#endsyntax#}
then {#syntax#}x{#endsyntax#} should be {#syntax#}TitleCase{#endsyntax#}, unless it
is a {#syntax#}struct{#endsyntax#} with 0 fields and is never meant to be instantiated,
- in which case it is considered to be a "namespace" and uses {#syntax#}snake_case{#endsyntax#}.
+ in which case it is considered to be a {#link|namespace|Container Namespace#} and uses {#syntax#}snake_case{#endsyntax#}.
If {#syntax#}x{#endsyntax#} is callable, and {#syntax#}x{#endsyntax#}'s return type is
@@ -10911,7 +11641,7 @@ coding style.
File names fall into two categories: types and namespaces. If the file
- (implicitly a struct) has top level fields, it should be named like any
+ (implicitly a struct) has {#link|Top-Level#} fields, it should be named like any
other struct with fields using TitleCase. Otherwise,
it should use snake_case. Directory names should be
snake_case.
@@ -11399,7 +12129,7 @@ fn readU32Be() u32 {}
{#syntax#}pub{#endsyntax#}
- The {#syntax#}pub{#endsyntax#} in front of a top level declaration makes the declaration available
+ The {#syntax#}pub{#endsyntax#} in front of a {#link|top-level declaration|Top-Level#} makes the declaration available
to reference from a different file than the one it is declared in.
See also {#link|import#}
@@ -11480,7 +12210,7 @@ fn readU32Be() u32 {}
{#syntax#}test{#endsyntax#}
- The {#syntax#}test{#endsyntax#} keyword can be used to denote a top-level block of code
+ The {#syntax#}test{#endsyntax#} keyword can be used to denote a {#link|Top-Level#} declaration of code
used to make sure behavior meets expectations.
See also {#link|Zig Test#}
@@ -11563,7 +12293,7 @@ fn readU32Be() u32 {}
{#syntax#}usingnamespace{#endsyntax#}
- {#syntax#}usingnamespace{#endsyntax#} is a top-level declaration that imports all the public declarations of the operand,
+ {#syntax#}usingnamespace{#endsyntax#} is a {#link|Top-Level#} declaration that imports all the public declarations of the operand,
which must be a struct, union, or enum, into the current scope.
- In addition to writing Zig code to create programs or libraries, Zig code can also be written to verify that the
- code used for making programs or libraries are functioning correctly. Test code is written within one or more
- {#syntax#}test{#endsyntax#} declarations as demonstrated in the introducing_zig_test.zig
- code sample below.
-
- In the code sample below, the introducing_zig_test.zig is shown again with comments
- to explain parts of the code. The lines starting with {#syntax#}//{#endsyntax#} and {#syntax#}///{#endsyntax#} are
- {#link|Comments#} that are useful for sharing information with other people.
-
- {#code_begin|test|introducing_zig_test#}
-// Using @import("std") causes the Standard Library to be represented as a structure (struct).
-// The constant identifier `std` provides access to the structure's data and methods.
-const std = @import("std");
-
-// Tests can only be declared at the top level of a container type (see Containers).
-// Tests can be given an optional name or description written between double-quotes.
-// Test code is written between the required open-curly brace and close-curly brace.
-// Any valid Zig code can be written inside of a test.
-test "expect addOne adds one to 41 correctly" {
+ {#header_open|Comments#}
+ {#code_begin|exe|comments#}
+const print = @import("std").debug.print;
- // The Standard Library contains useful functions to help create tests.
- // `expect` is a function that verifies its argument is true.
- // It will return an error if its argument is false to indicate a failure.
- // `try` is used to return an error to the test runner to notify it that the test failed.
- // Errors and `try` are explained in another section of this documentation.
- // The code below demonstrates a common way to verify something is true.
+pub fn main() void {
+ // Comments in Zig start with "//" and end at the next LF byte (end of line).
+ // The line below is a comment and won't be executed.
- try std.testing.expect(addOne(41) == 42);
-}
+ //print("Hello?", .{});
-/// The function `addOne` adds one to the number given as its argument.
-fn addOne(number: i32) i32 {
- return number + 1;
+ print("Hello, world!\n", .{}); // another comment
}
{#code_end#}
- zig test is a tool that runs test code. It packages the given Zig source file into the default test runner,
- builds a test runner executable program, and then runs the test runner's executable program. The default test
- runner is part of the {#link|Zig Standard Library#}. When the test runner is being built, it is provided with
- all of the tests that the Zig compiler found while {#link|resolving|Root Source File#} the given Zig source file.
-
-
-
- The shell output shown above displays the line Test [1/1] test "expect addOne adds one to 41 correctly"....
- This line was printed to standard error (stderr) by the test runner program. (When the test runner program's
- standard error is redirected to the terminal, these lines are cleared when a test succeeds.)
- Lines like this indicate which test, out of the total number of tests, is being run. In this case, [1/1]
- indicates that the first test, out of a total of one test, is being run. The next line, All 1 tests passed.,
- is also printed to standard error. It indicates the total number of tests that have passed.
-
-
- The code sample in introducing_zig_test.zig tests the {#link|function|Functions#}
- {#syntax#}addOne{#endsyntax#} to verify that it correctly adds one to {#syntax#}41{#endsyntax#} to
- demonstrate the use of a Zig {#syntax#}test{#endsyntax#} declaration. From this test's perspective, the
- {#syntax#}addOne{#endsyntax#} function is said to be the program/library's "code under test".
- More comprehensive tests ought to be written to verify that the {#syntax#}addOne{#endsyntax#} function behaves as expected.
-
- {#header_open|Test Declarations#}
-
- Test declarations contain the {#link|keyword|Keyword Reference#} {#syntax#}test{#endsyntax#} followed by an
- optional description written between double-quotation marks followed by a {#link|block|blocks#} containing test code.
- Test declarations can be written in the same files where code under test is written or in a separate Zig source file.
- If the test declarations are in the same file as the code under test, then the location of the test declarations can
- go before or after the code under test. Concretely, test declarations can only be declared at the top-level of a
- {#link|Container|Containers#}; hence, they are called top-level test declarations.
-
-
- Tests are similar to {#link|Functions#}, they have a return type and a block of code. The implicit
- return type of {#syntax#}test{#endsyntax#} is the {#link|Error Union Type#} {#syntax#}anyerror!void{#endsyntax#}.
- (See {#link|The Global Error Set#} to learn more about {#syntax#}anyerror{#endsyntax#}.) When a
- Zig source file is not built using the zig test tool, the tests are omitted from the program/library.
+ There are no multiline comments in Zig (e.g. like /* */
+ comments in C). This helps allow Zig to have the property that each line
+ of code can be tokenized out of context.
+ {#header_open|Doc comments#}
- zig test will only pass the given source file's top-level test declarations to the test runner.
- To run {#link|nested container|Nesting Containers#} tests, the nested containers need to
- be referenced from any of the given file's top-level test declarations. The code sample below uses
- {#syntax#}std.testing.refAllDecls(@This()){#endsyntax#} function call that will reference all of the
- {#link|Containers#} that are in the file. This includes the test declarations located in imported,
- local Zig source files. The code sample also shows an alternative way to reference containers
- using the {#syntax#}_ = T;{#endsyntax#} syntax. This syntax tells the compiler to ignore the result of
- the expression on the right side of the assignment operator.
+ A doc comment is one that begins with exactly three slashes (i.e.
+ {#syntax#}///{#endsyntax#} but not {#syntax#}////{#endsyntax#});
+ multiple doc comments in a row are merged together to form a multiline
+ doc comment. The doc comment documents whatever immediately follows it.
- {#code_begin|test|testdecl_container_top_level#}
-const std = @import("std");
-const expect = std.testing.expect;
-
-// Locally imported source files will be tested when referenced
-// (see comments further below)
-const local_import = @import("introducing_zig_test.zig");
-
-// The test's name is optional
-// Non-named tests should be used when referencing other tests.
-// Non-named tests are run regardless of the `--test-filter` parameter.
-test {
- // `zig test` will only resolve a file's top-level test declarations (like this one).
- // To run nested-containers' tests, either, call `refAllDecls` which will
- // reference all declarations located in the given argument.
-
- // `@This()` is a builtin function that returns the innermost container it is called from.
- // In this example, the innermost container is this file (implicitly a struct).
- std.testing.refAllDecls(@This());
-
- // or, reference each container individually from a top-level test declaration.
- // The `_ = T;` syntax is a no-op reference to the identifier `T`.
- _ = S;
- _ = E;
- _ = U;
- _ = O;
-
- // Alternative to `refAllDecls`, other local source files containing tests can
- // also be referenced like this
- _ = @import("introducing_zig_test.zig");
-}
+ {#code_begin|syntax|doc_comments#}
+/// A structure for storing a timestamp, with nanosecond precision (this is a
+/// multiline doc comment).
+const Timestamp = struct {
+ /// The number of seconds since the epoch (this is also a doc comment).
+ seconds: i64, // signed so we can represent pre-1970 (not a doc comment)
+ /// The number of nanoseconds past the second (doc comment again).
+ nanos: u32,
-const S = struct {
- test "S demo test" {
- try expect(true);
+ /// Returns a `Timestamp` struct representing the Unix epoch; that is, the
+ /// moment of 1970 Jan 1 00:00:00 UTC (this is a doc comment too).
+ pub fn unixEpoch() Timestamp {
+ return Timestamp{
+ .seconds = 0,
+ .nanos = 0,
+ };
}
};
+ {#code_end#}
+
+ Doc comments are only allowed in certain places; eventually, it will
+ become a compile error to have a doc comment in an unexpected place, such as
+ in the middle of an expression, or just before a non-doc comment.
+
User documentation that doesn't belong to whatever
+ immediately follows it, like package-level documentation, goes
+ in {#link|top-level|Top-Level#} doc comments. A container doc comment is one that
+ begins with two slashes and an exclamation point:
+ {#syntax#}//!{#endsyntax#}.
+ {#code_begin|syntax|container_doc_comments#}
+//! This module provides functions for retrieving the current date and
+//! time with varying degrees of precision and accuracy. It does not
+//! depend on libc, but will use functions from it if available.
+ {#code_end#}
+ {#header_close#}
+ {#header_close#}
+ {#header_open|Values#}
+ {#code_begin|exe|values#}
+// Top-level declarations are order-independent:
+const print = std.debug.print;
+const std = @import("std");
+const os = std.os;
+const assert = std.debug.assert;
-const E = enum {
- V,
-
- test "E demo test" {
- try expect(true);
- }
-};
+pub fn main() void {
+ // integers
+ const one_plus_one: i32 = 1 + 1;
+ print("1 + 1 = {}\n", .{one_plus_one});
-const U = union {
- s: US,
+ // floats
+ const seven_div_three: f32 = 7.0 / 3.0;
+ print("7.0 / 3.0 = {}\n", .{seven_div_three});
- const US = struct {
- test "U.US demo test" {
- // This test is a top-level test declaration for the struct.
- // The struct is nested (declared) inside of a union.
- try expect(true);
- }
- };
+ // boolean
+ print("{}\n{}\n{}\n", .{
+ true and false,
+ true or false,
+ !true,
+ });
- test "U demo test" {
- try expect(true);
- }
-};
+ // optional
+ var optional_value: ?[]const u8 = null;
+ assert(optional_value == null);
-const O = opaque {
- test "O demo test" {
- try expect(true);
- }
-};
- {#code_end#}
- {#header_close#}
- {#header_open|Test Failure#}
-
- When the default test runner runs a test, it checks to see if the test returns an {#link|error|Errors#}.
- When a test returns an error, the test is considered a failure and the failure will be reported after all
- test have been run. When a test fails, the default test runner will output the test that failed along with the
- {#link|Error Return Traces#}.
-
- If a {#syntax#}test{#endsyntax#} returns the error {#syntax#}error.SkipZigTest{#endsyntax#}, the
- default test runner will consider the test as being skipped. After all of the tests are complete,
- the number of skipped tests is reported.
-
- {#code_begin|test#}
-test "this will be skipped" {
- return error.SkipZigTest;
-}
- {#code_end#}
-
- The default test runner also skips tests containing a {#link|suspend point|Async Functions#} and the
- test is running using the default, blocking IO mode.
- (The evented IO mode is enabled using the --test-evented-io command line parameter.)
- In the code sample below, the test would not be skipped in blocking IO mode if the {#syntax#}nosuspend{#endsyntax#}
- keyword was used (see {#link|Async and Await#}).
-
- {#code_begin|test|async_skip#}
-const std = @import("std");
+ print("\noptional 2\ntype: {s}\nvalue: {s}\n", .{
+ @typeName(@TypeOf(optional_value)),
+ optional_value,
+ });
-// This test is skipped because a suspend point exists
-// and the `io_mode`` is set to blocking (the default).
-test "async skip test" {
- var frame = async func();
- const result = await frame;
- try std.testing.expect(result == 1);
-}
+ // error union
+ var number_or_error: anyerror!i32 = error.ArgNotFound;
-fn func() i32 {
- suspend {
- resume @frame();
- }
- return 1;
-}
- {#code_end#}
-
- Another way to skip tests is to filter them out by using the zig test command line parameter
- --test-filter [text]. This makes the test build only include tests whose name contains the
- supplied filter text. Note, non-named tests are run even when using the --test-filter [text]
- command line parameter.
-
- When you use code that allocates {#link|Memory#}, use the {#link|Zig Standard Library#} testing allocator
- {#syntax#}std.testing.allocator{#endsyntax#} to help find memory leaks. When using {#syntax#}std.testing.allocator{#endsyntax#},
- the default test runner will report any leaks that are found from using this allocator.
-
- {#code_begin|test_err#}
-const std = @import("std");
+ print("\nerror union 1\ntype: {s}\nvalue: {}\n", .{
+ @typeName(@TypeOf(number_or_error)),
+ number_or_error,
+ });
-test "detect leak" {
- var list = std.ArrayList(u8).init(std.testing.allocator);
- // missing `defer list.deinit();` (see `defer` in another part of this documentation)
- try list.append('Z');
- try list.append('i');
- try list.append('g');
+ number_or_error = 1234;
- try std.testing.expect(list.items.len == 3);
-}
- {#code_end#}
- {#see_also|defer|Memory#}
- {#header_close#}
- {#header_open|Detecting Test Build#}
-
- When you need to write code that is aware it is being run by the zig test tool, use the
- {#link|compile variable|Compile Variables#}, {#syntax#}@import("builtin").is_test{#endsyntax#}.
-
- The Zig Standard Library's testing {#link|namespace|Container Namespace#} contains useful functions to help
- you build tests. A few are listed in the following code sample.
-
- {#code_begin|test|testing_functions#}
-const std = @import("std");
-
-test "expect demo" {
- try std.testing.expect(true);
-}
-
-test "expectEqual demo" {
- const expected: i32 = 42;
- const actual = 42;
-
- // The first argument to `expectEqual` is the known, expected, result.
- // The second argument is the result of some expression.
- // The actual's type is casted to the type of expected.
- try std.testing.expectEqual(expected, actual);
-}
-
-test "expectError demo" {
- const expected_error = error.DemoError;
- const actual_error_union: anyerror!void = error.DemoError;
-
- // See the Errors section in this documentation to learn about errors.
- // `expectError` will fail when the actual error is different than
- // the expected error.
- try std.testing.expectError(expected_error, actual_error_union);
-}
- {#code_end#}
-
The Zig Standard Library also contains functions to compare {#link|Slices#}, strings, and more. See the rest of the
- {#syntax#}std.testing{#endsyntax#} namespace in the {#link|Zig Standard Library#} for more available functions.
- zig test has a few command line parameters which affect the compilation. See zig --help for a full list.
-
- {#header_close#}
- {#header_close#}
- {#header_open|Comments#}
- {#code_begin|test|comments#}
-const expect = @import("std").testing.expect;
-
-test "comments" {
- // Comments in Zig start with "//" and end at the next LF byte (end of line).
- // The below line is a comment, and won't be executed.
-
- //expect(false);
-
- const x = true; // another comment
- try expect(x);
-}
- {#code_end#}
-
- There are no multiline comments in Zig (e.g. like /* */
- comments in C). This helps allow Zig to have the property that each line
- of code can be tokenized out of context.
-
- {#header_open|Doc comments#}
-
- A doc comment is one that begins with exactly three slashes (i.e.
- {#syntax#}///{#endsyntax#} but not {#syntax#}////{#endsyntax#});
- multiple doc comments in a row are merged together to form a multiline
- doc comment. The doc comment documents whatever immediately follows it.
-
- {#code_begin|syntax|doc_comments#}
-/// A structure for storing a timestamp, with nanosecond precision (this is a
-/// multiline doc comment).
-const Timestamp = struct {
- /// The number of seconds since the epoch (this is also a doc comment).
- seconds: i64, // signed so we can represent pre-1970 (not a doc comment)
- /// The number of nanoseconds past the second (doc comment again).
- nanos: u32,
-
- /// Returns a `Timestamp` struct representing the Unix epoch; that is, the
- /// moment of 1970 Jan 1 00:00:00 UTC (this is a doc comment too).
- pub fn unixEpoch() Timestamp {
- return Timestamp{
- .seconds = 0,
- .nanos = 0,
- };
- }
-};
- {#code_end#}
-
- Doc comments are only allowed in certain places; eventually, it will
- become a compile error to have a doc comment in an unexpected place, such as
- in the middle of an expression, or just before a non-doc comment.
-
User documentation that doesn't belong to whatever
- immediately follows it, like package-level documentation, goes
- in {#link|top-level|Top-Level#} doc comments. A top-level doc comment is one that
- begins with two slashes and an exclamation point:
- {#syntax#}//!{#endsyntax#}.
- {#code_begin|syntax|tldoc_comments#}
-//! This module provides functions for retrieving the current date and
-//! time with varying degrees of precision and accuracy. It does not
-//! depend on libc, but will use functions from it if available.
- {#code_end#}
- {#header_close#}
- {#header_close#}
- {#header_open|Values#}
- {#code_begin|exe|values#}
-// Top-level declarations are order-independent:
-const print = std.debug.print;
-const std = @import("std");
-const os = std.os;
-const assert = std.debug.assert;
-
-pub fn main() void {
- // integers
- const one_plus_one: i32 = 1 + 1;
- print("1 + 1 = {}\n", .{one_plus_one});
-
- // floats
- const seven_div_three: f32 = 7.0 / 3.0;
- print("7.0 / 3.0 = {}\n", .{seven_div_three});
-
- // boolean
- print("{}\n{}\n{}\n", .{
- true and false,
- true or false,
- !true,
- });
-
- // optional
- var optional_value: ?[]const u8 = null;
- assert(optional_value == null);
-
- print("\noptional 1\ntype: {s}\nvalue: {s}\n", .{
- @typeName(@TypeOf(optional_value)),
- optional_value,
- });
-
- optional_value = "hi";
- assert(optional_value != null);
-
- print("\noptional 2\ntype: {s}\nvalue: {s}\n", .{
- @typeName(@TypeOf(optional_value)),
- optional_value,
- });
-
- // error union
- var number_or_error: anyerror!i32 = error.ArgNotFound;
-
- print("\nerror union 1\ntype: {s}\nvalue: {}\n", .{
- @typeName(@TypeOf(number_or_error)),
- number_or_error,
- });
-
- number_or_error = 1234;
-
- print("\nerror union 2\ntype: {s}\nvalue: {}\n", .{
- @typeName(@TypeOf(number_or_error)),
- number_or_error,
- });
+ print("\nerror union 2\ntype: {s}\nvalue: {}\n", .{
+ @typeName(@TypeOf(number_or_error)),
+ number_or_error,
+ });
}
{#code_end#}
{#header_open|Primitive Types#}
@@ -1136,24 +820,25 @@ pub fn main() void {
in recent versions of the Unicode specification (as of Unicode 13.0).
In Zig, a Unicode code point literal corresponds to the Unicode definition of a code point.
- {#code_begin|test|string_literals_test#}
-const expect = @import("std").testing.expect;
-const mem = @import("std").mem;
+ {#code_begin|exe|string_literals#}
+const print = @import("std").debug.print;
+const mem = @import("std").mem; // will be used to compare bytes
-test "string literals" {
+pub fn main() void {
const bytes = "hello";
- try expect(@TypeOf(bytes) == *const [5:0]u8);
- try expect(bytes.len == 5);
- try expect(bytes[1] == 'e');
- try expect(bytes[5] == 0);
- try expect('e' == '\x65');
- try expect('\u{1f4a9}' == 128169);
- try expect('💯' == 128175);
- try expect(mem.eql(u8, "hello", "h\x65llo"));
- try expect("\xff"[0] == 0xff); // non-UTF-8 strings are possible with \xNN notation.
-}
- {#code_end#}
- {#see_also|Arrays|Zig Test|Source Encoding#}
+ print("{s}\n", .{@typeName(@TypeOf(bytes))}); // *const [5:0]u8
+ print("{d}\n", .{bytes.len}); // 5
+ print("{c}\n", .{bytes[1]}); // 'e'
+ print("{d}\n", .{bytes[5]}); // 0
+ print("{}\n", .{'e' == '\x65'}); // true
+ print("{d}\n", .{'\u{1f4a9}'}); // 128169
+ print("{d}\n", .{'💯'}); // 128175
+ print("{}\n", .{mem.eql(u8, "hello", "h\x65llo")}); // true
+ print("0x{x}\n", .{"\xff"[0]}); // non-UTF-8 strings are possible with \xNN notation.
+ print("{u}\n", .{'⚡'});
+}
+ {#code_end#}
+ {#see_also|Arrays|Source Encoding#}
{#header_open|Escape Sequences#}
@@ -1287,211 +972,51 @@ test "init with undefined" {
{#header_close#}
{#header_close#}
{#header_close#}
+ {#header_open|Integers#}
+ {#header_open|Integer Literals#}
+ {#code_begin|syntax|integer_literals#}
+const decimal_int = 98222;
+const hex_int = 0xff;
+const another_hex_int = 0xFF;
+const octal_int = 0o755;
+const binary_int = 0b11110000;
- {#header_open|Variables#}
+// underscores may be placed between two digits as a visual separator
+const one_billion = 1_000_000_000;
+const binary_mask = 0b1_1111_1111;
+const permissions = 0o7_5_5;
+const big_address = 0xFF80_0000_0000_0000;
+ {#code_end#}
+ {#header_close#}
+ {#header_open|Runtime Integer Values#}
- A variable is a unit of {#link|Memory#} storage.
+ Integer literals have no size limitation, and if any undefined behavior occurs,
+ the compiler catches it.
- Variables are never allowed to shadow identifiers from an outer scope.
+ However, once an integer value is no longer known at compile-time, it must have a
+ known size, and is vulnerable to undefined behavior.
- It is generally preferable to use {#syntax#}const{#endsyntax#} rather than
- {#syntax#}var{#endsyntax#} when declaring a variable. This causes less work for both
- humans and computers to do when reading code, and creates more optimization opportunities.
+ In this function, values {#syntax#}a{#endsyntax#} and {#syntax#}b{#endsyntax#} are known only at runtime,
+ and thus this division operation is vulnerable to both {#link|Integer Overflow#} and
+ {#link|Division by Zero#}.
- {#header_open|Container Level Variables#}
- {#link|Container level|Top-Level#} variables have static lifetime and are order-independent and lazily analyzed.
- The initialization value of container level variables is implicitly
- {#link|comptime#}. If a container level variable is {#syntax#}const{#endsyntax#} then its value is
- {#syntax#}comptime{#endsyntax#}-known, otherwise it is runtime-known.
+ Operators such as {#syntax#}+{#endsyntax#} and {#syntax#}-{#endsyntax#} cause undefined behavior on
+ integer overflow. Also available are operations such as {#syntax#}+%{#endsyntax#} and
+ {#syntax#}-%{#endsyntax#} which are defined to have wrapping arithmetic on all targets.
- The {#syntax#}extern{#endsyntax#} keyword or {#link|@extern#} builtin function can be used to link against a variable that is exported
- from another object. The {#syntax#}export{#endsyntax#} keyword or {#link|@export#} builtin function
- can be used to make a variable available to other objects at link time. In both cases,
- the type of the variable must be C ABI compatible.
-
- {#see_also|Exporting a C Library#}
- {#header_close#}
-
- {#header_open|Thread Local Variables#}
-
A variable may be specified to be a thread-local variable using the
- {#syntax#}threadlocal{#endsyntax#} keyword:
- Local variables occur inside {#link|Functions#}, {#link|comptime#} blocks, and {#link|@cImport#} blocks.
-
-
- When a local variable is {#syntax#}const{#endsyntax#}, it means that after initialization, the variable's
- value will not change. If the initialization value of a {#syntax#}const{#endsyntax#} variable is
- {#link|comptime#}-known, then the variable is also {#syntax#}comptime{#endsyntax#}-known.
-
-
- A local variable may be qualified with the {#syntax#}comptime{#endsyntax#} keyword. This causes
- the variable's value to be {#syntax#}comptime{#endsyntax#}-known, and all loads and stores of the
- variable to happen during semantic analysis of the program, rather than at runtime.
- All variables declared in a {#syntax#}comptime{#endsyntax#} expression are implicitly
- {#syntax#}comptime{#endsyntax#} variables.
-
- {#code_begin|test|comptime_vars#}
-const std = @import("std");
-const expect = std.testing.expect;
-
-test "comptime vars" {
- var x: i32 = 1;
- comptime var y: i32 = 1;
-
- x += 1;
- y += 1;
-
- try expect(x == 2);
- try expect(y == 2);
-
- if (y != 2) {
- // This compile error never triggers because y is a comptime variable,
- // and so `y != 2` is a comptime value, and this if is statically evaluated.
- @compileError("wrong y value");
- }
-}
- {#code_end#}
- {#header_close#}
- {#header_close#}
-
- {#header_open|Integers#}
- {#header_open|Integer Literals#}
- {#code_begin|syntax|integer_literals#}
-const decimal_int = 98222;
-const hex_int = 0xff;
-const another_hex_int = 0xFF;
-const octal_int = 0o755;
-const binary_int = 0b11110000;
-
-// underscores may be placed between two digits as a visual separator
-const one_billion = 1_000_000_000;
-const binary_mask = 0b1_1111_1111;
-const permissions = 0o7_5_5;
-const big_address = 0xFF80_0000_0000_0000;
- {#code_end#}
- {#header_close#}
- {#header_open|Runtime Integer Values#}
-
- Integer literals have no size limitation, and if any undefined behavior occurs,
- the compiler catches it.
-
-
- However, once an integer value is no longer known at compile-time, it must have a
- known size, and is vulnerable to undefined behavior.
-
- In this function, values {#syntax#}a{#endsyntax#} and {#syntax#}b{#endsyntax#} are known only at runtime,
- and thus this division operation is vulnerable to both {#link|Integer Overflow#} and
- {#link|Division by Zero#}.
-
-
- Operators such as {#syntax#}+{#endsyntax#} and {#syntax#}-{#endsyntax#} cause undefined behavior on
- integer overflow. Also available are operations such as {#syntax#}+%{#endsyntax#} and
- {#syntax#}-%{#endsyntax#} which are defined to have wrapping arithmetic on all targets.
-
-
- Zig supports arbitrary bit-width integers, referenced by using
- an identifier of i or u followed by digits. For example, the identifier
- {#syntax#}i7{#endsyntax#} refers to a signed 7-bit integer. The maximum allowed bit-width of an
- integer type is {#syntax#}65535{#endsyntax#}.
+ Zig supports arbitrary bit-width integers, referenced by using
+ an identifier of i or u followed by digits. For example, the identifier
+ {#syntax#}i7{#endsyntax#} refers to a signed 7-bit integer. The maximum allowed bit-width of an
+ integer type is {#syntax#}65535{#endsyntax#}.
+ In the context of the Zig programming language, a container is a generic term used for
+ describing a language feature that groups related data and functionality.
+
+
Containers are used for creating user-defined types.
+ {#header_open|Types of Containers#}
+
+
A Zig source file is a type of container. Implicitly, a it is a {#link|structure (struct)|struct#}.
- If there is no type in the result location then an anonymous list literal actually
- turns into a {#link|struct#} with numbered field names:
+ {#syntax#}usingnamespace{#endsyntax#} is a declaration that mixes all the public
+ declarations of the operand, which must be a {#link|struct#}, {#link|union#}, {#link|enum#},
+ or {#link|opaque#}, into the immediate container's namespace:
- Multidimensional arrays can be created by nesting arrays:
+ {#syntax#}usingnamespace{#endsyntax#} has an important use case when organizing the public
+ API of a file or package. For example, one might have c.zig with all of the
+ {#link|C imports|Import from C Header File#}:
- The syntax {#syntax#}[N:x]T{#endsyntax#} describes an array which has a sentinel element of value {#syntax#}x{#endsyntax#} at the
- index corresponding to {#syntax#}len{#endsyntax#}.
+ The above example demonstrates using {#syntax#}pub{#endsyntax#} to qualify the
+ {#syntax#}usingnamespace{#endsyntax#} additionally makes the imported declarations
+ {#syntax#}pub{#endsyntax#}. This can be used to forward declarations, giving precise control
+ over what declarations a given file exposes.
- A vector is a group of booleans, {#link|Integers#}, {#link|Floats#}, or {#link|Pointers#} which are operated on
- in parallel using a single instruction ({#link|SIMD#}). Vector types are created with the builtin function {#link|@Type#},
- or using the shorthand as {#syntax#}std.meta.Vector{#endsyntax#}.
+ The term top-level (also spelled "top level") refers to direct child of a container.
- TODO talk about C ABI interop
+ Direct children of a container are called container level members, container level,
+ container members, top-level members, top-level or just members.
- {#header_open|SIMD#}
- TODO Zig's SIMD abilities are just beginning to be fleshed out. Here are some talking points to update the
- docs with:
- * What kind of operations can you do? All the operations on integers and floats? What about mixing scalar and vector?
- * How to convert to/from vectors/arrays
- * How to access individual elements from vectors, how to loop over the elements
- * "shuffle"
- * Advice on writing high perf software, how to abstract the best way
+ Let's revisit the source code for the Hello, world! program again, as shown below. The
+ hello_again_again.zig source file is a container. Its first whole line of code
+ is its {#link|top-level variable declaration|Container Level Variables#}. The code starting at the third line until the end
+ of the file is the hello_again_again container's top-level {#link|function|Functions#} declaration.
+ In summary, the hello_again_again container has two top-level members.
- Zig has two kinds of pointers: single-item and many-item.
+ Now, let's look at nesting containers and point out top-level language constructs. In the following code sample,
+ nested_containers_top_levels.zig is a container. The rest will be explained after the
+ code sample. Focus on the form of the code and ignore the meaning of the code.
-
-
{#syntax#}*T{#endsyntax#} - single-item pointer to exactly one item.
-
{#syntax#}T{#endsyntax#} must have a known size, which means that it cannot be
- {#syntax#}c_void{#endsyntax#} or any other {#link|opaque type|opaque#}.
-
-
-
-
These types are closely related to {#link|Arrays#} and {#link|Slices#}:
-
-
{#syntax#}*[N]T{#endsyntax#} - pointer to N items, same as single-item pointer to an array.
-
-
Supports index syntax: {#syntax#}array_ptr[i]{#endsyntax#}
When Container Documentation Comments are used, they are always the first member of a container.
+ That is, they are put inside of the container before any of the other top-level members.
+
{#link|Top-Level Declarations#}
+
Top-Level Declarations introduce identifiers and functionality.
+
{#link|Container Fields#}
+
Container Fields are properties or variants of a container.
+
+ {#header_open|Top-Level Declarations#}
+
The following are top-level declarations:
+
+
{#link|Test Declarations#}
+
{#link|Compile-Time Expressions#}
+
{#link|Function Declarations and Prototypes|Functions#}
+
{#link|Variable Declarations|Variables#}
+
{#link|usingnamespace#} Declarations
+
+
+ Note, {#link|Compile-Time Expressions#} and {#link|Variable Declarations|Variables#} can be located in places other than
+ the top-level position as well.
+
+ Container fields are also called variants or tags depending on the
+ {#link|type of container|Types of Containers#} being used. The number of fields a container can have
+ also depends on the type of container.
+
+
+ When fields are declared in a container, they must be grouped together. No other type of top-level member
+ is allowed to be placed between container fields. Here are the three valid ways to arrange top-level
+ declarations and fields:
+
+ Top-level declarations are resolved at compile-time. When a top-level member uses an identifier, the compiler verifies
+ that the identifier has been declared.
+
+
Use of undeclared identifiers is a compile error caused by a top-level member being compile-time resolved.
+ Zig does lazy, top-level declaration analysis, which means that if a top-level member is not called,
+ or otherwise used, it is not analyzed with exception to the {#link|Top-Level Compile-Time Resolution#} rules.
+
+ {#code_begin|test|unused_fn#}
+// Although this function returns the wrong type, no compile error
+// because this function is never called.
+fn unused() i32 {
+ return "wrong return type";
+}
+test "unused function" { }
+ {#code_end#}
+ {#header_close#}
+ {#header_open|Self-Referential Containers#}
+
+ When a container is given a name, it can refer to itself directly or indirectly unless
+ it causes an infinite recursion. To avoid infinite recursion, use a {#link|pointer|Pointers#}, or
+ pointer-like type.
+
+ Containers can only self-reference if they are named by a top-level declaration.
+ The compiler will give an error when trying to self-reference containers from
+ non-top-level declarations:
+
+ {#code_begin|exe_build_err|self_reference_error#}
+const print = @import("std").debug.print;
+
+fn deepThought() i32 {
+ const Node = struct { // Node is not named by a top-level declaration
+ child: ?*Node, // This container cannot self-reference
+ data: i32,
+ };
+ var leaf = Node{
+ .child = null,
+ .data = 42,
+ };
+ const root = Node{
+ .child = &leaf,
+ .data = 0,
+ };
+ return root.child.?.data;
+}
+
+fn ultimateChoice() bool {
+ const Choices = enum { // This is not named by a top-level declaration
+ B,
+ C,
+ Zig,
+ const default = Choices.Zig; // This container cannot self-reference
+ };
+
+ return .Zig == Choices.default;
+}
+
+pub fn main() void {
+ print("The ultimate answer: {any}", .{deepThought()});
+ print("You chose Zig: {}", .{ultimateChoice()});
+}
+ {#code_end#}
+
+ To have the same result as self-referencing, the code sample above can use the {#syntax#}@This(){#endsyntax#}
+ {#link|built-in function|@This#}:
+
+ Code written within one or more {#syntax#}test{#endsyntax#} declarations can help to
+ ensure behavior meets expectations:
+
+ {#code_begin|test|introducing_zig_test#}
+const std = @import("std");
-test "address of syntax" {
- // Get the address of a variable:
- const x: i32 = 1234;
- const x_ptr = &x;
+test "expect addOne adds one to 41 correctly" {
- // Dereference a pointer:
- try expect(x_ptr.* == 1234);
+ // The Standard Library contains useful functions to help create tests.
+ // `expect` is a function that verifies its argument is true.
+ // It will return an error if its argument is false to indicate a failure.
+ // `try` is used to return an error to the test runner to notify it that the test failed.
+ try std.testing.expect(addOne(41) == 42);
+}
- // When you get the address of a const variable, you get a const single-item pointer.
- try expect(@TypeOf(x_ptr) == *const i32);
+/// The function `addOne` adds one to the number given as its argument.
+fn addOne(number: i32) i32 {
+ return number + 1;
+}
+ {#code_end#}
+
+ zig test is a tool that creates and runs a test build. By default, it builds and runs an
+ executable program using the default test runner provided by the {#link|Zig Standard Library#}
+ as its main entry point. During the build, {#syntax#}test{#endsyntax#} declarations found while
+ {#link|resolving|Root Source File#} the given Zig source file are included for the default test runner
+ to run and report on.
+
+
+
+ The shell output shown above displays the line Test [1/1] test "expect addOne adds one to 41 correctly"....
+ This line was printed to standard error (stderr) by the test runner program. (When the test runner program's
+ standard error is redirected to the terminal, these lines are cleared when a test succeeds.)
+ Lines like this indicate which test, out of the total number of tests, is being run. In this case, [1/1]
+ indicates that the first test, out of a total of one test, is being run. The next line, All 1 tests passed.,
+ is also printed to standard error. It indicates the total number of tests that have passed.
+
+
+ The code sample in introducing_zig_test.zig tests the {#link|function|Functions#}
+ {#syntax#}addOne{#endsyntax#} to ensure that it correctly adds one to {#syntax#}41{#endsyntax#}.
+ From this test's perspective, the {#syntax#}addOne{#endsyntax#} function is said to be
+ code under test.
+
+ {#header_open|Test Declarations#}
+
+ Test declarations contain the {#link|keyword|Keyword Reference#} {#syntax#}test{#endsyntax#} followed by an
+ optional name written between double-quotation marks followed by a {#link|block|blocks#} containing test code.
+
+
+ By convention, non-named tests should be used when referencing other tests. Non-named tests are run regardless
+ of the {#link|--test-filter|Skip Tests#} command-line parameter.
+
+
+ Tests are similar to {#link|Functions#}: they have a return type and a block of code. The implicit
+ return type of {#syntax#}test{#endsyntax#} is the {#link|Error Union Type#} {#syntax#}anyerror!void{#endsyntax#}.
+ When a Zig source file is not built using the zig test tool, the tests are omitted from the build.
+
+
+ Test declarations can be written in the same file where code under test is written or in a separate Zig source file.
+ Since test declarations are {#link|top-level|Top Level#} declarations, they are order-independent and can
+ be written before or after the code under test.
+
+ {#see_also|The Global Error Set#}
+ {#header_close#}
+ {#header_open|Nested Test Declarations#}
+
+ Top-level test declarations are {#link|lazily analyzed|Lazy Top-Level Declaration Analysis#}.
+ When the zig test tool is building a test runner, only resolved {#syntax#}test{#endsyntax#}
+ declarations are included in the build. Initially, the given Zig source file's top-level declarations are
+ resolved. Unless nested containers are referenced, the nested containers' tests will not be resolved.
+
+
+ The code sample below uses the {#syntax#}std.testing.refAllDecls(@This()){#endsyntax#} function call to
+ reference all of the {#link|Containers#} that are in the file. This includes the test declarations
+ located in imported, local Zig source files. The code sample also shows an alternative way to reference
+ containers using the {#syntax#}_ = C;{#endsyntax#} syntax. This syntax tells the compiler to ignore the
+ result of the expression on the right side of the assignment operator.
+
+ {#code_begin|test|testdecl_container_top_level#}
+const std = @import("std");
+const expect = std.testing.expect;
- // If you want to mutate the value, you'd need an address of a mutable variable:
- var y: i32 = 5678;
- const y_ptr = &y;
- try expect(@TypeOf(y_ptr) == *i32);
- y_ptr.* += 1;
- try expect(y_ptr.* == 5679);
+// Locally imported source files will be tested when referenced
+const local_import = @import("introducing_zig_test.zig");
+
+test {
+ // To run nested-containers' tests, either, call `refAllDecls` which will
+ // reference all declarations located in the given argument.
+ // `@This()` is a builtin function that returns the innermost container it is called from.
+ // In this example, the innermost container is this file (implicitly a struct).
+ std.testing.refAllDecls(@This());
+
+ // or, reference each container individually from a top-level test declaration.
+ // The `_ = C;` syntax is a no-op reference to the identifier `C`.
+ _ = S;
+ _ = E;
+ _ = U;
+ _ = O;
+
+ // Alternative to `refAllDecls`, other local source files containing tests can
+ // also be referenced like this
+ _ = @import("introducing_zig_test.zig");
}
-test "pointer array access" {
- // Taking an address of an individual element gives a
- // single-item pointer. This kind of pointer
- // does not support pointer arithmetic.
- var array = [_]u8{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
- const ptr = &array[2];
- try expect(@TypeOf(ptr) == *u8);
+const S = struct {
+ test "S demo test" {
+ try expect(true);
+ }
+};
- try expect(array[2] == 3);
- ptr.* += 1;
- try expect(array[2] == 4);
+const E = enum {
+ V,
+
+ test "E demo test" {
+ try expect(true);
+ }
+};
+
+const U = union {
+ s: US, // US is referenced here; therefore, its test will run
+
+ const US = struct {
+ test "U.US demo test" {
+ // This test is a top-level test declaration for the struct.
+ // The struct is nested (declared) inside of a union.
+ try expect(true);
+ }
+ };
+
+ test "U demo test" {
+ try expect(true);
+ }
+};
+
+const O = opaque {
+ test "O demo test" {
+ try expect(true);
+ }
+};
+ {#code_end#}
+ {#header_close#}
+ {#header_open|Test Failure#}
+
+ The default test runner checks for an {#link|error|Errors#} returned from a test.
+ When a test returns an error, the test is considered a failure and the failure will be reported after all
+ test have been run. When a test fails, the default test runner will output the test that failed along with the
+ {#link|Error Return Traces#}.
+
+ {#code_begin|test_err#}
+const std = @import("std");
+
+test "expect this to fail" {
+ try std.testing.expect(false);
+}
+
+test "expect this to succeed" {
+ try std.testing.expect(true);
+}
+ {#code_end#}
+ {#header_close#}
+ {#header_open|Skip Tests#}
+
+ If a {#syntax#}test{#endsyntax#} returns the error {#syntax#}error.SkipZigTest{#endsyntax#}, the
+ default test runner will consider the test as being skipped. After all of the tests are complete,
+ the number of skipped tests is reported.
+
+ {#code_begin|test#}
+test "this will be skipped" {
+ return error.SkipZigTest;
+}
+ {#code_end#}
+
+ The default test runner also skips tests containing a {#link|suspend point|Async Functions#} and the
+ test is running using the default, blocking IO mode.
+ (The evented IO mode is enabled using the --test-evented-io command line parameter.)
+
+ {#code_begin|test|async_skip#}
+const std = @import("std");
+
+// This test is skipped because a suspend point exists
+// and the `io_mode`` is set to blocking (the default).
+test "async skip test" {
+ var frame = async func();
+ const result = await frame;
+ try std.testing.expect(result == 1);
+}
+
+fn func() i32 {
+ suspend {
+ resume @frame();
+ }
+ return 1;
+}
+ {#code_end#}
+
+ In the code sample above, the test would not be skipped in blocking IO mode if the {#syntax#}nosuspend{#endsyntax#}
+ keyword was used (see {#link|Async and Await#}).
+
+
+ Another way to skip tests is to filter them out by using the zig test command line parameter
+ --test-filter [text]. This makes the test build only include tests whose name contains the
+ supplied filter text. Note, non-named tests are run even when using the --test-filter [text]
+ command line parameter.
+
+ When code allocates {#link|Memory#} using the {#link|Zig Standard Library#}'s testing allocator,
+ {#syntax#}std.testing.allocator{#endsyntax#}, the default test runner will report any leaks that are
+ found from using this allocator:
+
+ To detect that code is being run by the zig test tool, use the
+ {#link|compile variable|Compile Variables#} {#syntax#}@import("builtin").is_test{#endsyntax#}.
+
+ The Zig Standard Library's testing {#link|namespace|Container Namespace#} contains useful functions to help
+ you build tests. A few are listed here:
+
+ {#code_begin|test|testing_functions#}
+const std = @import("std");
+
+test "expect demo" {
+ try std.testing.expect(true);
+}
+
+test "expectEqual demo" {
+ const expected: i32 = 42;
+ const actual = 42;
+
+ // The first argument to `expectEqual` is the known, expected, result.
+ // The second argument is the result of some expression.
+ // The actual's type is casted to the type of expected.
+ try std.testing.expectEqual(expected, actual);
+}
+
+test "expectError demo" {
+ const expected_error = error.DemoError;
+ const actual_error_union: anyerror!void = error.DemoError;
+
+ // `expectError` will fail when the actual error is different than
+ // the expected error.
+ try std.testing.expectError(expected_error, actual_error_union);
}
{#code_end#}
+
The Zig Standard Library also contains functions to compare {#link|Slices#}, strings, and more. See the rest of the
+ {#syntax#}std.testing{#endsyntax#} namespace in the {#link|Zig Standard Library#} for more available functions.
- In Zig, we generally prefer {#link|Slices#} rather than {#link|Sentinel-Terminated Pointers#}.
- You can turn an array or pointer into a slice using slice syntax.
+ zig test has a few command line parameters which affect the compilation.
+ See zig test --help for a full list.
- Slices have bounds checking and are therefore protected
- against this kind of undefined behavior. This is one reason
- we prefer slices to pointers.
+ A variable is a unit of {#link|Memory#} storage.
+ Variables are never allowed to shadow identifiers from an outer scope.
+
+
+ It is generally preferable to use {#syntax#}const{#endsyntax#} rather than
+ {#syntax#}var{#endsyntax#} when declaring a variable. This causes less work for both
+ humans and computers to do when reading code, and creates more optimization opportunities.
+
+ {#header_open|Container Level Variables#}
+
+ {#link|Container level|Top-Level#} variables have static lifetime and are order-independent and lazily analyzed.
+ The initialization value of container level variables is implicitly
+ {#link|comptime#}. If a container level variable is {#syntax#}const{#endsyntax#} then its value is
+ {#syntax#}comptime{#endsyntax#}-known, otherwise it is runtime-known.
+
To convert an integer address into a pointer, use {#syntax#}@intToPtr{#endsyntax#}.
- To convert a pointer to an integer, use {#syntax#}@ptrToInt{#endsyntax#}:
Zig is able to preserve memory addresses in comptime code, as long as
- the pointer is never dereferenced:
- {#code_begin|test|comptime_pointer_conversion#}
-const expect = @import("std").testing.expect;
+ {#header_close#}
-test "comptime @intToPtr" {
- comptime {
- // Zig is able to do this at compile-time, as long as
- // ptr is never dereferenced.
- const ptr = @intToPtr(*i32, 0xdeadbee0);
- const addr = @ptrToInt(ptr);
- try expect(@TypeOf(addr) == usize);
- try expect(addr == 0xdeadbee0);
- }
+ {#header_open|Static Local Variables#}
+
+ It is also possible to have local variables with static lifetime by using {#link|Containers#} inside functions.
+
+ {#code_begin|test|static_local_variable#}
+const std = @import("std");
+const expect = std.testing.expect;
+
+test "static local variable" {
+ try expect(foo() == 1235);
+ try expect(foo() == 1236);
}
- {#code_end#}
- {#see_also|Optional Pointers|@intToPtr|@ptrToInt|C Pointers|Pointers to Zero Bit Types#}
- {#header_open|volatile#}
-
Loads and stores are assumed to not have side effects. If a given load or store
- should have side effects, such as Memory Mapped Input/Output (MMIO), use {#syntax#}volatile{#endsyntax#}.
- In the following code, loads and stores with {#syntax#}mmio_ptr{#endsyntax#} are guaranteed to all happen
- and in the same order as in source code:
- Note that {#syntax#}volatile{#endsyntax#} is unrelated to concurrency and {#link|Atomics#}.
- If you see code that is using {#syntax#}volatile{#endsyntax#} for something other than Memory Mapped
- Input/Output, it is probably a bug.
+ The {#syntax#}extern{#endsyntax#} keyword or {#link|@extern#} builtin function can be used to link against a variable that is exported
+ from another object. The {#syntax#}export{#endsyntax#} keyword or {#link|@export#} builtin function
+ can be used to make a variable available to other objects at link time. In both cases,
+ the type of the variable must be C ABI compatible.
+ {#see_also|Exporting a C Library#}
{#header_close#}
-
- To convert one pointer type to another, use {#link|@ptrCast#}. This is an unsafe
- operation that Zig cannot protect you against. Use {#syntax#}@ptrCast{#endsyntax#} only when other
- conversions are not possible.
-
A variable may be specified to be a thread-local variable using the
+ {#syntax#}threadlocal{#endsyntax#} keyword:
+ {#code_begin|test|tls#}
+const std = @import("std");
+const assert = std.debug.assert;
- // Even this example is contrived - there are better ways to do the above than
- // pointer casting. For example, using a slice narrowing cast:
- const u32_value = std.mem.bytesAsSlice(u32, bytes[0..])[0];
- try expect(u32_value == 0x12121212);
+threadlocal var x: i32 = 1234;
- // And even another way, the most straightforward way to do it:
- try expect(@bitCast(u32, bytes) == 0x12121212);
+test "thread local storage" {
+ const thread1 = try std.Thread.spawn(.{}, testTls, .{});
+ const thread2 = try std.Thread.spawn(.{}, testTls, .{});
+ testTls();
+ thread1.join();
+ thread2.join();
}
-test "pointer child type" {
- // pointer types have a `child` field which tells you the type they point to.
- try expect(@typeInfo(*u32).Pointer.child == u32);
+fn testTls() void {
+ assert(x == 1234);
+ x += 1;
+ assert(x == 1235);
}
{#code_end#}
- {#header_open|Alignment#}
- Each type has an alignment - a number of bytes such that,
- when a value of the type is loaded from or stored to memory,
- the memory address must be evenly divisible by this number. You can use
- {#link|@alignOf#} to find out this value for any type.
+ For {#link|Single Threaded Builds#}, all thread local variables are treated as regular {#link|Container Level Variables#}.
- Alignment depends on the CPU architecture, but is always a power of two, and
- less than {#syntax#}1 << 29{#endsyntax#}.
+ Thread local variables may not be {#syntax#}const{#endsyntax#}.
- In Zig, a pointer type has an alignment value. If the value is equal to the
- alignment of the underlying type, it can be omitted from the type:
+ Local variables occur inside {#link|Functions#}, {#link|comptime#} blocks, and {#link|@cImport#} blocks.
- {#code_begin|test|variable_alignment#}
+
+ When a local variable is {#syntax#}const{#endsyntax#}, it means that after initialization, the variable's
+ value will not change. If the initialization value of a {#syntax#}const{#endsyntax#} variable is
+ {#link|comptime#}-known, then the variable is also {#syntax#}comptime{#endsyntax#}-known.
+
+
+ A local variable may be qualified with the {#syntax#}comptime{#endsyntax#} keyword. This causes
+ the variable's value to be {#syntax#}comptime{#endsyntax#}-known, and all loads and stores of the
+ variable to happen during semantic analysis of the program, rather than at runtime.
+ All variables declared in a {#syntax#}comptime{#endsyntax#} expression are implicitly
+ {#syntax#}comptime{#endsyntax#} variables.
+
+ {#code_begin|test|comptime_vars#}
const std = @import("std");
const expect = std.testing.expect;
-test "variable alignment" {
- var x: i32 = 1234;
- const align_of_i32 = @alignOf(@TypeOf(x));
- try expect(@TypeOf(&x) == *i32);
- try expect(*i32 == *align(align_of_i32) i32);
- if (std.Target.current.cpu.arch == .x86_64) {
- try expect(@typeInfo(*i32).Pointer.alignment == 4);
+test "comptime vars" {
+ var x: i32 = 1;
+ comptime var y: i32 = 1;
+
+ x += 1;
+ y += 1;
+
+ try expect(x == 2);
+ try expect(y == 2);
+
+ if (y != 2) {
+ // This compile error never triggers because y is a comptime variable,
+ // and so `y != 2` is a comptime value, and this if is statically evaluated.
+ @compileError("wrong y value");
+ }
+}
+ {#code_end#}
+ {#header_close#}
+ {#header_close#}
+
+ {#header_open|Arrays#}
+ {#code_begin|test|arrays#}
+const expect = @import("std").testing.expect;
+const assert = @import("std").debug.assert;
+const mem = @import("std").mem;
+
+// array literal
+const message = [_]u8{ 'h', 'e', 'l', 'l', 'o' };
+
+// get the size of an array
+comptime {
+ assert(message.len == 5);
+}
+
+// A string literal is a single-item pointer to an array literal.
+const same_message = "hello";
+
+comptime {
+ assert(mem.eql(u8, &message, same_message));
+}
+
+test "iterate over an array" {
+ var sum: usize = 0;
+ for (message) |byte| {
+ sum += byte;
+ }
+ try expect(sum == 'h' + 'e' + 'l' * 2 + 'o');
+}
+
+// modifiable array
+var some_integers: [100]i32 = undefined;
+
+test "modify an array" {
+ for (some_integers) |*item, i| {
+ item.* = @intCast(i32, i);
}
+ try expect(some_integers[10] == 10);
+ try expect(some_integers[99] == 99);
}
- {#code_end#}
-
In the same way that a {#syntax#}*i32{#endsyntax#} can be {#link|coerced|Type Coercion#} to a
- {#syntax#}*const i32{#endsyntax#}, a pointer with a larger alignment can be implicitly
- cast to a pointer with a smaller alignment, but not vice versa.
-
-
- You can specify alignment on variables and functions. If you do this, then
- pointers to them get the specified alignment:
-
- If you have a pointer or a slice that has a small alignment, but you know that it actually
- has a bigger alignment, use {#link|@alignCast#} to change the
- pointer into a more aligned pointer. This is a no-op at runtime, but inserts a
- {#link|safety check|Incorrect Pointer Alignment#}:
-
- This pointer attribute allows a pointer to have address zero. This is only ever needed on the
- freestanding OS target, where the address zero is mappable. If you want to represent null pointers, use
- {#link|Optional Pointers#} instead. {#link|Optional Pointers#} with {#syntax#}allowzero{#endsyntax#}
- are not the same size as pointers. In this code example, if the pointer
- did not have the {#syntax#}allowzero{#endsyntax#} attribute, this would be a
- {#link|Pointer Cast Invalid Null#} panic:
-
- {#code_begin|test|allowzero#}
+ {#header_open|Anonymous List Literals#}
+
Similar to {#link|Enum Literals#} and {#link|Anonymous Struct Literals#}
+ the type can be omitted from array literals:
- The syntax {#syntax#}[*:x]T{#endsyntax#} describes a pointer that
- has a length determined by a sentinel value. This provides protection
- against buffer overflow and overreads.
+ If there is no type in the result location then an anonymous list literal actually
+ turns into a {#link|struct#} with numbered field names:
- {#code_begin|exe_build_err#}
- {#link_libc#}
+ {#code_begin|test|infer_list_literal#}
const std = @import("std");
+const expect = std.testing.expect;
-// This is also available as `std.c.printf`.
-pub extern "c" fn printf(format: [*:0]const u8, ...) c_int;
-
-pub fn main() anyerror!void {
- _ = printf("Hello, world!\n"); // OK
+test "fully anonymous list literal" {
+ try dump(.{ @as(u32, 1234), @as(f64, 12.34), true, "hi"});
+}
- const msg = "Hello, world!\n";
- const non_null_terminated_msg: [msg.len]u8 = msg.*;
- _ = printf(&non_null_terminated_msg);
+fn dump(args: anytype) !void {
+ try expect(args.@"0" == 1234);
+ try expect(args.@"1" == 12.34);
+ try expect(args.@"2");
+ try expect(args.@"3"[0] == 'h');
+ try expect(args.@"3"[1] == 'i');
}
{#code_end#}
- {#see_also|Sentinel-Terminated Slices|Sentinel-Terminated Arrays#}
- {#header_close#}
{#header_close#}
- {#header_open|Slices#}
- {#code_begin|test_safety|index out of bounds#}
-const expect = @import("std").testing.expect;
-
-test "basic slices" {
- var array = [_]i32{ 1, 2, 3, 4 };
- // A slice is a pointer and a length. The difference between an array and
- // a slice is that the array's length is part of the type and known at
- // compile-time, whereas the slice's length is known at runtime.
- // Both can be accessed with the `len` field.
- var known_at_runtime_zero: usize = 0;
- const slice = array[known_at_runtime_zero..array.len];
- try expect(&slice[0] == &array[0]);
- try expect(slice.len == array.len);
-
- // Using the address-of operator on a slice gives a single-item pointer,
- // while using the `ptr` field gives a many-item pointer.
- try expect(@TypeOf(slice.ptr) == [*]i32);
- try expect(@TypeOf(&slice[0]) == *i32);
- try expect(@ptrToInt(slice.ptr) == @ptrToInt(&slice[0]));
-
- // Slices have array bounds checking. If you try to access something out
- // of bounds, you'll get a safety check failure:
- slice[10] += 1;
-
- // Note that `slice.ptr` does not invoke safety checking, while `&slice[0]`
- // asserts that the slice has len >= 1.
-}
- {#code_end#}
-
+ Multidimensional arrays can be created by nesting arrays:
+
+ {#code_begin|test|multidimensional#}
const std = @import("std");
const expect = std.testing.expect;
-const mem = std.mem;
-const fmt = std.fmt;
-
-test "using slices for strings" {
- // Zig has no concept of strings. String literals are const pointers
- // to null-terminated arrays of u8, and by convention parameters
- // that are "strings" are expected to be UTF-8 encoded slices of u8.
- // Here we coerce *const [5:0]u8 and *const [6:0]u8 to []const u8
- const hello: []const u8 = "hello";
- const world: []const u8 = "世界";
-
- var all_together: [100]u8 = undefined;
- // You can use slice syntax on an array to convert an array into a slice.
- const all_together_slice = all_together[0..];
- // String concatenation example.
- const hello_world = try fmt.bufPrint(all_together_slice, "{s} {s}", .{ hello, world });
- // Generally, you can use UTF-8 and not worry about whether something is a
- // string. If you don't need to deal with individual characters, no need
- // to decode.
- try expect(mem.eql(u8, hello_world, "hello 世界"));
-}
-
-test "slice pointer" {
- var array: [10]u8 = undefined;
- const ptr = &array;
-
- // You can use slicing syntax to convert a pointer into a slice:
- const slice = ptr[0..5];
- slice[2] = 3;
- try expect(slice[2] == 3);
- // The slice is mutable because we sliced a mutable pointer.
- // Furthermore, it is actually a pointer to an array, since the start
- // and end indexes were both comptime-known.
- try expect(@TypeOf(slice) == *[5]u8);
+const mat4x4 = [4][4]f32{
+ [_]f32{ 1.0, 0.0, 0.0, 0.0 },
+ [_]f32{ 0.0, 1.0, 0.0, 1.0 },
+ [_]f32{ 0.0, 0.0, 1.0, 0.0 },
+ [_]f32{ 0.0, 0.0, 0.0, 1.0 },
+};
+test "multidimensional arrays" {
+ // Access the 2D array by indexing the outer array, and then the inner array.
+ try expect(mat4x4[1][1] == 1.0);
- // You can also slice a slice:
- const slice2 = slice[2..3];
- try expect(slice2.len == 1);
- try expect(slice2[0] == 3);
+ // Here we iterate with for loops.
+ for (mat4x4) |row, row_index| {
+ for (row) |cell, column_index| {
+ if (row_index == column_index) {
+ try expect(cell == 1.0);
+ }
+ }
+ }
}
{#code_end#}
- {#see_also|Pointers|for|Arrays#}
+ {#header_close#}
- {#header_open|Sentinel-Terminated Slices#}
+ {#header_open|Sentinel-Terminated Arrays#}
- The syntax {#syntax#}[:x]T{#endsyntax#} is a slice which has a runtime known length
- and also guarantees a sentinel value at the element indexed by the length. The type does not
- guarantee that there are no sentinel elements before that. Sentinel-terminated slices allow element
- access to the {#syntax#}len{#endsyntax#} index.
+ The syntax {#syntax#}[N:x]T{#endsyntax#} describes an array which has a sentinel element of value {#syntax#}x{#endsyntax#} at the
+ index corresponding to {#syntax#}len{#endsyntax#}.
- In the context of the Zig programming language, a container is a generic term used for
- describing a language feature that groups related data and functionality.
+ A vector is a group of booleans, {#link|Integers#}, {#link|Floats#}, or {#link|Pointers#} which are operated on
+ in parallel using a single instruction ({#link|SIMD#}). Vector types are created with the builtin function {#link|@Type#},
+ or using the shorthand as {#syntax#}std.meta.Vector{#endsyntax#}.
-
Containers are used for creating user-defined types.
- {#header_open|Types of Containers#}
-
-
A file containing Zig code is a type of container. Implicitly, a Zig source file is a {#link|structure (struct)|struct#}.
- Containers have a default, extern, or packed memory layout. See specific container documentation
- for more information.
+ TODO talk about C ABI interop
- Containers can be declared inside one another.
+ TODO Zig's SIMD abilities are just beginning to be fleshed out. Here are some talking points to update the
+ docs with:
+ * What kind of operations can you do? All the operations on integers and floats? What about mixing scalar and vector?
+ * How to convert to/from vectors/arrays
+ * How to access individual elements from vectors, how to loop over the elements
+ * "shuffle"
+ * Advice on writing high perf software, how to abstract the best way
{#syntax#}T{#endsyntax#} must have a known size, which means that it cannot be
+ {#syntax#}c_void{#endsyntax#} or any other {#link|opaque type|opaque#}.
+
+
+
+
These types are closely related to {#link|Arrays#} and {#link|Slices#}:
+
+
{#syntax#}*[N]T{#endsyntax#} - pointer to N items, same as single-item pointer to an array.
+
+
Supports index syntax: {#syntax#}array_ptr[i]{#endsyntax#}
Supports len property: {#syntax#}slice.len{#endsyntax#}
+
+
+
+
Use {#syntax#}&x{#endsyntax#} to obtain a single-item pointer:
+ {#code_begin|test|single_item_pointer_test#}
+const expect = @import("std").testing.expect;
- const WhyNoEnum = enum {
- not_yet,
- soon,
- later,
- };
- };
+test "address of syntax" {
+ // Get the address of a variable:
+ const x: i32 = 1234;
+ const x_ptr = &x;
- const Contribute = union(enum) {
- start_project,
- spread_word,
- translate,
- help_learn,
- work_on_issues,
- give: foundation.Sponsor,
- };
- };
+ // Dereference a pointer:
+ try expect(x_ptr.* == 1234);
- const foundation = struct {
- const Board = struct {
- president: community.Ziguana,
- secretary: community.Ziguana,
- treasurer: community.Ziguana,
- };
+ // When you get the address of a const variable, you get a const single-item pointer.
+ try expect(@TypeOf(x_ptr) == *const i32);
- const Sponsor = enum {
- monetary,
- infrastructure,
- };
- };
-};
+ // If you want to mutate the value, you'd need an address of a mutable variable:
+ var y: i32 = 5678;
+ const y_ptr = &y;
+ try expect(@TypeOf(y_ptr) == *i32);
+ y_ptr.* += 1;
+ try expect(y_ptr.* == 5679);
+}
-test "nested containers" {
- const friendo = zig.community.Ziguana{ .yes = .wizard };
- const soon_friendo = zig.community.Ziguana{ .no = .soon };
- const help_out = zig.community.Contribute.spread_word;
- const donate = zig.foundation.Sponsor.monetary;
+test "pointer array access" {
+ // Taking an address of an individual element gives a
+ // single-item pointer. This kind of pointer
+ // does not support pointer arithmetic.
+ var array = [_]u8{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
+ const ptr = &array[2];
+ try expect(@TypeOf(ptr) == *u8);
- try std.testing.expect(friendo.yes == .wizard);
- try std.testing.expect(soon_friendo.no == .soon);
- try std.testing.expect(help_out == .spread_word);
- try std.testing.expect(donate == .monetary);
+ try expect(array[2] == 3);
+ ptr.* += 1;
+ try expect(array[2] == 4);
}
{#code_end#}
- {#header_close#}
- {#header_open|Container Namespace#}
-
- When a container is given a name, it provides a namespace for the identifiers it contains. Identifiers
- that are namespaced are unique.
-
- There are three ways to name a container:
+ In Zig, we generally prefer {#link|Slices#} rather than {#link|Sentinel-Terminated Pointers#}.
+ You can turn an array or pointer into a slice using slice syntax.
-
-
The file name (without extension) of a Zig source file is the name of the implicit struct it represents. See {#link|@import#}.
-
When a container is assigned to an identifier, the container's name is the identifier.
-
When a container is in the {#syntax#}return{#endsyntax#} expression of a function, the
- container's name is the function's name.
-
- Otherwise, the container is considered to be anonymous.
+ Slices have bounds checking and are therefore protected
+ against this kind of undefined behavior. This is one reason
+ we prefer slices to pointers.
- {#code_begin|exe|container_namespace#}
-const std = @import("std");
+ {#code_begin|test|slice_bounds#}
+const expect = @import("std").testing.expect;
-// The following three functions return a `type` of a container.
-// The containers are given the name of the function.
-fn StructFun(comptime T: type) type {
- return struct { f: T };
-}
+test "pointer slicing" {
+ var array = [_]u8{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
+ const slice = array[2..4];
+ try expect(slice.len == 2);
-fn EnumFun(comptime T: type) type {
- return enum(T) { f };
+ try expect(array[3] == 4);
+ slice[1] += 1;
+ try expect(array[3] == 5);
}
+ {#code_end#}
+
Pointers work at compile-time too, as long as the code does not depend on
+ an undefined memory layout:
To convert an integer address into a pointer, use {#syntax#}@intToPtr{#endsyntax#}.
+ To convert a pointer to an integer, use {#syntax#}@ptrToInt{#endsyntax#}:
+ {#code_begin|test|integer_pointer_conversion#}
+const expect = @import("std").testing.expect;
-pub fn main() void {
- // The name of an import
- std.debug.print("As import: {s}\n\n", .{@typeName(@import("hello.zig"))});
-
- // In this example, containers are declared in three ways:
- // 1. Declared and stored in a constant local identifier
- // 2. By using a function to declare the container at compile-time
- // 3. By declaring the container anonymously
- //
- // The name of the containers are printed using the code below.
-
- const S = struct { f: i32 };
- const sFun = StructFun(i32);
- std.debug.print("As identifier: {s}\n", .{@typeName(S)});
- std.debug.print("As function: {s}\n", .{@typeName(sFun)});
- std.debug.print("As anonymous: {s}\n\n", .{@typeName(struct { f: i32 })});
-
- const E = enum { f };
- const eFun = EnumFun(u8);
- std.debug.print("As identifier: {s}\n", .{@typeName(E)});
- std.debug.print("As function: {s}\n", .{@typeName(eFun)});
- std.debug.print("As anonymous: {s}\n\n", .{@typeName(enum { f })});
+test "@ptrToInt and @intToPtr" {
+ const ptr = @intToPtr(*i32, 0xdeadbee0);
+ const addr = @ptrToInt(ptr);
+ try expect(@TypeOf(addr) == usize);
+ try expect(addr == 0xdeadbee0);
+}
+ {#code_end#}
+
Zig is able to preserve memory addresses in comptime code, as long as
+ the pointer is never dereferenced:
+ {#code_begin|test|comptime_pointer_conversion#}
+const expect = @import("std").testing.expect;
- const U = union { f: bool };
- const uFun = UnionFun(bool);
- std.debug.print("As identifier: {s}\n", .{@typeName(U)});
- std.debug.print("As function: {s}\n", .{@typeName(uFun)});
- std.debug.print("As anonymous: {s}\n\n", .{@typeName(union { f: bool })});
+test "comptime @intToPtr" {
+ comptime {
+ // Zig is able to do this at compile-time, as long as
+ // ptr is never dereferenced.
+ const ptr = @intToPtr(*i32, 0xdeadbee0);
+ const addr = @ptrToInt(ptr);
+ try expect(@TypeOf(addr) == usize);
+ try expect(addr == 0xdeadbee0);
+ }
}
{#code_end#}
- {#header_open|usingnamespace#}
-
- {#syntax#}usingnamespace{#endsyntax#} is a declaration that mixes all the public
- declarations of the operand, which must be a {#link|struct#}, {#link|union#}, {#link|enum#},
- or {#link|opaque#}, into the immediate container's namespace:
-
- {#code_begin|test|usingnamespace#}
-test "using std namespace" {
- const S = struct {
- usingnamespace @import("std");
- };
- try S.testing.expect(true);
+ {#see_also|Optional Pointers|@intToPtr|@ptrToInt|C Pointers|Pointers to Zero Bit Types#}
+ {#header_open|volatile#}
+
Loads and stores are assumed to not have side effects. If a given load or store
+ should have side effects, such as Memory Mapped Input/Output (MMIO), use {#syntax#}volatile{#endsyntax#}.
+ In the following code, loads and stores with {#syntax#}mmio_ptr{#endsyntax#} are guaranteed to all happen
+ and in the same order as in source code:
- {#syntax#}usingnamespace{#endsyntax#} has an important use case when organizing the public
- API of a file or package. For example, one might have c.zig with all of the
- {#link|C imports|Import from C Header File#}:
-
- The above example demonstrates using {#syntax#}pub{#endsyntax#} to qualify the
- {#syntax#}usingnamespace{#endsyntax#} additionally makes the imported declarations
- {#syntax#}pub{#endsyntax#}. This can be used to forward declarations, giving precise control
- over what declarations a given file exposes.
+ Note that {#syntax#}volatile{#endsyntax#} is unrelated to concurrency and {#link|Atomics#}.
+ If you see code that is using {#syntax#}volatile{#endsyntax#} for something other than Memory Mapped
+ Input/Output, it is probably a bug.
- The term top-level (also spelled "top level") refers to direct child of a container.
-
-
- Direct children of a container are called container level members, container level,
- container members, top-level members, top-level or just members.
-
- Let's revisit the source code for the Hello, world! program again, as shown below. The
- hello_again_again.zig source file is a container. Its first whole line of code
- is its {#link|top-level variable declaration|Container Level Variables#}. The code starting at the third line until the end
- of the file is the hello_again_again container's top-level {#link|function|Functions#} declaration.
- In summary, the hello_again_again container has two top-level members.
+ To convert one pointer type to another, use {#link|@ptrCast#}. This is an unsafe
+ operation that Zig cannot protect you against. Use {#syntax#}@ptrCast{#endsyntax#} only when other
+ conversions are not possible.
- Now, let's look at nesting containers and point out top-level language constructs. In the following code sample,
- nested_containers_top_levels.zig is a container. The rest will be explained after the
- code sample. Focus on the form of the code and ignore the meaning of the code.
-
- {#code_begin|syntax|nested_containers_top_levels#}
-const print = @import("std").debug.print;
+test "pointer casting" {
+ const bytes align(@alignOf(u32)) = [_]u8{ 0x12, 0x12, 0x12, 0x12 };
+ const u32_ptr = @ptrCast(*const u32, &bytes);
+ try expect(u32_ptr.* == 0x12121212);
-const S = struct {
- pub fn sTopLevelFn() void {
- print("Hello, world!\n", .{});
- }
+ // Even this example is contrived - there are better ways to do the above than
+ // pointer casting. For example, using a slice narrowing cast:
+ const u32_value = std.mem.bytesAsSlice(u32, bytes[0..])[0];
+ try expect(u32_value == 0x12121212);
- sTopLevelField: i32,
+ // And even another way, the most straightforward way to do it:
+ try expect(@bitCast(u32, bytes) == 0x12121212);
+}
- const sTopLevelEnum = enum {
- enumTopLevelField1,
- enumTopLevelField2,
- };
-};
+test "pointer child type" {
+ // pointer types have a `child` field which tells you the type they point to.
+ try expect(@typeInfo(*u32).Pointer.child == u32);
+}
{#code_end#}
-
-
{#syntax#}nested_containers_top_levels{#endsyntax#} is this example's root container and has two top-level declarations.
-
-
{#syntax#}const print = {#endsyntax#} is the root's top-level variable declaration
-
{#syntax#}const S = struct {#endsyntax#} is the root's top-level variable declaration,
- is a container, and has three top-level declarations.
-
-
{#syntax#}pub fn sTopLevelFn() void{#endsyntax#} is the struct's top-level function declaration
-
{#syntax#}sTopLevelField: i32{#endsyntax#} is the struct's top-level container field
-
{#syntax#}sTopLevelEnum: enum{#endsyntax#} is the struct's top-level variable declaration,
- is a container, and has two top-level declarations.
-
-
{#syntax#}enumTopLevelField1{#endsyntax#} is the enum's top-level container variant
-
{#syntax#}enumTopLevelField2{#endsyntax#} is the enum's top-level container variant
-
-
-
-
-
-
-
- {#header_open|Types of Top-Level#}
-
- There are three main types of top-level members.
-
When Container Documentation Comments are used, they are always the first member of a container.
- That is, they are put inside of the container before any of the other top-level members.
-
{#link|Top-Level Declarations#}
-
Top-Level Declarations introduce identifiers and functionality.
-
{#link|Container Fields#}
-
Container Fields are properties or variants of a container.
-
- {#header_open|Top-Level Declarations#}
-
The following are top-level declarations:
-
-
{#link|Test Declarations#}
-
{#link|Compile-Time Expressions#}
-
{#link|Function Declarations and Prototypes|Functions#}
-
{#link|Variable Declarations|Variables#}
-
{#link|usingnamespace#} Declarations
-
+ {#header_open|Alignment#}
- Note, {#link|Compile-Time Expressions#} and {#link|Variable Declarations|Variables#} can be located in places other than
- the top-level position as well. See their documentation to learn more.
+ Each type has an alignment - a number of bytes such that,
+ when a value of the type is loaded from or stored to memory,
+ the memory address must be evenly divisible by this number. You can use
+ {#link|@alignOf#} to find out this value for any type.
- Container fields are also called variants or tags depending on the
- {#link|type of container|Types of Containers#} being used. The number of fields a container can have
- also depends on the type of container being used.
+ Alignment depends on the CPU architecture, but is always a power of two, and
+ less than {#syntax#}1 << 29{#endsyntax#}.
- When fields are declared in a container, they must be grouped together. No other type of top-level member
- is allowed to be placed between container fields. The following code samples demonstrate the three valid
- ways to arrange top-level declarations and fields.
+ In Zig, a pointer type has an alignment value. If the value is equal to the
+ alignment of the underlying type, it can be omitted from the type:
In the same way that a {#syntax#}*i32{#endsyntax#} can be {#link|coerced|Type Coercion#} to a
+ {#syntax#}*const i32{#endsyntax#}, a pointer with a larger alignment can be implicitly
+ cast to a pointer with a smaller alignment, but not vice versa.
+
- Top-level declarations are order independent. They can be arranged in any order without changing
- the functionality of the program/library.
+ You can specify alignment on variables and functions. If you do this, then
+ pointers to them get the specified alignment:
- Top-level declarations are resolved at compile-time. When a top-level member uses an identifier, the compiler verifies
- that the identifier has been declared.
+ If you have a pointer or a slice that has a small alignment, but you know that it actually
+ has a bigger alignment, use {#link|@alignCast#} to change the
+ pointer into a more aligned pointer. This is a no-op at runtime, but inserts a
+ {#link|safety check|Incorrect Pointer Alignment#}:
-
Use of undeclared identifiers is a compile error caused by a top-level member being compile-time resolved.
- Zig does lazy, top-level declaration analysis, which means that if a top-level member is not called,
- or otherwise used, it is not analyzed with exception to the {#link|Top-Level Compile-Time Resolution#} rules.
+ This pointer attribute allows a pointer to have address zero. This is only ever needed on the
+ freestanding OS target, where the address zero is mappable. If you want to represent null pointers, use
+ {#link|Optional Pointers#} instead. {#link|Optional Pointers#} with {#syntax#}allowzero{#endsyntax#}
+ are not the same size as pointers. In this code example, if the pointer
+ did not have the {#syntax#}allowzero{#endsyntax#} attribute, this would be a
+ {#link|Pointer Cast Invalid Null#} panic:
- {#code_begin|test|unused_fn#}
-// Although this function returns the wrong type, no compile error
-// because this function is never called.
-fn unused() i32 {
- return "wrong return type";
+ {#code_begin|test|allowzero#}
+const std = @import("std");
+const expect = std.testing.expect;
+
+test "allowzero" {
+ var zero: usize = 0;
+ var ptr = @intToPtr(*allowzero i32, zero);
+ try expect(@ptrToInt(ptr) == 0);
}
-test "unused function" { }
{#code_end#}
{#header_close#}
- {#header_open|Self-Referential Containers#}
+
+ {#header_open|Sentinel-Terminated Pointers#}
- When a container is given a name, the body of a container can contain references to itself
- as long as it doesn't directly depend on itself. To get around self-dependency, use a
- {#link|pointer|Pointers#}, or pointer-like, type as shown in the {#syntax#}Node{#endsyntax#} and
- {#syntax#}JsonType{#endsyntax#} examples below.
+ The syntax {#syntax#}[*:x]T{#endsyntax#} describes a pointer that
+ has a length determined by a sentinel value. This provides protection
+ against buffer overflow and overreads.
- {#code_begin|test|self_reference_containers#}
+ {#code_begin|exe_build_err#}
+ {#link_libc#}
const std = @import("std");
-const expect = std.testing.expect;
-fn LinkedList(comptime T: type) type {
- return struct {
- pub const Node = struct { // This is declared at the top-level of a container
- prev: ?*Node, // So, it can self-reference
- next: ?*Node, // Node self-references using a pointer
- data: T,
- };
+// This is also available as `std.c.printf`.
+pub extern "c" fn printf(format: [*:0]const u8, ...) c_int;
- first: ?*Node,
- last: ?*Node,
- len: usize,
- };
+pub fn main() anyerror!void {
+ _ = printf("Hello, world!\n"); // OK
+
+ const msg = "Hello, world!\n";
+ const non_null_terminated_msg: [msg.len]u8 = msg.*;
+ _ = printf(&non_null_terminated_msg);
}
+ {#code_end#}
+ {#see_also|Sentinel-Terminated Slices|Sentinel-Terminated Arrays#}
+ {#header_close#}
+ {#header_close#}
-const Choices = enum {
- B,
- C,
- Zig,
- const default = Choices.Zig; // self-reference
-};
+ {#header_open|Slices#}
+ {#code_begin|test_safety|index out of bounds#}
+const expect = @import("std").testing.expect;
-const JsonType = union(enum) {
- object: ?struct { k: u32 },
- array: []const JsonType, // self-reference using a slice
- string: []const u8,
- number: u32,
- boolean: bool,
-};
+test "basic slices" {
+ var array = [_]i32{ 1, 2, 3, 4 };
+ // A slice is a pointer and a length. The difference between an array and
+ // a slice is that the array's length is part of the type and known at
+ // compile-time, whereas the slice's length is known at runtime.
+ // Both can be accessed with the `len` field.
+ var known_at_runtime_zero: usize = 0;
+ const slice = array[known_at_runtime_zero..array.len];
+ try expect(&slice[0] == &array[0]);
+ try expect(slice.len == array.len);
-test "self referencing containers" {
- // Test LinkedList
- const ListOfInts = LinkedList(i32);
- var node = ListOfInts.Node{
- .prev = null,
- .next = null,
- .data = 42,
- };
- var list = ListOfInts{
- .first = &node,
- .last = &node,
- .len = 1,
- };
- try expect(list.first.?.data == 42);
+ // Using the address-of operator on a slice gives a single-item pointer,
+ // while using the `ptr` field gives a many-item pointer.
+ try expect(@TypeOf(slice.ptr) == [*]i32);
+ try expect(@TypeOf(&slice[0]) == *i32);
+ try expect(@ptrToInt(slice.ptr) == @ptrToInt(&slice[0]));
- // Test Choices
- const choice = Choices.default;
- try std.testing.expect(choice == .Zig);
+ // Slices have array bounds checking. If you try to access something out
+ // of bounds, you'll get a safety check failure:
+ slice[10] += 1;
- // Test JsonType
- const jsonArray = &[_]JsonType{
- JsonType{ .number = 42 },
- JsonType{ .array = &[_]JsonType{
- JsonType{ .boolean = false },
- JsonType{ .boolean = true },
- } },
- };
- try std.testing.expect(jsonArray[1].array[1].boolean);
+ // Note that `slice.ptr` does not invoke safety checking, while `&slice[0]`
+ // asserts that the slice has len >= 1.
}
{#code_end#}
-
- Containers can only self-reference if they are named by a top-level declaration.
- The following sample demonstrates a compile error when trying to self-reference
- containers from non-top-level declarations.
-
- {#code_begin|test_err#}
+
This is one reason we prefer slices to pointers.
+ {#code_begin|test|slices#}
const std = @import("std");
+const expect = std.testing.expect;
+const mem = std.mem;
+const fmt = std.fmt;
-fn deepThought() i32 {
- const Node = struct { // This is not named by a top-level declaration
- child: ?*Node, // This container cannot self-reference
- data: i32,
- };
- var leaf = Node{
- .child = null,
- .data = 42,
- };
- const root = Node{
- .child = &leaf,
- .data = 0,
- };
- return root.child.?.data;
-}
+test "using slices for strings" {
+ // Zig has no concept of strings. String literals are const pointers
+ // to null-terminated arrays of u8, and by convention parameters
+ // that are "strings" are expected to be UTF-8 encoded slices of u8.
+ // Here we coerce *const [5:0]u8 and *const [6:0]u8 to []const u8
+ const hello: []const u8 = "hello";
+ const world: []const u8 = "世界";
-fn ultimateChoice() bool {
- const Choices = enum { // This is not named by a top-level declaration
- B,
- C,
- Zig,
- const default = Choices.Zig; // This container cannot self-reference
- };
+ var all_together: [100]u8 = undefined;
+ // You can use slice syntax on an array to convert an array into a slice.
+ const all_together_slice = all_together[0..];
+ // String concatenation example.
+ const hello_world = try fmt.bufPrint(all_together_slice, "{s} {s}", .{ hello, world });
- return .Zig == Choices.default;
+ // Generally, you can use UTF-8 and not worry about whether something is a
+ // string. If you don't need to deal with individual characters, no need
+ // to decode.
+ try expect(mem.eql(u8, hello_world, "hello 世界"));
}
-test "cannot self-reference" {
- std.testing.expect(deepThought() == 42);
- std.testing.expect(ultimateChoice());
+test "slice pointer" {
+ var array: [10]u8 = undefined;
+ const ptr = &array;
+
+ // You can use slicing syntax to convert a pointer into a slice:
+ const slice = ptr[0..5];
+ slice[2] = 3;
+ try expect(slice[2] == 3);
+ // The slice is mutable because we sliced a mutable pointer.
+ // Furthermore, it is actually a pointer to an array, since the start
+ // and end indexes were both comptime-known.
+ try expect(@TypeOf(slice) == *[5]u8);
+
+ // You can also slice a slice:
+ const slice2 = slice[2..3];
+ try expect(slice2.len == 1);
+ try expect(slice2[0] == 3);
}
{#code_end#}
+ {#see_also|Pointers|for|Arrays#}
+
+ {#header_open|Sentinel-Terminated Slices#}
- To have the same affect as self-referencing, the code sample above can use the {#syntax#}@This(){#endsyntax#}
- {#link|built-in function|@This#} as shown in the code sample below.
+ The syntax {#syntax#}[:x]T{#endsyntax#} is a slice which has a runtime known length
+ and also guarantees a sentinel value at the element indexed by the length. The type does not
+ guarantee that there are no sentinel elements before that. Sentinel-terminated slices allow element
+ access to the {#syntax#}len{#endsyntax#} index.