肥仔教程网

SEO 优化与 Web 开发技术学习分享平台

Zig 0.12.0 Release Notes ——语言的变化

语言的变化 §

不必使用Var §

Zig 0.12.0 引入了一个新的编译错误,当局部变量被声明为 var时,编译器会推断出 const就足够了。

unnecessary_var.zig

const expectEqual = @import("std").testing.expectEqual;
test "unnecessary use of var" {
    var x: u32 = 123;
    try expectEqual(123, x);
}
$ zig test unnecessary_var.zig
docgen_tmp/unnecessary_var.zig:3:9: error: local variable is never mutated
    var x: u32 = 123;
        ^
docgen_tmp/unnecessary_var.zig:3:9: note: consider using 'const'

正如错误信息所示,解决方法很简单:在适当的地方使用 const代替。

结果定位语义(Result Location Semantics) §

Zig 0.12.0 包含对结果定位语义 (RLS) 的若干改进。

此版本通过寻址操作符 ( & ) 实现了结果类型的转发。这允许依赖于结果类型的语法结构,例如匿名初始化 .{ ... } 和像 @intCast 这样的铸造内置函数,都能在有寻址操作符的情况下正确运行:

address_of_rls.zig

const S = struct { x: u32 };
const int: u64 = 123;
const val: *const S = &.{ .x = @intCast(int) };
comptime {
    _ = val;
}
$ zig test address_of_rls.zig
All 0 tests passed.

此外,Zig 0.12.0 取消了通过 @as 和显式类型聚合初始化T{ ... } 传播结果定位的功能。 这一限制是为了简化语言设计:之前的版本包含了几个与结果指针的错误转换有关的 bug。

总体结构调整 §

Zig 0.12.0 引入了一种新语法,允许对可索引集合(即元组、向量和数组)进行重组。在赋值的左侧写入一串 lvalues 或局部变量声明将尝试重构右侧指定的值:

destructure.zig

const std = @import("std");
const assert = std.debug.assert;
const expectEqual = std.testing.expectEqual;
test "destructure array" {
    var z: u32 = undefined;
    const x, var y, z = [3]u32{ 1, 2, 3 };
    y += 10;
    try expectEqual(1, x);
    try expectEqual(12, y);
    try expectEqual(3, z);
}
test "destructure vector" {
    // Comptime-known values are propagated as you would expect.
    const x, const y = @Vector(2, u32){ 1, 2 };
    comptime assert(x == 1);
    comptime assert(y == 2);
}
test "destructure tuple" {
    var runtime: u32 = undefined;
    runtime = 123;
    const x, const y = .{ 42, runtime };
    // The first tuple field is a `comptime` field, so `x` is comptime-known even
    // though `y` is runtime-known.
    comptime assert(x == 42);
    try expectEqual(123, y);
}
$ zig test destructure.zig
1/3 destructure.test.destructure array... OK
2/3 destructure.test.destructure vector... OK
3/3 destructure.test.destructure tuple... OK
All 3 tests passed.

Slices 不能直接重组. 要从片段中重组值,可通过使用已知时间界限的片段将其转换为数组。例如 slice[ 0 .. 3 ].* .

命名空间类型 (Namespace Type) 等价 §

在 Zig 中,struct , enum , union , 和opaque类型都很特殊。它们不像元组和数组那样使用结构等价;相反,它们创建了不同的类型。这些类型有命名空间,因此可以包含声明。因此,它们可以统称为 "namespace types"。

在 0.11.0 中,每次对此类类型的声明进行语义分析时,都会创建一个新类型。通用类型的等价性是通过调用函数时的备忘录化来处理的;例如,std.ArrayList(u8) == std.ArrayList(u8) 之所以成立,是因为 ArrayList函数只被调用一次,其结果也被记录了。

在 0.12.0 中,这种情况有所改变。命名空间类型现在根据两个因素进行重复:源位置和捕获。

类型的 "captures(捕获) "指的是它所关闭的已知类型和值的集合。换句话说,它是在类型内部引用但在其外部声明的值的集合。例如,std.ArrayList 的 comptime T: type参数被其返回的类型所捕获。如果两个命名空间类型是由同一段代码声明的,并且具有相同的捕获,那么它们现在被认为是完全相同的类型。

请注意,编译器仍会对 comptime 调用进行 memoize:这一点没有改变。不过,这种记忆化已不再对语言语义产生有意义的影响。

这种更改不太可能导致现有代码的中断。最有可能出现以下情况:

opaque_generator.zig

fn MakeOpaque(comptime n: comptime_int) type {
    _ = n;
    return opaque {};
}
const A = MakeOpaque(0);
const B = MakeOpaque(1);

在 Zig 0.11.0 中,这段代码会创建两个不同的类型,因为对 MakeOpaque的调用是不同的,因此对每次调用都要分别分析opaque(不透明)声明。在 Zig 0.12.0 中,这些类型是相同的 ( A == B ),因为虽然函数被调用了两次,但声明并没有捕获任何值。

可以通过强制类型声明捕捉 n 来修正这段代码:

opaque_generator.zig

