0.15.1 Reader/Writer

Now I’m even more confused.

I’m typing on my phone, so I don’t want to type long source code and cannot test, but what I meant was to use

var reader = &file.reader(&buf).interface;

And then use reader

while (reader.takeDelimiterExclusive('\n')) |line| {
        try e.insertRow(e.buffer.rows.items.len, line);
    } else |err| if (err != error.EndOfStream) return err;
}

Andrew said that copying the interface caused the issue.
But with my tiny change I avoid exactly that, so why shouldn’t this work?
Btw I never said anything about the Writer.

I think the File.Reader returned by reader(&buf) doesn’t live past that line. You need to store the file reader in a variable and then take the address of the field, rather than copy the field then take the address of it, which is what that line does.

1 Like

Just tried it, but it doesn’t work because:

src/Editor.zig|163 col 18| error: expected type '*Io.Reader', found '*const Io.Reader'

just like @Sze said before.

1 Like

I meant reader, my mistake, but it is pretty much the same situation.

The key insight you’re missing is that reader() creates a thing that needs to live somewhere.

var file_reader = file.reader(&buf); // this var needs the same lifetime as `buf`
const reader = &file_reader.interface; // `reader` not reassigned, so use `const`
4 Likes

Underscores are just wannabe private fields. Zig doesn’t have private fields, and you don’t need to pretend Zig has private fields. Underscores in Zig code are a dead giveaway of code written by newbies. No need for underscores, no need for helper methods - it’s not that hard to use the interface correctly.

If you have a hard time understanding this stuff, please take a few moments to read the source code and understand the reason behind why things work this way. It all comes down to simplicity. Other languages hide complex details from you; Zig keeps things simpler but in exchange requires you to understand those details.

6 Likes

Thanks and I agree. But I think that at least some better documentation for these cases is needed, I don’t think I’m the only one who fell for it. At least just by reading the documentation you’d know what’s safe to assign and what not. As far as I could understand, things that have @fieldParentPtr called on them are not safe to assign. How should I know if they do somewhere, it if it’s not documented? Ok peace.

6 Likes

rather, things with @fieldParentPointer require the parent to continue to exist, you can assign the interface wherever you want as long as the parent is still valid.

Thanks, I think I understand it now.
Tbh, only because I worked through “Crafting Interpreters” and getting my hands dirty by implementing a language from scratch.

Still, as an application developer, it feels natural to not use a local variable for the reader, when all I’m interested in is one of its attributes, in this case, “interface”. This could even be called best practice (I don’t have a strong opinion about this).

If what feels natural is actually wrong, that’s a big footgun.

My feeling is that it’s somehow against the philosophy of the language of making it easy to write correct code.

Maybe the word “interface” should ring the alarm bell, when found in a Zig program: “Don’t use it without a dot in front of it, and never copy it, not even its address”.

Maybe a linter or the complier should emit a warning when it finds this kind of usage.

7 Likes

I want to chime in here for a second:

The fact that you can’t treat a std.Io.Reader or std.Io.Writer like a std.mem.Allocator (or what it seems like the upcoming std.Io interface), but must treat them as pointers, is pretty much asking for troubles down the road, especially for users.

Evolution gave our brains a (too) good ability at detecting patterns and dealing with them, a not really good ability to deal with situations, where patterns don’t exist, and a really bad ability to deal with cases of fake patterns (things which look like they should be patterns, but actually don’t). And this is pretty much a case of a fake pattern (it’s in interface but you can’t treat it like a well established one).

2 Likes

I think the solution to this problem is what was already referenced above:


Zig has many kinds of interfaces, only one kind of interface is simply not a thing in Zig, at least not in status-quo.

So a Zig programmer has to get familiar with the different kinds of interfaces, how they are used and what tradeoffs they have. However if the referenced issue is implemented, then users will get safety check errors when they misuse this kind of interface.

(To me that seems like the best way forward, besides simply explaining to people that the Io.Reader/Writer field can’t be moved independently from the surrounding concrete implementation, because the pointer to this field is used to get back to the surrounding struct, which is an error if that struct isn’t there anymore)

2 Likes

This restriction also applies to Allocator the difference is Allocator needs to be given a mutable pointer to the implementation state.

There are multiple proposals that could enforce this restriction, I think they have been linked earlier.

The pattern isn’t fake, rather there are different patterns that stem from different styles of interfaces.

The struggles stem from lack of knowledge of other patterns and/or unfamiliarity with the new interfaces, which is understandable as most are not keeping up with master.

8 posts were split to a new topic: 0.15.1 Changes

A post was merged into an existing topic: 0.15.1 Changes

A post was merged into an existing topic: 0.15.1 Changes

The pattern I mean here is called: interface.

Maybe you have heard about it, but in case you don’t: Evolution gave us humans a brain which is so good at detecting patterns, that we find them where they don’t exist.

This is what I call a “fake pattern”. I can look like a pattern from a glance, but isn’t one. And these can be incredibly dangerous (as you can hopefully imagine).

This is what I try to get at here. From a glance it looks like you should be able to treat all of these interfaces the same, but you can’t for various reasons.

This comes off as sarcastic, which isn’t helpful.

FWIW, this style of interface where the vtable is stored in a field of the implementation and a pointer to the interface is passed is actually how Allocator used to be implemented (AFAIU) until it was changed to the current version for performance reasons.

The conceptual pattern of an interface is the same, the way both of these interfaces are used differ because of the characteristics of what they do, which comes at a cost of a consistent usage pattern.

3 Likes

This discussion has come to a point where it’s more or less fruitless.

With the new I/O, a new foot gun was introduced and that’s a fact that can’t be ignored, as already some people lost a few toes.

So let’s not pretend it doesn’t exist.

OTOH every programming language has a few individual foot guns, so let’s not make a drama out of this one.

Design decisions have their pros and cons,
Time will tell if the pros outweighs the cons of this one.

Zig is a language with a lot of controversial but very interesting design decisions, and that’s one of the reasons why we love it.

So let’s give it a try.

Unfortunately, there was no reaction to my question/suggestion about detecting possibly wrong usage and issuing a warning automatically, by the compiler or a linter.

I think this could reduce the risk for our feet and nerves significantly.

2 Likes

because it was answered earlier, but since you missed it:
runtime safety check: add safety checks for pointer casting · Issue #2414 · ziglang/zig · GitHub
compile time safety check: Proposal: Pinned Structs · Issue #7769 · ziglang/zig · GitHub
there is also talk of pinned fields instead of types, which I think serve the case of @fieldParentPtr better, but both are useful. I don’t think there is a proposal for that yet though.

Since I don’t think this was mentioned, a great current example of this kind of interface is Build.Step used for the build graph that zig uses.

2 Likes

Actually I missed the pinned struct proposal #7769. This would eliminate this problem in 99.9% of all cases.

Given that one of its predecessors dates back to 2017 and contains the term “design flaw” in its title, and that it’s marked as urgent, I wonder why this always was postponed until now?

Because user code rarely had the risk to stumble over this, I reckon.

But now, with the new I/O, the risk increases, because every program needs I/O, often in several places.

Thus I propose to make this pinned struct proposal top priority.

Edit:
The remaining 0.1% would be when this kind of interface is implemented in user code and the user simply forgets to add the pinned keyword.