diff --git a/build.zig b/build.zig index 2489e2a..391da47 100644 --- a/build.zig +++ b/build.zig @@ -381,7 +381,15 @@ const exercises = [_]Exercise{ }, .{ .main_file = "076_sentinels.zig", - .output = "Array:123056. Many-pointer:123.", + .output = "Array:123056. Many-item pointer:123.", + }, + .{ + .main_file = "077_sentinels2.zig", + .output = "Weird Data!", + }, + .{ + .main_file = "078_sentinels3.zig", + .output = "Weird Data!", }, }; diff --git a/exercises/076_sentinels.zig b/exercises/076_sentinels.zig index 4cd25e9..4c16e7c 100644 --- a/exercises/076_sentinels.zig +++ b/exercises/076_sentinels.zig @@ -29,11 +29,11 @@ // Slice 'b' is only allowed to point to zero-terminated arrays // but otherwise works just like a normal slice. // -// Pointer 'c' is exactly like the many-pointers we learned about -// in exercise 054, but it is guaranteed to end in 0. Because of -// this guarantee, we can safely find the end of this -// many-pointer without knowing its length. (We CAN'T do that -// with regular many-pointers!). +// Pointer 'c' is exactly like the many-item pointers we learned +// about in exercise 054, but it is guaranteed to end in 0. +// Because of this guarantee, we can safely find the end of this +// many-item pointer without knowing its length. (We CAN'T do +// that with regular many-item pointers!). // const print = @import("std").debug.print; @@ -41,24 +41,25 @@ pub fn main() void { // Here's a zero-terminated array of u32 values: var nums = [_:0]u32{ 1, 2, 3, 4, 5, 6 }; - // And here's a zero-terminated many-pointer: + // And here's a zero-terminated many-item pointer: var ptr: [*:0]u32 = &nums; // For fun, let's replace the value at position 3 with the // sentinel value 0. This seems kind of naughty. nums[3] = 0; - // So now we have a zero-terminated array and a many-pointer - // that reference the same data: a sequence of numbers that - // both ends in and CONTAINS the sentinal value. + // So now we have a zero-terminated array and a many-item + // pointer that reference the same data: a sequence of + // numbers that both ends in and CONTAINS the sentinal value. // // Attempting to loop through and print both of these should // demonstrate how they are similar and different. // // (It turns out that the array prints completely, including - // the sentinel 0 in the middle. The many-pointer must stop - // at the first sentinel value. The difference is simply that - // arrays have a known length and many-pointers don't.) + // the sentinel 0 in the middle. The many-item pointer must + // stop at the first sentinel value. The difference is simply + // that arrays have a known length and many-item pointers + // don't.) printSequence(nums); printSequence(ptr); @@ -86,7 +87,7 @@ fn printSequence(my_seq: anytype) void { .Pointer => { // Check this out - it's pretty cool: const my_sentinel = my_type.Pointer.sentinel; - print("Many-pointer:", .{}); + print("Many-item pointer:", .{}); // Loop through the items in my_seq until we hit the // sentinel value. @@ -100,22 +101,3 @@ fn printSequence(my_seq: anytype) void { } print(". ", .{}); } -// -// ------------------------------------------------------------ -// TOP SECRET TOP SECRET TOP SECRET TOP SECRET TOP SECRET -// ------------------------------------------------------------ -// -// Are you ready for the THE TRUTH about Zig string literals? -// -// You've earned it. Here it is: -// -// @TypeOf("foo") == *const [3:0]u8 -// -// Zig's string literals are constant pointers to zero-terminated -// (or "null-terminated") arrays of u8. -// -// Now you know. Welcome to the secret club! -// -// ------------------------------------------------------------ -// TOP SECRET TOP SECRET TOP SECRET TOP SECRET TOP SECRET -// ------------------------------------------------------------ diff --git a/exercises/077_sentinels2.zig b/exercises/077_sentinels2.zig new file mode 100644 index 0000000..7a03dcd --- /dev/null +++ b/exercises/077_sentinels2.zig @@ -0,0 +1,66 @@ +// +// ------------------------------------------------------------ +// TOP SECRET TOP SECRET TOP SECRET TOP SECRET TOP SECRET +// ------------------------------------------------------------ +// +// Are you ready for the THE TRUTH about Zig string literals? +// +// Here it is: +// +// @TypeOf("foo") == *const [3:0]u8 +// +// Which means a string literal is a "constant pointer to a +// zero-terminated (null-terminated) fixed-size array of u8". +// +// Now you know. You've earned it. Welcome to the secret club! +// +// ------------------------------------------------------------ +// +// Why do we bother using a zero/null sentinel to terminate +// strings in Zig when we already have a known length? +// +// Versatility! Zig strings are compatible with C strings (which +// are null-terminated) AND can be coerced to a variety of other +// Zig types: +// +// const a: [5]u8 = "array".*; +// const b: *const [16]u8 = "pointer to array"; +// const c: []const u8 = "slice"; +// const d: [:0]const u8 = "slice with sentinel"; +// const e: [*:0]const u8 = "many-item pointer with sentinel"; +// const f: [*]const u8 = "many-item pointer"; +// +// All but 'f' may be printed. (A many-item pointer without a +// sentinel is not safe to print because we don't know where it +// ends!) +// +const print = @import("std").debug.print; + +const WeirdContainer = struct { + data: [*]const u8, + length: usize, +}; + +pub fn main() void { + // WeirdContainer is an awkward way to house a string. + // + // Being a many-item pointer (with no sentinel termination), + // the 'data' field "loses" the length information AND the + // sentinel termination of the string literal "Weird Data!". + // + // Luckily, the 'length' field makes it possible to still + // work with this value. + const foo = WeirdContainer { + .data = "Weird Data!", + .length = 11, + }; + + // How do we get a printable value from 'foo'? One way is to + // turn it into something with a known length. We do have a + // length... You've actually solved this problem before! + // + // Here's a big hint: do you remember how to take a slice? + const printable = ???; + + print("{s}\n", .{printable}); +} diff --git a/exercises/078_sentinels3.zig b/exercises/078_sentinels3.zig new file mode 100644 index 0000000..bad4810 --- /dev/null +++ b/exercises/078_sentinels3.zig @@ -0,0 +1,27 @@ +// +// We were able to get a printable string out of a many-item +// pointer by using a slice to assert a specific length. +// +// But can we ever GO BACK to a sentinel-terminated pointer +// after we've "lost" the sentinel in a coercion? +// +// Yes, we can. Zig's @ptrCast() builtin can do this. Check out +// the signature: +// +// @ptrCast(comptime DestType: type, value: anytype) DestType +// +// See if you can use it to solve the same many-item pointer +// problem, but without needing a length! +// +const print = @import("std").debug.print; + +pub fn main() void { + // Again, we've coerced the sentinel-terminated string to a + // many-item pointer, which has no length or sentinel. + const data: [*]const u8 = "Weird Data!"; + + // Please cast 'data' to 'printable': + const printable: [*:0]const u8 = ???; + + print("{s}\n", .{printable}); +} diff --git a/patches/patches/076_sentinels.patch b/patches/patches/076_sentinels.patch index 52a5424..33e4483 100644 --- a/patches/patches/076_sentinels.patch +++ b/patches/patches/076_sentinels.patch @@ -1,8 +1,8 @@ -82c82 +83c83 < for (???) |s| { --- > for (my_seq) |s| { -94c94 +95c95 < while (??? != my_sentinel) { --- > while (my_seq[i] != my_sentinel) { diff --git a/patches/patches/077_sentinels2.patch b/patches/patches/077_sentinels2.patch new file mode 100644 index 0000000..4fef677 --- /dev/null +++ b/patches/patches/077_sentinels2.patch @@ -0,0 +1,4 @@ +63c63 +< const printable = ???; +--- +> const printable = foo.data[0..foo.length]; diff --git a/patches/patches/078_sentinels3.patch b/patches/patches/078_sentinels3.patch new file mode 100644 index 0000000..94257b0 --- /dev/null +++ b/patches/patches/078_sentinels3.patch @@ -0,0 +1,4 @@ +24c24 +< const printable: [*:0]const u8 = ???; +--- +> const printable: [*:0]const u8 = @ptrCast([*:0]const u8, data);