fn MakeOpaque(comptime n: comptime_int) type {
    return opaque {
        comptime {
            _ = n;
        }
    };
}
const A = MakeOpaque(0);
const B = MakeOpaque(1);

由于 n 在opaque声明中被引用,这段代码创建了两个不同的类型。

Comptime 内存改版§

Zig 0.12.0 全面修改了编译器对编译时间内存的内部表示,更具体地说,就是编译时间可变内存(comptime var)。这次大修还带来了一些面向用户的变更,比如对使用 comptime var 进行操作的新限制。

第一条,也是最重要的一条新规则是,绝不允许指向comptime var的指针成为运行时已知指针。例如,请看下面的代码段:

comptime_var_ptr_runtime.zig

test "runtime-known comptime var pointer" {
    comptime var x: u32 = 123;
    // `var` makes `ptr` runtime-known
    var ptr: *const u32 = undefined;
    ptr = &x;
    if (ptr.* != 123) return error.TestFailed;
}
$ zig test comptime_var_ptr_runtime.zig
docgen_tmp/comptime_var_ptr_runtime.zig:5:11: error: runtime value contains reference to comptime var
    ptr = &x;
          ^~
docgen_tmp/comptime_var_ptr_runtime.zig:5:11: note: comptime var pointers are not available at runtime

在以前的 Zig 版本中,这个测试如你所料通过了。而在 Zig 0.12.0 中,由于对 ptr的赋值会使 &x 这个指向comptime var变量的指针成为运行时已知值,因此会出现编译错误。

这些指针也可以在运行时成为已知指针,例如,可以传递给运行时调用的函数:

comptime_var_ptr_runtime_arg.zig

test "comptime var pointer as runtime argument" {
    comptime var x: u32 = 123;
    if (load(&x) != 123) return error.TestFailed;
}
fn load(ptr: *const u32) u32 {
    return ptr.*;
}
$ zig test comptime_var_ptr_runtime_arg.zig
docgen_tmp/comptime_var_ptr_runtime_arg.zig:3:14: error: runtime value contains reference to comptime var
    if (load(&x) != 123) return error.TestFailed;
             ^~
docgen_tmp/comptime_var_ptr_runtime_arg.zig:3:14: note: comptime var pointers are not available at runtime

在 Zig 0.12.0 中,该测试也会出现编译错误。对 load的调用发生在运行时,而它的 ptr参数没有标记为comptime ,因此 ptr 在 load 主体中是运行时已知的。这意味着调用 load 时指针 &x 是运行时已知的,因此会出现编译错误。

设置这一限制是为了修复一些稳健性错误。当指向comptime var的指针变成运行时已知时,由于指向的数据变成常量,对它的突变就会无效,但类型系统却无法反映这一点,从而导致在看似有效的代码中出现运行时分段错误的可能性。此外,在运行时从指针读取的值将是其 "final(最终) "的运行时值,这是一种不直观的行为。因此,这些指针在运行时不再是已知的。

第二个新限制是,全局声明的解析值中绝不允许包含指向 comptime var 的指针。例如,请看下面的代码段:

comptime_var_ptr_global.zig

