-
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
update standard library to a better "interface" pattern so that we have a null hypothesis against the interface/oop proposal #1829
Comments
Nice:
|
@dbandstra So unless the users explicitly shoots thenself with unsafe features, this pattern is safe, unlike interface fields, where a simple copy will break everything. |
Why |
@Rocknest No reason really. It was easier to just use |
I've been playing with this and I think I've come up with a pattern that should accomplish our goals with a minimum of API changes from the user perspective. It's a slight modification on @Hejsil's example which allows us to keep 'convenience' functions as part of the interface instead of separating them out. I've got an example in this gist. When I get to the Stream interface I'll have to see what can be done about the ErrorSet (#764) mess there. |
I personally have come to prefer the free function approach more than member functions. What you end up doing, is creating more helper functions yourself and those cannot be member functions unless you do more wrapping. This Let structs implement a minimal composable interface, and have free function that implements helpers around this interface. |
I don't necessarily disagree. However doing it this way incurs a minimal API change from the user perspective, and while API changes aren't really a big deal pre-1.0.0, I personally think that one should be its own issue and PR since it isn't necessary to accomplish the goals of this one. Doing it this way also allows for a relatively straight-forward trait implementation re #1669, if we decide to go that route, for example by comparing the parts of the type name or checking for the existence and value of a pub const. |
@tgschultz Alright, reasonable 👍 |
I am more for @Hejsil's approach at this point. I think it is cleaner and easier to document. |
After working through #1848, here's what I'm thinking: If we don't add anything to the compiler for interfaces, then I agree that @Hejsil's suggestion to separate the convenience functions from the interface struct will make things generally cleaner as there will be no more passing around wrapped and unwrapped versions of the implementation. If we do add something to the compiler, then I think the most helpful things to have would be to have vtable checking built in, and automatic interface abstraction. I imagine a new
The interface parameter would like |
I'm giving this another go with what I learned from the previous effort. This iteration of the pattern can be seen in interface.zig, which contains some helper types and functions. Improvements of this over the previous iteration:
Changes:
Regressions:
Notes (both iterations):
|
So this second crack at this isn't going so well. The first issue is that |
I had an idea recently that // imaginary code
const Allocator = InterfaceOf(struct {
alloc: fn (usize) *c_void,
free: fn (*c_void) void,
});
test "" {
const impl = SomeAllocator { .a = b };
const interface = Allocator.init(&iml);
var ptr = interface.alloc(5);
interface.free(ptr);
} Function {
@"$vtable": *struct {...},
@"$impl": *OpaqueType(),
pub fn init(impl: var) Allocator {
// assert impl == Pointer
return Allocator {
.@"$vtable" = comptimeGenAllocatorVtable(@typeOf(var).Pointer.Child),
.@"$impl" = impl,
};
}
pub fn alloc(self: Allocator, arg0: usize) *c_void {
return self.@"$vtable".alloc(arg0);
}
pub fn free(self: Allocator, arg0: *c_void) void {
self.@"$vtable".free(arg0);
}
} But im not sure if comptime code generation would ever be allowed in zig. |
My third attempt at this went pretty well. See #2533 for a summary. My opinion from working with this pattern is that it probably shouldn't be merged, despite the benefits to optimization, because of the added complexity. A better solution will almost certainly involve new language features, and a simpler solution could involve:
Update: I was struck with the idea to submit a version of this that follows the simpler conception above. Instead of using addendum: I'm kinda tired of rewriting the same changes over and over, so I might not get to this for a while, but if I do it'll be basically the barest possible change from status-quo that gets us the above. |
The simpler version I discussed is up now, #2553 . The changes to the pattern over status-quo are that interfaces consist of a pointer to an implementer as well as a pointer to each implementation function. Consequently, instead of the implementer carrying around an interface struct and everything needing a pointer to it, they instead have an Rather than use a single I'm still not entirely satisfied with it, as it doesn't solve the dynamic ErrorSet problem, but at least it doesn't make things even more complicated than they already are. It occurred to me during this iteration that we only really care about maintaining ErrorSet information if we intend to switch on an error union response from an interface (or chain of interfaces as in Streams) where the implementer(s) are (all) comptime-known. Consequently, I think I will submit one more variation, which is a modification of #2553 where Streams (the only interface with dynamic ErrorSets currently) are generalized and only return @andrewrk: After that, there will be 3 open PRs for consideration. If any of them are considered enough of an improvement over the status-quo to merge, then I'll rebase it after the big posix layer change is merged. |
This variation is identical to the previous one, but I've made the Stream interface ErrorSets non-dynamic. From what I can tell, basically nothing is lost by doing this, some errors just become more standardized versions of themselves: From the work on these two I believe I can improve #2533 so it at least works at comptime again and is a bit simpler. However, I will only commit time to that if the consensus is that we want to go in that direction, as it is it should serve well enough to demonstrate the pattern. Anyways, that it, I'm out. I'm leaving these three PRs (#2533, #2553, #2582) for perusal and consideration. If we decide to move towards one of them let me know and I'll rebase it. |
I'm a fan of #2553 and #2582 because it's simpler mental model. With the existing With an embedded opaque interface, we can copy the struct around without problems. This also allows us to return interfaces directly instead of needing to embed them into a wrapper struct. Edit: I just realized that the interface has a pointer to the implementation and they can have different lifecycles, e.g. the implementation is on the stack but it can return back the interface. Is this a real concern? |
#130 has been closed. I don't think there is anything to do here. |
Related:
Probably lots more. But the big parent issue is #130. It's one of the biggest topics in the community and we really don't know the direction Zig will go (it's a difficult design decision).
However, one thing we can do to get closer to knowing the answer, is to push the null hypothesis to the best thing it can be. That is, using status quo Zig and accepted proposals, how would we solve the problems that #130 would solve, without any other modifications to the language?
As @Hejsil points out on that issue, the status quo solution to "interfaces" is hostile to optimization. He created a demo of a different pattern here: https://github.com/Hejsil/zig-interface
@tgschultz has done similar prototypes (see for example #130 (comment)) I've also seen him post a bunch of related gists on IRC but I don't have any links.
So it looks like we have a clear way to update the standard library to use this pattern which is at least better on the optimizer. It also fixes a footgun and seems at least as readable, if not more, than status quo. So let's do it in the standard library. Then we can close this issue, and go back to all those other issues, armed with an improved null hypothesis.
The text was updated successfully, but these errors were encountered: