ratfactor/ziglings

Strange Failure On Exercise 054

Closed this issue · 6 comments

I recently updated my zig version to 0.11.0-dev.1540+304420b99

When going back to work on Ziglings, I'm getting a failure on an exercise that passed previously.

Here's the code for the exercise.

const std = @import("std");

pub fn main() void {
    const zen12: *const [21]u8 = "Memory is a resource.";
    const zen_manyptr: [*]const u8 = zen12;
    const zen12_string: []const u8 = zen_manyptr[0..22];
    std.debug.print("{s}", .{zen12_string});
}

And here's the error.

Compiling 054_manypointers.zig...
Checking 054_manypointers.zig...

----------- Expected this output -----------
"Memory is a resource."
----------- but found -----------
"Memory is a resource."
-----------

Edit exercises/054_manypointers.zig and run this again.
To continue from this zigling, use this command:
    zig build 54

your slice range is slightly off [0..22]. Think about how many values that will be. I also made the same mistake :)

But the output is wrong. It is not possible to see what the error is. This goes along with another supposed improvement, and I will correct that. Thank you, therefore, for bringing it to our attention.

I just ran into this same error and after playing around I think I understand what's happening ... but I'm not sure.

The syntax for creating slices a[x..y] is supposed to return a slice where the elements are those at position x all the way up to y-1.

In this case, I would expect 0..22 to result in a slice consisting of elements at indexes 0 through 21 (where the element at position 21 is the period) -- so, the expected string.

edit: sigh, see below. something something off by one errors.

edit this isn't quite right but is still relevant-ish to the error confusion, I think.

The only thing that I can think of is that using the slice syntax on a many-item pointer behaves like the ... range syntax instead (i.e., it's inclusive of the end), and so it is returning the null byte at the end of the string?

that using the slice syntax on a many-item pointer behaves like the ... range syntax instead ...

Do you mean something like a[0..]?

and so it is returning the null byte at the end of the string?

But Zig generally has no null byte at the end of a string. Because the length is already enough.

I edited my above comment, I take back what I said about the range syntax. As it turns out, the problem existed between the keyboard and the chair...not sure where I got a length of 22 from.

Anyways:

But Zig generally has no null byte at the end of a string. Because the length is already enough.

This is counter to what I'm led to believe in 077_sentinels2.zig, namely:

//
// 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!

So, what I was thinking originally by mentioning the null byte is that somehow it was getting returned as part of the output (which would make sense given how the printed error seems to be saying that, for the expected output x, x != x).

Here's an example I put together:

Example Code
const std = @import("std");

pub fn main() void {
    // string literal
    const zen12: *const [21]u8 = "Memory is a resource.";
    // turn it into a many-item pointer
    const zen_manyptr: [*]const u8 = zen12;

    std.debug.print("\nx = 0, y = 4\n", .{});
    std.debug.print("expected: Memo\n", .{});
    std.debug.print("actual: {s}\n\n", .{zen_manyptr[0..4]});

    std.debug.print("x = 0, y = 15\n", .{});
    std.debug.print("expected: Memory is a res\n", .{});
    std.debug.print("actual: {s}\n\n", .{zen_manyptr[0..15]});

    std.debug.print("x = 0, y = 20\n", .{});
    std.debug.print("expected: Memory is a resource\n", .{});
    std.debug.print("actual: {s}\n\n", .{zen_manyptr[0..20]});

    std.debug.print("x = 0, y = 21\n", .{});
    std.debug.print("expected: Memory is a resource.\n", .{});
    std.debug.print("actual: {s}\n\n", .{zen_manyptr[0..21]});

    std.debug.print("x = 0, y = 22\n", .{});
    std.debug.print("expected: Memory is a resource.<???>\n", .{});
    // actual is the "same" as above from the perspective of the user reading the terminal
    // I _think_ this is because it's including the null byte.
    std.debug.print("actual: {s}\n\n", .{zen_manyptr[0..22]});

    std.debug.print("x = 0, y = 23\n", .{});
    // I would expect garbage to start getting printed now, it does
    std.debug.print("expected: Memory is a resource.<???>\n", .{});
    std.debug.print("actual: {s}\n\n", .{zen_manyptr[0..23]});
}

In any case, I think the crux of the issue is that when the user is silly like me, puts a number off by 1, and gets an error like this:

========= expected this output: ==========
Memory is a resource.
========= but found: =====================
Memory is a resource.
==========================================

Edit exercises/054_manypointers.zig and run 'zig build -Dn=54' again.

there isn't a ton of information to go off of to try to figure out what they actually did wrong. The only reason why I thought about the possibility of a null byte is because I've gone through ziglings in the past and remembered that little tidbit about string literals...and if this isn't what's going on, I'd really like to understand what is (and maybe help come up with a more helpful error)

From https://ziglang.org/documentation/master/#String-Literals-and-Unicode-Code-Point-Literals:
"String literals are constant single-item Pointers to null-terminated byte arrays".

To use a more general term, they are sentinel terminated pointers.

This means that accessing the element after the last is valid; and it is valid even in case when a string literal is casted to a normal slice.

As for the output check, I agree that is not very useful in same rare cases.
I plan to improve it by showing invisible characters (like NUL) and trailing whitespace.