const ptr: *const u32 = ptr: {
    var x: u32 = 123;
    break :ptr &x;
};
comptime {
    _ = ptr;
}
$ zig test comptime_var_ptr_global.zig
docgen_tmp/comptime_var_ptr_global.zig:1:30: error: global variable contains reference to comptime var
const ptr: *const u32 = ptr: {
                        ~~~~~^
referenced by:
    comptime_0: docgen_tmp/comptime_var_ptr_global.zig:6:9
    remaining reference traces hidden; use '-freference-trace' to see all reference traces

这里,ptr是一个全局声明,其值是指向 comptime var 的指针。这种声明在 Zig 0.11.0 中是允许的,但在 Zig 0.12.0 中会引发编译错误。同样的规则也适用于更复杂的情况,例如当指针包含在一个 struct 字段中时:

comptime_var_ptr_global_struct.zig

const S = struct { ptr: *const u32 };
const val: S = blk: {
    var x: u32 = 123;
    break :blk .{ .ptr = &x };
};
comptime {
    _ = val;
}
$ zig test comptime_var_ptr_global.zig
docgen_tmp/comptime_var_ptr_global.zig:1:30: error: global variable contains reference to comptime var
const ptr: *const u32 = ptr: {
                        ~~~~~^
referenced by:
    comptime_0: docgen_tmp/comptime_var_ptr_global.zig:6:9
    remaining reference traces hidden; use '-freference-trace' to see all reference traces

这段代码会产生与上例相同的编译错误。设置这一限制主要是为了帮助 Zig 编译器实现增量编译,因为增量编译依赖于全局声明的分析与顺序无关,而且声明之间的依赖关系可以很容易地建模。

在现有代码中,最常见的编译错误表现形式是函数在编译时构造了一个片段,然后在运行时使用了该片段。例如,请看下面的代码段:

construct_slice_comptime.zig

fn getName() []const u8 {
    comptime var buf: [9]u8 = undefined;
    // In practice there would likely be more complex logic here to populate `buf`.
    @memcpy(&buf, "some name");
    return &buf;
}
test getName {
    try @import("std").testing.expectEqualStrings("some name", getName());
}

Shell

$ zig test construct_slice_comptime.zig
docgen_tmp/construct_slice_comptime.zig:5:12: error: runtime value contains reference to comptime var
    return &buf;
           ^~~~
docgen_tmp/construct_slice_comptime.zig:5:12: note: comptime var pointers are not available at runtime
referenced by:
    decltest.getName: docgen_tmp/construct_slice_comptime.zig:8:64
    remaining reference traces hidden; use '-freference-trace' to see all reference traces

调用 getName 会返回一个slice ,其 ptr 字段是指向 comptime var的指针。这意味着该值不能在运行时使用,也不能出现在全局声明的值中。可以通过在填充缓冲区后将计算数据提升为const 来修复这段代码:

construct_slice_comptime.zig

fn getName() []const u8 {
    comptime var buf: [9]u8 = undefined;
    // In practice there would likely be more complex logic here to populate `buf`.
    @memcpy(&buf, "some name");
    const final_name = buf;
    return &final_name;
}
test getName {
    try @import("std").testing.expectEqualStrings("some name", getName());
}
$ zig test construct_slice_comptime.zig
1/1 construct_slice_comptime.decltest.getName... OK
All 1 tests passed.

与 Zig 以前的版本一样,comptime-known const 的生命周期是无限的,这里讨论的限制对它们并不适用。因此,这段代码可以正常运行。

另一种可能的失败模式出现在使用旧语义创建全局可变计算时间状态的代码中。例如,以下代码段试图创建一个全局comptime 计数器:

global_comptime_counter.zig

const counter: *u32 = counter: {
    var n: u32 = 0;
    break :counter &n;
};
comptime {
    counter.* += 1;
}
$ zig test global_comptime_counter.zig
docgen_tmp/global_comptime_counter.zig:1:32: error: global variable contains reference to comptime var
const counter: *u32 = counter: {
                      ~~~~~~~~~^
referenced by:
    comptime_0: docgen_tmp/global_comptime_counter.zig:6:5
    remaining reference traces hidden; use '-freference-trace' to see all reference traces

在 Zig 0.12.0 中,该代码会出现编译错误。Zig 现在和将来都不会支持这种用例:任何可变的comptime 状态都必须在本地表示。

@fieldParentPtr §

去掉第一个参数,使用结果类型 .

迁移指南:

const parent_ptr = @fieldParentPtr(Parent, "field_name", field_ptr);

const parent_ptr: *Parent = @fieldParentPtr("field_name", field_ptr);

const parent_ptr: *Parent = @alignCast(@fieldParentPtr("field_name", field_ptr));

取决于编译器能够证明父指针的对齐方式。第二种形式更具可移植性,因为某些目标可能需要 @alignCast,而另一些目标则不需要。

函数类型禁止对其 §

Zig 0.11.0 允许函数类型指定对齐方式。Zig 0.12.0 不允许这样做,因为这是函数声明和指针的属性,而不是函数类型的属性。

func_type_align.zig

comptime {
    _ = fn () align(4) void;
}
$ zig test func_type_align.zig
docgen_tmp/func_type_align.zig:2:21: error: function type cannot have an alignment
    _ = fn () align(4) void;

@errorCast §

以前的Zig版本包括一个内置的@errSetCast,它执行一个安全检查过的从一个错误集到另一个可能更小的错误集的强制转换。在Zig 0.12.0中,这个内置功能被@errSetCast取代。以前的使用将继续工作,但除此之外,这个新的内置功能可以强制转换错误联合的错误集:

error_cast.zig

const testing = @import("std").testing;

test "@errorCast error set" {
    const err: error{Foo, Bar} = error.Foo;
    const casted: error{Foo} = @errorCast(err);
    try testing.expectEqual(error.Foo, casted);
}

test "@errorCast error union" {
    const err: error{Foo, Bar}!u32 = error.Foo;
    const casted: error{Foo}!u32 = @errorCast(err);
    try testing.expectError(error.Foo, casted);
}

test "@errorCast error union payload" {
    const err: error{Foo, Bar}!u32 = 123;
    const casted: error{Foo}!u32 = @errorCast(err);
    try testing.expectEqual(123, casted);
}

Shell

$ zig test error_cast.zig
1/3 error_cast.test.@errorCast error set... OK
2/3 error_cast.test.@errorCast error union... OK
3/3 error_cast.test.@errorCast error union payload... OK
All 3 tests passed.

@abs §

以前的Zig版本包含了@fabs内置。这已经被一个新的内置@abs所取代,它能够对整数和浮点数进行操作:

abs.zig

const expectEqual = @import("std").testing.expectEqual;

test "@abs on float" {
    const x: f32 = -123.5;
    const y = @abs(x);
    try expectEqual(123.5, y);
}

test "@abs on int" {
    const x: i32 = -12345;
    const y = @abs(x);
    try expectEqual(12345, y);
}
$ zig test abs.zig
1/2 abs.test.@abs on float... OK
2/2 abs.test.@abs on int... OK
All 2 tests passed.
控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言