// If expressions have three uses, corresponding to the three types:
// * bool
// * ?T
// * anyerror!T
const expect = @import("std").testing.expect;
test "if expression" {
// If expressions are used instead of a ternary expression.
const a: u32 = 5;
const b: u32 = 4;
const result = if (a != b) 47 else 3089;
try expect(result == 47);
}
test "if boolean" {
// If expressions test boolean conditions.
const a: u32 = 5;
const b: u32 = 4;
if (a != b) {
try expect(true);
} else if (a == 9) {
unreachable;
} else {
unreachable;
}
}
test "if optional" {
// If expressions test for null.
const a: ?u32 = 0;
if (a) |value| {
try expect(value == 0);
} else {
unreachable;
}
const b: ?u32 = null;
if (b) |value| {
unreachable;
} else {
try expect(true);
}
// The else is not required.
if (a) |value| {
try expect(value == 0);
}
// To test against null only, use the binary equality operator.
if (b == null) {
try expect(true);
}
// Access the value by reference using a pointer capture.
var c: ?u32 = 3;
if (c) |*value| {
value.* = 2;
}
if (c) |value| {
try expect(value == 2);
} else {
unreachable;
}
}
test "if error union" {
// If expressions test for errors.
// Note the |err| capture on the else.
const a: anyerror!u32 = 0;
if (a) |value| {
try expect(value == 0);
} else |err| {
unreachable;
}
const b: anyerror!u32 = error.BadValue;
if (b) |value| {
unreachable;
} else |err| {
try expect(err == error.BadValue);
}
// The else and |err| capture is strictly required.
if (a) |value| {
try expect(value == 0);
} else |_| {}
// To check only the error value, use an empty block expression.
if (b) |_| {} else |err| {
try expect(err == error.BadValue);
}
// Access the value by reference using a pointer capture.
var c: anyerror!u32 = 3;
if (c) |*value| {
value.* = 9;
} else |err| {
unreachable;
}
if (c) |value| {
try expect(value == 9);
} else |err| {
unreachable;
}
}
test "if error union with optional" {
// If expressions test for errors before unwrapping optionals.
// The |optional_value| capture's type is ?u32.
const a: anyerror!?u32 = 0;
if (a) |optional_value| {
try expect(optional_value.? == 0);
} else |err| {
unreachable;
}
const b: anyerror!?u32 = null;
if (b) |optional_value| {
try expect(optional_value == null);
} else |err| {
unreachable;
}
const c: anyerror!?u32 = error.BadValue;
if (c) |optional_value| {
unreachable;
} else |err| {
try expect(err == error.BadValue);
}
// Access the value by reference by using a pointer capture each time.
var d: anyerror!?u32 = 3;
if (d) |*optional_value| {
if (optional_value.*) |*value| {
value.* = 9;
}
} else |err| {
unreachable;
}
if (d) |optional_value| {
try expect(optional_value.? == 9);
} else |err| {
unreachable;
}
}
for
const expect = @import("std").testing.expect;
test "for basics" {
const items = [_]i32 { 4, 5, 3, 4, 0 };
var sum: i32 = 0;
// For loops iterate over slices and arrays.
for (items) |value| {
// Break and continue are supported.
if (value == 0) {
continue;
}
sum += value;
}
try expect(sum == 16);
// To iterate over a portion of a slice, reslice.
for (items[0..1]) |value| {
sum += value;
}
try expect(sum == 20);
// To access the index of iteration, specify a second capture value.
// This is zero-indexed.
var sum2: i32 = 0;
for (items) |value, i| {
try expect(@TypeOf(i) == usize);
sum2 += @intCast(i32, i);
}
try expect(sum2 == 10);
}
test "for reference" {
var items = [_]i32 { 3, 4, 2 };
// Iterate over the slice by reference by
// specifying that the capture value is a pointer.
for (items) |*value| {
value.* += 1;
}
try expect(items[0] == 4);
try expect(items[1] == 5);
try expect(items[2] == 3);
}
test "for else" {
// For allows an else attached to it, the same as a while loop.
var items = [_]?i32 { 3, 4, null, 5 };
// For loops can also be used as expressions.
// Similar to while loops, when you break from a for loop, the else branch is not evaluated.
var sum: i32 = 0;
const result = for (items) |value| {
if (value != null) {
sum += value.?;
}
} else blk: {
try expect(sum == 12);
break :blk sum;
};
try expect(result == 12);
}
const std = @import("std");
const expect = std.testing.expect;
test "nested break" {
var count: usize = 0;
outer: for ([_]i32{ 1, 2, 3, 4, 5 }) |_| {
for ([_]i32{ 1, 2, 3, 4, 5 }) |_| {
count += 1;
break :outer;
}
}
try expect(count == 1);
}
test "nested continue" {
var count: usize = 0;
outer: for ([_]i32{ 1, 2, 3, 4, 5, 6, 7, 8 }) |_| {
for ([_]i32{ 1, 2, 3, 4, 5 }) |_| {
count += 1;
continue :outer;
}
}
try expect(count == 8);
}
fn max(comptime T: type, a: T, b: T) T {
return if (a > b) a else b;
}
fn gimmeTheBiggerFloat(a: f32, b: f32) f32 {
return max(f32, a, b);
}
fn gimmeTheBiggerInteger(a: u64, b: u64) u64 {
return max(u64, a, b);
}
fn max(comptime T: type, a: T, b:T) T {
if (a > b) {
return a;
}
return b;
}
fn List(comptime T: type) type {
return struct {
items: []T,
};
}
const MyList == List(u8);
var list = MyList{
.items = ...
}
fn max(comptime T: type, a: T, b: T) T {
return if (a > b) a else b;
}
// 编译出错
test "try to pass a runtime type" {
foo(false);
}
fn foo(condition: bool) void {
const result = max(
if (condition) f32 else u64,
1234,
5678);
_ = result;
}
在 zig 中 type 是一等公民,它们能赋值给变量,赋值给函数形参,从函数中返回,但是他们只能在编译器计算,所以 T 通过 comptime 来标识。
一个 comptime 参数意味着。
在调用方,该值必须在编译时已知,否则就是一个编译错误。
在函数定义中,该值在编译时是已知的。
要在上面的片段中引入另一个函数
fn max(comptime T: type, a: T, b: T) T {
return if (a > b) a else b;
}
test "try to pass a runtime type" {
foo(false);
}
fn foo(condition: bool) void {
const result = max(
if (condition) f32 else u64,
1234,
5678);
_ = result;
}
一个错误,因为程序员试图将一个在运行时才知道的值传递给一个期望在编译时知道的值的函数。
如果在分析函数时传递了一个违反类型检查器的类型,也不能工作
这就是编译时的鸭子类型的含义。
fn max(comptime T: type, a: T, b: T) T {
return if (a > b) a else b;
}
// can't work
test "try to compare bools" {
_ = max(bool, true, false);
}
反过来说,在带有 comptime 参数的函数定义中,该值在编译时是已知的。
这意味着,如果需要,实际上能使这个方法适用于 bool 类型。
n max(comptime T: type, a: T, b: T) T {
if (T == bool) {
return a or b;
} else if (a > b) {
return a;
} else {
return b;
}
}
test "try to compare bools" {
try @import("std").testing.expect(max(bool, false, true) == true);
}
zig没有字符串的概念,string literals 是 const pointers to null-terminated array of u8
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);
// You can also slice a slice:
const slice2 = slice[2..3];
try expect(slice2.len == 1);
try expect(slice2[0] == 3);
}
const std = @import("std");
const expect = std.testing.expect;
test "null terminated slice" {
const slice: [:0]const u8 = "hello";
try expect(slice.len == 5);
try expect(slice[5] == 0);
}
enum
const expect = @import("std").testing.expect;
const mem = @import("std").mem;
// Declare an enum.
const Type = enum {
ok,
not_ok,
};
// Declare a specific instance of the enum variant.
const c = Type.ok;
// If you want access to the ordinal value of an enum, you
// can specify the tag type.
const Value = enum(u2) {
zero,
one,
two,
};
// Now you can cast between u2 and Value.
// The ordinal value starts from 0, counting up for each member.
test "enum ordinal value" {
try expect(@enumToInt(Value.zero) == 0);
try expect(@enumToInt(Value.one) == 1);
try expect(@enumToInt(Value.two) == 2);
}
// You can override the ordinal value for an enum.
const Value2 = enum(u32) {
hundred = 100,
thousand = 1000,
million = 1000000,
};
test "set enum ordinal value" {
try expect(@enumToInt(Value2.hundred) == 100);
try expect(@enumToInt(Value2.thousand) == 1000);
try expect(@enumToInt(Value2.million) == 1000000);
}
// Enums can have methods, the same as structs and unions.
// Enum methods are not special, they are only namespaced
// functions that you can call with dot syntax.
const Suit = enum {
clubs,
spades,
diamonds,
hearts,
pub fn isClubs(self: Suit) bool {
return self == Suit.clubs;
}
};
test "enum method" {
const p = Suit.spades;
try expect(!p.isClubs());
}
// An enum variant of different types can be switched upon.
const Foo = enum {
string,
number,
none,
};
test "enum variant switch" {
const p = Foo.number;
const what_is_it = switch (p) {
Foo.string => "this is a string",
Foo.number => "this is a number",
Foo.none => "this is a none",
};
try expect(mem.eql(u8, what_is_it, "this is a number"));
}
// @typeInfo can be used to access the integer tag type of an enum.
const Small = enum {
one,
two,
three,
four,
};
test "std.meta.Tag" {
try expect(@typeInfo(Small).Enum.tag_type == u2);
}
// @typeInfo tells us the field count and the fields names:
test "@typeInfo" {
try expect(@typeInfo(Small).Enum.fields.len == 4);
try expect(mem.eql(u8, @typeInfo(Small).Enum.fields[1].name, "two"));
}
// @tagName gives a []const u8 representation of an enum value:
test "@tagName" {
try expect(mem.eql(u8, @tagName(Small.three), "three"));
}
const std = @import("std");
const expect = std.testing.expect;
const Color = enum {
auto,
off,
on,
};
test "enum literals" {
const color1: Color = .auto;
const color2 = Color.auto;
try expect(color1 == color2);
}
test "switch using enum literals" {
const color = Color.on;
const result = switch (color) {
.auto => false,
.on => true,
.off => false,
};
try expect(result);
}
extern enum
默认的 enum 不是 C-abi 兼容的,如果要兼容,那么得声明
const Foo = extern enum { a, b, c };
export fn entry(foo: Foo) void { }
// Declare a struct.
// Zig gives no guarantees about the order of fields and the size of
// the struct but the fields are guaranteed to be ABI-aligned.
const Point = struct {
x: f32,
y: f32,
};
// Maybe we want to pass it to OpenGL so we want to be particular about
// how the bytes are arranged.
const Point2 = packed struct {
x: f32,
y: f32,
};
// Declare an instance of a struct.
const p = Point {
.x = 0.12,
.y = 0.34,
};
// Maybe we're not ready to fill out some of the fields.
var p2 = Point {
.x = 0.12,
.y = undefined,
};
// Structs can have methods
// Struct methods are not special, they are only namespaced
// functions that you can call with dot syntax.
const Vec3 = struct {
x: f32,
y: f32,
z: f32,
pub fn init(x: f32, y: f32, z: f32) Vec3 {
return Vec3 {
.x = x,
.y = y,
.z = z,
};
}
pub fn dot(self: Vec3, other: Vec3) f32 {
return self.x * other.x + self.y * other.y + self.z * other.z;
}
};
const expect = @import("std").testing.expect;
test "dot product" {
const v1 = Vec3.init(1.0, 0.0, 0.0);
const v2 = Vec3.init(0.0, 1.0, 0.0);
try expect(v1.dot(v2) == 0.0);
// Other than being available to call with dot syntax, struct methods are
// not special. You can reference them as any other declaration inside
// the struct:
try expect(Vec3.dot(v1, v2) == 0.0);
}
// Structs can have global declarations.
// Structs can have 0 fields.
const Empty = struct {
pub const PI = 3.14;
};
test "struct namespaced variable" {
try expect(Empty.PI == 3.14);
try expect(@sizeOf(Empty) == 0);
// you can still instantiate an empty struct
const does_nothing = Empty {};
}
// struct field order is determined by the compiler for optimal performance.
// however, you can still calculate a struct base pointer given a field pointer:
fn setYBasedOnX(x: *f32, y: f32) void {
const point = @fieldParentPtr(Point, "x", x);
point.y = y;
}
test "field parent pointer" {
var point = Point {
.x = 0.1234,
.y = 0.5678,
};
setYBasedOnX(&point.x, 0.9);
try expect(point.y == 0.9);
}
// You can return a struct from a function. This is how we do generics
// in Zig:
fn LinkedList(comptime T: type) type {
return struct {
pub const Node = struct {
prev: ?*Node,
next: ?*Node,
data: T,
};
first: ?*Node,
last: ?*Node,
len: usize,
};
}
test "linked list" {
// Functions called at compile-time are memoized. This means you can
// do this:
try expect(LinkedList(i32) == LinkedList(i32));
var list = LinkedList(i32) {
.first = null,
.last = null,
.len = 0,
};
try expect(list.len == 0);
// Since types are first class values you can instantiate the type
// by assigning it to a variable:
const ListOfInts = LinkedList(i32);
try expect(ListOfInts == LinkedList(i32));
var node = ListOfInts.Node {
.prev = null,
.next = null,
.data = 1234,
};
var list2 = LinkedList(i32) {
.first = &node,
.last = &node,
.len = 1,
};
try expect(list2.first.?.data == 1234);
}
union
定义了一组可能值的类型,一个值能作为一个字段的列表。
一次只能有一个字段被激活。
简单的 union 类型,不能直接转义内存的解释。
为此,请使用 @ptrCast,或者使用 extern union 或 packed union,他们能保证内存的布局
fn doAThing(str: []u8) void {
if (parseU64(str, 10)) |number| {
doSomethingWithNumber(number);
} else |err| switch (err) {
error.Overflow => {
// handle overflow...
},
// we promise that InvalidChar won't happen (or crash in debug mode if it does)
error.InvalidChar => unreachable,
}
}
errdefer
在返回错误的时候执行,这样让清理在错误的时候清理变得容易。
fn createFoo(param: i32) !Foo {
const foo = try tryToAllocateFoo();
// now we have allocated foo. we need to free it if the function fails.
// but we want to return it if the function succeeds.
errdefer deallocateFoo(foo);
const tmp_buf = allocateTmpBuffer() orelse return error.OutOfMemory;
// tmp_buf is truly a temporary resource, and we for sure want to clean it up
// before this block leaves scope
defer deallocateTmpBuffer(tmp_buf);
if (param > 1337) return error.InvalidParam;
// here the errdefer will not run since we're returning success from the function.
// but the defer will run!
return foo;
}
在编译期间获取 error union 类型
const expect = @import("std").testing.expect;
test "error union" {
var foo: anyerror!i32 = undefined;
// Coerce from child type of an error union:
foo = 1234;
// Coerce from an error set:
foo = error.SomeError;
// Use compile-time reflection to access the payload type of an error union:
comptime try expect(@typeInfo(@TypeOf(foo)).ErrorUnion.payload == i32);
// Use compile-time reflection to access the error set type of an error union:
comptime try expect(@typeInfo(@TypeOf(foo)).ErrorUnion.error_set == anyerror);
}
const expect = @import("std").testing.expect;
test "optional pointers" {
// Pointers cannot be null. If you want a null pointer, use the optional
// prefix `?` to make the pointer type optional.
var ptr: ?*i32 = null;
var x: i32 = 1;
ptr = &x;
try expect(ptr.?.* == 1);
// Optional pointers are the same size as normal pointers, because pointer
// value 0 is used as the null value.
try expect(@sizeOf(?*i32) == @sizeOf(*i32));
}
var optional_value: ?ur32 = null;
optioal_value = 42;
var x = some_function();
if (x) |value| {
// do something with value
}
// malloc prototype included for reference
extern fn malloc(size: size_t) ?*u8;
fn doAThing() ?*Foo {
const ptr = malloc(1234) orelse return null;
_ = ptr; // ...
}
在这里,Zig 至少和 C 一样方便,甚至更方便。
而且,"ptr"的类型是*u8 而不是?*u8。
orelse 关键字解开了可选类型,因此 ptr 被保证在函数中使用的任何地方都是非空的。
可能看到的另一种针对 NULL 的检查形式是这样的。
const Foo = struct{};
fn doSomethingWithFoo(foo: *Foo) void { _ = foo; }
fn doAThing(optional_foo: ?*Foo) void {
// do some stuff
if (optional_foo) |foo| {
doSomethingWithFoo(foo);
}
// do some stuff
}
Are you making a library? In this case, best to accept an *Allocator as a parameter and allow your library's users to decide what allocator to use.
是否链接到 libc? 如果是, std.heap.c_allocator 是好的选择,至少作为你的 main allocator
Is the maximum number of bytes that you will need bounded by a number known at comptime? In this case, use std.heap.FixedBufferAllocator or std.heap.ThreadSafeFixedBufferAllocator depending on whether you need thread-safety or not.
const Builder = @import("std").build.Builder;
pub fn build(b: *Builder) void {
// Standard target options allows the person running `zig build` to choose
// what target to build for. Here we do not override the defaults, which
// means any target is allowed, and the default is native. Other options
// for restricting supported target set are available.
const target = b.standardTargetOptions(.{});
// Standard release options allow the person running `zig build` to select
// between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall.
const mode = b.standardReleaseOptions();
const exe = b.addExecutable("example", "src/main.zig");
exe.setTarget(target);
exe.setBuildMode(mode);
exe.install();
const run_cmd = exe.run();
run_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| {
run_cmd.addArgs(args);
}
const run_step = b.step("run", "Run the app");
run_step.dependOn(&run_cmd.step);
}
zig init-lib
const Builder = @import("std").build.Builder;
pub fn build(b: *Builder) void {
const mode = b.standardReleaseOptions();
const lib = b.addStaticLibrary("example", "src/main.zig");
lib.setBuildMode(mode);
lib.install();
var main_tests = b.addTest("src/main.zig");
main_tests.setBuildMode(mode);
const test_step = b.step("test", "Run library tests");
test_step.dependOn(&main_tests.step);
const options = b.addOptions();
var shell = Shell.create(b.allocator) catch unreachable;
defer shell.destroy();
// The "tigerbeetle version" command includes the build-time commit hash.
options.addOption([]const u8, "git_commit", try shell.git_commit());
// 指定创建 vsr 的模块,模块依赖 vsr_options 的声明
// 代码来使用 @import("vsr_options");
const vsr_module = b.addModule("vsr", .{
.source_file = .{ .path = "src/vsr.zig" },
.dependencies = &.{
.{
.name = "vsr_options",
.module = vsr_options_module,
},
},
});
}
const build_options: BuildOptions = blk: {
if (@hasDecl(root, "vsr_options")) {
break :blk root.vsr_options;
} else {
const vsr_options = @import("vsr_options");
// Zig's `addOptions` reuses the type, but redeclares it — identical structurally,
// but a different type from a nominal typing perspective.
var result: BuildOptions = undefined;
// 指定相关类型和值
for (std.meta.fields(BuildOptions)) |field| {
// 指定 result 相关实例的值
@field(result, field.name) = launder_type(
field.type,
@field(vsr_options, field.name),
);
}
break :blk result;
}
// Compares two strings ignoring case (ascii strings only).
// Specialzied version where `uppr` is comptime known and *uppercase*.
fn insensitive_eql(comptime uppr: []const u8, str: []const u8) bool {
comptime {
var i = 0;
while (i < uppr.len) : (i += 1) {
if (uppr[i] >= 'a' and uppr[i] <= 'z') {
@compileError("`uppr` must be all uppercase");
}
}
}
var i = 0;
while (i < uppr.len) : (i += 1) {
const val = if (str[i] >= 'a' and str[i] <= 'z')
str[i] - 32
else
str[i];
if (val != uppr[i]) return false;
}
return true;
}
pub fn main() void {
const x = insensitive_eql("Hello", "hElLo");
}
执行 build
➜ zig build-exe ieq.zig
/Users/loriscro/ieq.zig:8:17: error: `uppr` must be all uppercase
@compileError("`uppr` must be all uppercase");
^
/Users/loriscro/ieq.zig:24:30: note: called from here
const x = insensitive_eql("Hello", "hElLo");
^
/// Compares two slices and returns whether they are equal.
pub fn eql(comptime T: type, a: []const T, b: []const T) bool {
if (a.len != b.len) return false;
for (a) |item, index| {
if (b[index] != item) return false;
}
return true;
}
也能对 type类型的值进行反射。
在前面的例子中,从用户输入中解析了一个整数,并要求一个特定类型的整数。
解析函数使用这一信息,从其通用实现中省略了一些代码。
// This is the line in `apply_ops` where we parsed a number
return fmt.parseInt(i64, user_input, 10);
// This is the stdlib implementation of `parseInt`
pub fn parseInt(comptime T: type, buf: []const u8, radix: u8) !T {
if (!T.is_signed) return parseUnsigned(T, buf, radix);
if (buf.len == 0) return T(0);
if (buf[0] == '-') {
return math.negate(try parseUnsigned(T, buf[1..], radix));
} else if (buf[0] == '+') {
return parseUnsigned(T, buf[1..], radix);
} else {
return parseUnsigned(T, buf, radix);
}
}
// To try this code, paste both definitions in the same file.
const PointList = LinkedList(Point);
const p = Point{ .x = 0, .y = 2, .z = 8 };
var my_list = PointList{};
// A complete implementation would offer an `append` method.
// For now let's add the new node manually.
var node = PointList.Node{ .data = p };
my_list.first = &node;
my_list.last = &node;
my_list.len = 1;
// I moved part of the original definition to
// a separate function for better readability.
fn decide_return_type(comptime T: type) type {
if (@typeId(T) == TypeId.Int) {
return @IntType(false, T.bit_count / 2);
} else {
return T;
}
}
pub fn sqrt(x: var) decide_return_type(@typeOf(x)) {
const T = @typeOf(x);
switch (@typeId(T)) {
TypeId.ComptimeFloat => return T(@sqrt(f64, x)),
TypeId.Float => return @sqrt(T, x),
TypeId.ComptimeInt => comptime {
if (x > maxInt(u128)) {
@compileError(
"sqrt not implemented for " ++
"comptime_int greater than 128 bits");
}
if (x < 0) {
@compileError("sqrt on negative number");
}
return T(sqrt_int(u128, x));
},
TypeId.Int => return sqrt_int(T, x),
else => @compileError("not implemented for " ++ @typeName(T)),
}
}
原来的代码实际上是内联了一个 if 表达式,但为了提高可读性,我把它移到了一个单独的函数中。
那么 sqrt 想用它的返回类型做什么呢?当传入一个整数值时,它正在应用一个小的优化。
在这种情况下,该函数将其返回类型声明为一个无符号整数,其位数是原始输入的一半。
这意味着,如果传入一个 i64 值,函数将返回一个 u32 值。
考虑到平方根函数的作用,这是有意义的。
然后声明的其余部分使用反射来进一步专门化,并在适当的时候报告编译时错误。
@hasDecl
返回一个 struct, enum 或 union 是否有与 name 相匹配的声明。
const std = @import("std");
const expect = std.testing.expect;
const Foo = struct {
nope: i32,
pub var blah = "xxx";
const hi = 1;
};
test "@hasDecl" {
try expect(@hasDecl(Foo, "blah"));
// Even though `hi` is private, @hasDecl returns true because this test is
// in the same file scope as Foo. It would return false if Foo was declared
// in a different file.
try expect(@hasDecl(Foo, "hi"));
// @hasDecl is for declarations; not fields.
try expect(!@hasDecl(Foo, "nope"));
try expect(!@hasDecl(Foo, "nope1234"));
}
通过使用 std.testing.refAllDecls 调用来引用文件中的所有容器
const std = @import("std");
const expect = std.testing.expect;
// Imported source file tests will run when referenced from a top-level test declaration.
// The next line alone does not cause "testing_introduction.zig" tests to run.
const imported_file = @import("testing_introduction.zig");
test {
// To run nested container 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;
_ = U;
_ = @import("testing_introduction.zig");
}
const S = struct {
test "S demo test" {
try expect(true);
}
const SE = enum {
V,
// This test won't run because its container (SE) is not referenced.
test "This Test Won't Run" {
try expect(false);
}
};
};
const U = union { // U is referenced by the file's top-level test declaration
s: US, // and US is referenced here; therefore, "U.Us demo 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);
}
};
资料
字符串
if
for
指针
*T
指向一个 item 的指针ptr.*
[*]T
指向包含多个 item 的地址,也就是C 语言中的首地址的概念,区分出指向数组还是指向单个元素ptr[i]
ptr[start..end]
ptr + x
,ptr - x
T
必须有已知的大小,which means that it cannot bec_void
or any other opaque type.[*]const u8 pointer to unknown number of immutable u8s
const [2]u8 pointer to *an immutable array of 2 u8
*[N]T
- pointer to N items, same as single-item pointer to an array.array_ptr[i]
array_ptr[start..end]
array_ptr.len
[2]u8 - array of 2 bytes
[]T - pointer to 运行才能知道个数的 item
slice[i]
slice[start..end]
slice.len
allocator.alloc 来创建多个对象
allocator.create 来创建一个对象
allocator.destroy 来销毁一个对象
allocator.free 来销毁多个对象
Function Parameter Type Inference
使用@TypeOf 和@typeInfo 来获取推断出的类型的信息
comptime
用来实现范型,comptime 修饰的类型必须在编译期间知晓
实现接口
slice
zig没有字符串的概念,string literals 是 const pointers to null-terminated array of u8
enum
extern enum
non-exhaustive enum
struct
union
定义了一组可能值的类型,一个值能作为一个字段的列表。
一次只能有一个字段被激活。
简单的 union 类型,不能直接转义内存的解释。
为此,请使用
@ptrCast
,或者使用 extern union 或 packed union,他们能保证内存的布局tagged union
union 能用 enum tag type 来声明。
这将 union 变成了一个 tagged union,这使得它有资格与 switch 表达式一起使用。
tagged union 与它们的 tag 相强制转换。
为了改变 tagged tag 的值,为了修改 switch 表达式中的 tag,在变量名称前放置一个*,使其成为一个指针。
union 能被用来推断 enum tag 类型。
此外,union 能像 struct 和 enum 一样拥有方法。
error set type
能将一个错误从子集强制到超集。
Error Union Type
注意返回类型前面有一个!表示是一个错误联合类型(error union type),这是常会使用的
catch
try
想在得到错误时返回错误,否则继续执行函数逻辑。
等价的表达
try 是计算一个 error union expression。
想对每种情况采取不同的行动。为此,结合 if 和 switch 表达式。
errdefer
在返回错误的时候执行,这样让清理在错误的时候清理变得容易。
在编译期间获取 error union 类型
Error Return Traces
实际上使用 try
合并错误类型
推断错误类型
optional
在类型中加入?表示可选类型。zig 没有optional 的引用。
optional pointers
能使用一个可选的指针。
这秘密地编译成一个普通的指针,因为知道能使用 0 作为可选的类型的空值。
但是编译器能检查你的工作,确保你没有把 null 赋值给不可能是 null 的东西。
通常情况下,没有 null 的缺点是,它使代码写得更冗长。
比较一下一些等价的 C 代码和 Zig 代码。
在这里,Zig 至少和 C 一样方便,甚至更方便。
而且,"ptr"的类型是*u8 而不是?*u8。
orelse 关键字解开了可选类型,因此 ptr 被保证在函数中使用的任何地方都是非空的。
可能看到的另一种针对 NULL 的检查形式是这样的。
memory
ziglang 需要用户自己定义内存分配器
分配内存器的规则
*Allocator
as a parameter and allow your library's users to decide what allocator to use.std.heap.c_allocator
是好的选择,至少作为你的 main allocatorstd.heap.FixedBufferAllocator
orstd.heap.ThreadSafeFixedBufferAllocator
depending on whether you need thread-safety or not.你的程序没有那种周期的任务,那么你的程序最好满足下面这种 pattern
std.testing.allocator
build.zig
zig init-exe 生成的 build.zig
zig init-lib
comptime
在编译期间的函数调用
COMPILE-TIME BLOCKS
编译时执行确保该函数不会被误用。
编译时期的代码消除
下面的程序要求用户提供一个数字,然后对其反复进行一系列的操作。
泛型
解析函数使用这一信息,从其通用实现中省略了一些代码。
通用的 struct
这是一个双重链表
LinkedList 如何与之前的 Point 结构进行组合。
编译时期的反射
当 parseInt 检查T.is_signed 时,已经看到了一个反射的例子,
下面的代码是 math.sqrt 的实现,在前面的例子中用它来计算两点之间的欧氏距离。
@hasDecl
返回一个 struct, enum 或 union 是否有与 name 相匹配的声明。
通过使用 std.testing.refAllDecls 调用来引用文件中的所有容器
定义函数指针
type/ziglang #public