Why std.Io.Writer interface design is different from std.mem.Allocator interface in 0.15.1

I will reiterate that this is a silly discussion about semantics.

I was, in fact, referring to the whole of the buffering state.

Yes, pointers are required to share state between different pieces of code.

I am not working from some other languages’ definition, and there is no clear language agnostic definition of an interface.

Rather, I refer to the core, practical use case they solve.

That is an ‘abstract contract, agnostic of the implementation’, many languages add the restriction that they are stateless, but as far as I can tell it is an arbitrary restriction, I have yet to find a justification.
I see no reason why state can’t be a part of that abstract contract.

1 Like

Yes, it is indeed just a semantic debate, but I think clarifying semantics is very important for effective communication, and it is not stupid.

Yes, we were not on the same channel before, because I noticed that the “interface” you mentioned refers to the “universality” in polymorphism (as opposed to “particularity”.
But I think one thing that needs to be clarified is that when the concept of polymorphism was born, the word “interface” was not used to express the universality in polymorphism. I think the word “interface” was used to express this after pure contract-based polymorphism became popular.

The statelessness of the “interface” I am discussing is not even necessarily related to polymorphism, but rather the more literal meaning of “interface”: an abstraction for accessing and manipulating objects.
Just as in “APIs”, “interface” is simply a stable, stateless “handle” for manipulation. When we use an interface to access and operate an object, the simplest interface can be a pointer to the object, or a member pointer that can be used to retrieve the object body through container_of, so that the object can be accessed and operated through the interface. Parts of an object’s implementation can be elevated to the interface level, but only if they are stateless. Stateful content should not appear in interfaces, as this destabilizes the interface.

In programming language polymorphism, the term “interface”, due to its inherent statelessness, has become used to express “implementation-independent” polymorphic abstractions.

Therefore, in my eyes, statelessness is the essence of the interface, and the concept of interface in polymorphism borrows the statelessness of the word “interface” to emphasize its contractual properties to distinguish it from the inheritance-based polymorphism of the past.

You keep stating, with more and more words, that interfaces have to be stateless in your opinion.
You just keep stating it, without justifying it.

This is quite annoying, especially after stating you want to clarify the term for better communication.
As well as your literal definition of ‘interface’ not excluding state.

This conversation is not productive, I won’t be continuing it, unless you can provide actual justification for disallowing state in interfaces

This might have come across as me being upset, I’m not, just not invested in this anymore

If we were to simply state “why,” the answer lies in the literal meaning of “interface”: an interface is an abstraction for accessing and manipulating something.
Would you ever pass an Io.Writer as a parameter when operating on a writer? No, you never would, because Io.Writer has state, and if you pass it as a parameter, that state is lost. Therefore, Io.Writer is not an interface; you cannot use it as an abstraction to operate on the writer.
You can operate directly with *Io.Writer, so *Io.Writer is an interface. It’s that simple.

How you use that style of interface has no baring on the question ‘should interfaces be allowed/disallowed to have state’.

Even if Writer/Reader didn’t have state, you would still need to use them by pointer due to them being intrusive interfaces, it’s required to be able to access implementation state.

I’m a little frustrated because it feels like you’re still using “interface” as a synonym for “polymorphic universality.” Therefore, you don’t understand what I’m saying.
Interface itself is the opposite of implementation; it’s an abstraction. So, you shouldn’t ask, “How do I use that style of interface?” Asking that question is actually discussing implementation.

Interface is the opposite of implementation.

To interject for a second, you are asserting you know what “interface” means objectively.

Words do not have objective meaning. Move on.

2 Likes

Yes, simply discussing the semantics of interface is indeed going too far. What I really don’t understand is why Io.Writer isn’t currently considered inheritance-based polymorphism. Even if interface means all “polymorphic common parts,” then inheritance-based polymorphism fits that definition.

Furthermore, inheritance isn’t incapable of implementing Allocator-style interfaces, as shown in the following code, modified from the OP’s code:

Interjecting myself here, because I think this is symptomatic of a communication problem. If you are getting frustrated because you know someone is using a word differently than you, then try using different words to convey your meaning. This is particularly important when talking jargon. This stubborn resolve over this issue reflects less of an interest to communicate and more of a desire to win.

This is a rather harsh accusation, so I may need to defend myself. I quickly realized that we were discussing completely different things with this term, and I assumed I understood what the other party was discussing and tried to make them understand what I was discussing. I spent too much energy explaining what I was discussing. My original intention was simply to clear up some misunderstandings caused by the different objects of discussion in our communication. Of course, your accusation may not be entirely wrong, as I do feel that the other party has misused the term.

On reflection, I realized I might have been overconfident in my understanding of what the other person was referring to, and it’s possible I never understood what he meant. Our discussion stemmed from whether the current Io.Writer implementation was inheritance-based, and I think I might not have actually understood what he meant by “interface,” and therefore I still can’t understand why it shouldn’t be considered inheritance-based.

5 Likes

Sounds good. I just wanted to intercede before either party got much more frustrated.

1 Like

I think it can be.

Most of us use multiple languages, and these often come with their own nomenclature. I think it’s fair to occasionally think by analogy and then accept that Zig has named a concept X instead of Y. Zig terminology is under formation as core developers iron out the language and std APIs from first principles.

As an example, if you come from C++, a bit of squinting and the Writer interface will look a lot like an abstract base class with some data members, a bunch of regular functions, and four pure virtual functions (such as drain) which concrete types like File can (and in some cases, must) override. Some C++ developers will happily call this an interface with data.

1 Like

Thank you for your explanation. I realized that the difference in understanding between him and me stems from our understanding of “inheritance.”

My understanding of inheritance is a polymorphic implementation method characterized by coupling implementation-specific data with public implementation data. This is achieved by embedding the public implementation within the implementation. There are various polymorphic implementations competing with inheritance. Composition-based polymorphism is characterized by separating implementation-specific data from public implementation data. This is achieved by having the public implementation reference a pointer to the implementation-specific data. Interface-based polymorphism (I think this is the origin of the word “interface” for polymorphism. When other polymorphisms also use the word “interface”, I think it spreads backward from here. The “interface” that I repeatedly tried to explain above does not actually refer to the “interface” here, but I feel that the other party wants to express this meaning) expresses a simplified polymorphic implementation that only provides methods but not public data implementation. For some languages, interface-based polymorphism does not necessarily require vtb or similar back-end implementations, but is merely a semantic convention.

As for the placement of the vtable pointer, if the content of the vtable in a polymorphic state does not change at runtime, it can be placed anywhere, in the public implementation, or outside, together with the interface (well, this is what I just tried to explain the meaning of the “interface” I wanted to express, which is completely another thing and has nothing to do with interface-based polymorphism here). So, when I discussed “Allocator-style interfaces” above, I was actually discussing the pattern of moving the vtable from the implementation to the interface. I admit that the term “interface” here is easily confused with the “interface-style polymorphism” mentioned earlier, and the misunderstanding is understandable. In my testing, this pattern does indeed lead to better anti-virtualization optimization.

Because I’ve always had a need for these polymorphic implementations in C, I don’t really attach much sentimentality to terms like “inheritance,” “composition,” and “interface.” So, when I saw Io.Writer being considered inherited, I was met with a lot of pushback, which was quite baffling. To me, this implementation is the inheritance-based polymorphism I’m familiar with.

After your clarification, I now understand that many programmers don’t think of “inheritance” as a specific way to implement polymorphism, but rather as a way of thinking about how to design polymorphism using crude taxonomies, like “penguins inherit birds.” However, in practical areas like GUIs, where inheritance is particularly effective, inheritance-based polymorphism isn’t actually used in this way.

I think using “interface” in Zig to mean a potentially–stateful interface is fine. I will say it was confusing for me, because although I know a bunch of languages (around 7 or 8) I have only seen interface used for stateless interfaces. Someday it might be worth a couple sentences in the docs mentioning that interfaces can be stateful in Zig.

Most of the time languages where interfaces are stateless also have abstract classes. So the reason for why they restrict interfaces to a subset of what an abstract class can do is to make it easier for people to talk about the same thing. Different words for different concepts essentially.

Another reason is that having implementation inheritance can become quite a mess if the language handles it for you instead the programmer compositing the stuff into another manually and you want something to inherit multiple things (see virtual inheritance in C++).

I think this discussion is drifting off-topic. Maybe discussing about the meaning of “interface” could be moved to a different thread?

Back to the question:
In fact the OP’s question was more or less solved right at the beginning:
It’s a question of memory overhead and performance.
There are many, many allocations*) in a program, and it was seen that the Allocator style is better than the @fieldParentPtr style of a writer in this scenario.

*) …unless the program is explicitly written with a goal to avoid this.

But it’s worth mentioning that the Writer has a fundamental different usage scenario:

The writers are written in such a way that VTable calls are more or less only needed when the buffer is full (ie rarely), and in that case, there is actually some heavy work to do (eg syscalls).
And in this scenario, the overhead for the VTable call is absolutely neglectable, so the @fieldParentPtr way “wins” here.

1 Like

I don’t think it is off-topic. It is quite fundamental to the question OP asked. If this were a complete beginner who had asked this, I’d agree with you, but OP has clearly been trying to explore this space themselves already.

This discussion, as long as it doesn’t stray past the brinks of a complete flame war, is interesting to read.

2 Likes