我对 Zig 很陌生(通过其中的 Advent Of Code 进行工作),并且我对它作为函数参数和返回类型处理字符串(或者,我应该说,
[]u8
)感到非常困惑。
TL;DR 以下函数的正确实现是什么?
fn doIt(string: []u8) []u8 {
return "prefix" ++ string;
}
我希望通过以下测试:
fn doIt(string: []u8) []u8 {
return "prefix" ++ string;
}
const expect = @import("std").testing.expect;
test {
try expect(std.mem.eql(u8, doIt("foo"), "prefixfoo"));
}
但是
zig test
给出:
scratch.zig:27:33: error: expected type '[]u8', found '*const [3:0]u8'
try expect(std.mem.eql(u8, doIt("foo"), "prefixfoo"));
^~~~~
scratch.zig:27:33: note: cast discards const qualifier
scratch.zig:20:17: note: parameter type declared here
fn doIt(string: []u8) []u8 {
好的,所以错误消息似乎很清楚 - 我需要更改函数的签名以接受指针。我不知道为什么我不允许直接传递~~字符串~~
[]u8
-文字,但让我们相信编译器并尝试一下:
// Let's not worry, for the moment, about the fact that we're writing a function which
// can only accept strings of length 3...
fn doIt(string: *const [3:0]u8) []u8 {
return "prefix" ++ string;
}
...
给予
scratch.zig:23:21: error: expected type '[]u8', found '*const [9:0]u8'
return "prefix" ++ string;
~~~~~~~~~^~~~~~~~~
scratch.zig:23:21: note: cast discards const qualifier
scratch.zig:20:33: note: function return type declared here
fn doIt(string: *const [3:0]u8) []u8 {
^~~~
好的,两个数组指针相加得到一个指向结果的指针。这是有道理的。我一开始就不想处理指针 - 但由于我被迫进入“指针领域”,我可以理解操作的输出也将是一个指针。所以,大概我们只是使用
.*
(又名指针解引用)来返回实际值(a []u8
),然后呢?
fn doIt(string: *const [3:0]u8) []u8 {
return ("prefix" ++ string).*;
}
给予
scratch.zig:21:32: error: array literal requires address-of operator (&) to coerce to slice type '[]u8'
return ("prefix" ++ string).*;
...取址运算符如何将指针强制转换为对象?这不是该运算符所做的的逆吗?但是好吧,让我们尝试一下...
fn doIt(string: *const [3:0]u8) []u8 {
return &("prefix" ++ string).*;
}
scratch.zig:21:12: error: expected type '[]u8', found '*const [9:0]u8'
return &("prefix" ++ string).*;
^~~~~~~~~~~~~~~~~~~~~~~
scratch.zig:21:12: note: cast discards const qualifier
scratch.zig:20:33: note: function return type declared here
fn doIt(string: *const [3:0]u8) []u8 {
...我放弃了,我一定误会了什么。谁能指出(啊哈)我正确的方向吗?
采取不同的策略,如果我们将函数的返回类型更改为指针,那么前面仍然存在问题:
fn doIt(string: *const [3:0]u8) *[]u8 {
return "prefix" ++ string;
}
const expect = @import("std").testing.expect;
test {
try expect(std.mem.eql(u8, doIt("foo"), "prefixfoo"));
}
scratch.zig:21:21: error: expected type '*[]u8', found '*const [9:0]u8'
return "prefix" ++ string;
~~~~~~~~~^~~~~~~~~
scratch.zig:21:21: note: cast discards const qualifier
scratch.zig:20:33: note: function return type declared here
fn doIt(string: *const [3:0]u8) *[]u8 {
^~~~~
scratch.zig:27:32: error: expected type '[]const u8', found '*[]u8'
try expect(std.mem.eql(u8, doIt("foo"), "prefixfoo"));
~~~~^~~~~~~
/Users/scubbo/zig/zig-macos-x86_64-0.14.0-dev.2362+a47aa9dd9/lib/std/mem.zig:658:33: note: parameter type declared here
pub fn eql(comptime T: type, a: []const T, b: []const T) bool {
编译器建议我应该设置函数的返回类型
*const [9:0]u8
。经过一点调整......仍然失败,以一种更令人惊讶的方式:
fn doIt(string: *const [3:0]u8) *const [9:0]u8 {
return "prefix" ++ string;
}
const expect = @import("std").testing.expect;
test {
for (doIt("foo")) |char| {print("{c}", .{char});}
print("\n", .{});
for ("prefixfoo") |char| {print("{c}", .{char});}
print("\n", .{});
try expect(std.mem.eql(u8, doIt("foo"), "prefixfoo"));
}
pefixfoo
prefixfoo
1/1 scratch.test_0...FAIL (TestUnexpectedResult)
/Users/scubbo/zig/zig-macos-x86_64-0.14.0-dev.2362+a47aa9dd9/lib/std/testing.zig:546:14: 0x10846a78f in expect (test)
if (!ok) return error.TestUnexpectedResult;
^
/Users/scubbo/Code/advent-of-code-2024/scratch.zig:31:5: 0x10846a936 in test_0 (test)
try expect(std.mem.eql(u8, doIt("foo"), "prefixfoo"));
^
0 passed; 0 skipped; 1 failed.
error: the following test command failed with exit code 1:
/Users/scubbo/.cache/zig/o/1bb299b096246ee4dc2c6057c3d21f46/test --seed=0xc38b771a
这不是打字错误或复制粘贴错误。
return "prefix" ++ string;
输出的逐字符打印是pefixfoo
。如果我错误地调整了数组的大小(不过,请参阅下一节),或者第一个字符由于某种原因而被删除,我也许可以理解最后一个字符会以某种方式被删除,但是什么可以使第二个角色被丢弃?
并且不考虑这样一个事实,即
(string: *const [3:0]u8) *const[9:0]u8
的函数签名可能无法接受长度为 4 的字符串。几乎不是一个多用途函数!
我查阅过一些链接来尝试理解:
[]u8
更改为 []const u8
,但没有尝试返回字符串运算符仅适用于具有 comptime 已知大小的数组。但您显然希望该函数在运行时完全可用。 这意味着您需要能够回答以下问题:您的函数从哪里获取新字符串的
内存?通常,该函数将采用分配器,例如:
fn doIt(allocator: std.mem.Allocator, string: []const u8) ![]u8 {
const prefix = "prefix";
const new_string = try allocator.alloc(u8, prefix.len + string.len);
@memcpy(new_string[0..prefix.len], prefix);
@memcpy(new_string[prefix.len..], string);
return new_string;
}
使用完新字符串后,您需要释放它。