#annoyed at a ziglings problem that seems to rely on .?

1 messages · Page 1 of 1 (latest)

olive jasper
#

heya, so, in exercise 058 "quiz7", you're tasked to fix the following function:

    // found, we return null.
    fn getEntry(self: *HermitsNotebook, place: *const Place) ?*NotebookEntry {
        for (&self.entries, 0..) |*entry, i| {
            if (i >= self.end_of_entries) break;

            // Here's where the hermit got stuck. We need to return
            // an optional pointer to a NotebookEntry.
            //
            // What we have with "entry" is the opposite: a pointer to
            // an optional NotebookEntry!
            //
            // To get one from the other, we need to dereference
            // "entry" (with .*) and get the non-null value from the
            // optional (with .?) and return the address of that. The
            // if statement provides some clues about how the
            // dereference and optional value "unwrapping" look
            // together. Remember that you return the address with the
            // "&" operator.
            if (place == entry.*.?.place) return entry;
            // Try to make your answer this long:__________;
        }
        return null;
    }```

as discussed in a previous question, ".?" seems to kinda suck, but i can't think of a way to do this without it?
the issue appears to be that you need to return an optional pointer to an item in an array, but i don't understand how exactly to Un and Re wrap the value in such a way
if i try doing like, `if (entry) |nonNullEntry| { //do stuff with nonNullEntry, return it if it matches the criteria}` then i end up passing back a Const NotebookEntry, which i can only imagine is because the capture value is a new scoped const that's copied by value instead of reference or something?

either way, please do advise, i'm finding going in and messing with these functions to be useful to learn, even if i sometimes get confused like this.
#

(lmk if more context is neeed, like the NotebookEntry struct or w/e)

crisp bloom
#

did you try capturing a pointer? i.e. if (entry) |*nonNullEntry|? and you could also do orelse unreachable if it's actually appropriate here

#

idk I haven't done ziglings

winter cedar
#

yes, the way to avoid .? here is indeed by a capture-by-pointer:```zig
if (entry) |*e| if (e.place == place) return e;

olive jasper
#

trying that gets me

crisp bloom
#

ah it's already a pointer

#

deref first

winter cedar
olive jasper
#

huh, i suppose that makes sense

winter cedar
#

capture-by-pointer semantics are a bit weird if you think about them deeply enough, there's an accepted proposal to change them somewhat

olive jasper
#

this is my current attempt

        for (&self.entries) |*entry| {
            if (entry.*) |*nonNullEntry| {
                if (nonNullEntry.place == place) {
                    return entry;
                }
            }
        }
        return null;
  }```

but that gives me a 
```Compiling 058_quiz7.zig...
Checking 058_quiz7.zig...
exercises/058_quiz7.zig:244:13: error: expected type '?*T', found '*?T'
     return entry;
            ^~~~~
exercises/058_quiz7.zig:244:13: note: T = 058_quiz7.NotebookEntry
exercises/058_quiz7.zig:244:13: note: pointer type child '?058_quiz7.NotebookEntry' cannot cast into pointer type child '058_quiz7.NotebookEntry'
exercises/058_quiz7.zig:207:23: note: struct declared here
const NotebookEntry = struct {
                      ^~~~~~
exercises/058_quiz7.zig:240:62: note: function return type declared here
    fn getEntry(self: *HermitsNotebook, place: *const Place) ?*NotebookEntry {
                                                             ^~~~~~~~~~~~~~~
referenced by:
    getTripTo: exercises/058_quiz7.zig:301:48
    main: exercises/058_quiz7.zig:401:23
    5 reference(s) hidden; use '-freference-trace=7' to see all references


Ziglings hint: This is the biggest program we've seen yet. But you can do it!```
winter cedar
olive jasper
#

ahhh, good catch, cheers

#

and that works, huh. i suppose i was just confused by the capture-by-pointer semantics?

winter cedar
#

yes; they're kinda weird

#

they make you think you're iffing over a value, but it's actually a pointer, and it's very messy

winter cedar
olive jasper
#

ah, as far as i can tell end_of_entries exists only for this function, but i'll check and see if anything else in the exercise uses it

#

ah nah it's used a few more places

winter cedar
#

I'm looking at the exercise right now - you mght get the most of it by attempting to remove all usages of .? :p

olive jasper
#
    // Remember the array repetition function @splat()? It is a great way
    // to assign multiple items in an array without having to list them
    // one by one. Here we use it to initialize an array with null values.
    entries: [place_count]?NotebookEntry = @splat(null),

    // The next entry keeps track of where we are in our "todo" list.
    next_entry: u8 = 0,

    // Mark the start of empty space in the notebook.
    end_of_entries: u8 = 0,
#

oh gods, worth a try to get used to it lol

#

oh man it uses it a LOT

olive jasper
#

alright, i've gotten the program to use no ".?" 's, but there's a few rough spots in it

#

mostly asking about my two todo's

#

for fn getNextEntry(self: *HermitsNotebook) *const NotebookEntry { defer self.next_entry += 1; // Increment after getting entry if (self.entries[self.next_entry]) |*capture| { return capture; } else { unreachable; //TODO: try and fix this } }

the main thing that i am trying to figure out is what to return if the capture fails, i suppose an error, and then rework anywhere that uses getNextEntry() to handle errors too?

and for

            var comingFrom: *const Place = undefined;                             //TODO: name this something better, and see if there's a better way to do it
            if (current_entry.coming_from) |nonNull| {
                comingFrom = nonNull;
            } else {
                return TripError.EatenByAGrue;
            } ```
it's mostly just, i mean, this works, but it feels a little odd
#

what are smart peoples thoughts?

dull lily
#

A common way to define "get next" functions is to merge the hasNext and getNext functions into one "get next if we have it":

fn getNextEntry(self: *HermitsNotebook) ?*const NotebookEntry {
    if (!self.hasNextEntry()) return null;
    defer self.next_entry += 1;
    if (self.entries[self.next_entry]) |*capture| {
        return capture;
    } else {
        // This is now a valid assertion.
        // We returned above if there was not a next entry.
        unreachable;
    }
}

Then, the while loop in the main function can go from this

while (notebook.hasNextEntry()) {
    const place_entry = notebook.getNextEntry();
    // ...

to this

while (notebook.getNextEntry()) |place_entry| {
    // ...

Zig calls functions that return ?T until there is no more "next item" iterators, and the special while (opt) |value| syntax that captures an optional is there to make calling an iterator to exhaustion idiomatic

https://ziglang.org/documentation/master/#while-with-Optionals

olive jasper
#

oh, sweet, thanks!