Skip to content
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

std interface reform (#1829) #2533

Closed
wants to merge 25 commits into from
Closed

std interface reform (#1829) #2533

wants to merge 25 commits into from

Conversation

tgschultz
Copy link
Contributor

@tgschultz tgschultz commented May 21, 2019

Attempt 3 of this went pretty well. It is my determination that this is close to the best we can do in userland today. Here's the summary:

Interfaces are in the form of pub fn InterfaceName(comptime T: type, comptime SomeFn: type...) type. For instance, the Allocator interface signature is pub fn Allocator(comptime A: type, comptime ReallocFn: type, comptime ShrinkFn: type) type. The interface function is used to produce an interface implementation from a struct by passing the *Self and type(s) of the implementation functions. The implementation will have an impl field, which is a pointer to the implementation instance, and one or more someFn fields that point to the implementation functions. The interface provides functions that utilize these implementation fn fields by calling them with impl as the first parameter. Since these generated interfaces are unique to a each type they wrap, they must be taken as var parameters, but will retain errorset information.

Any struct providing an interface implementation should provide a function named after the interface that returns the implementation. For example, Pcg has a random() function which returns its Random interface implementation. Further, the struct should expose a pub const defining its implementation type in the form of InterfaceImpl (Pcg.RandomImpl in the above case) and any errorsets in the form of ImplFnError. For instance, all Allocator implementing structs expose ReallocError (this should be unnecessary when bugs regarding error inference are cleared up, #1810, #1386, etc.).

If one needs to store an interface implementation in a non-generic struct, or otherwise pass around a runtime-determined interface implementation, the interface provides an 'abstracted' form with the name of AnyInterfaceName (AnyAllocator, AnyRandom etc.), These can be obtained from the non-abstracted interface implementation using toAny(), i.e. var allocator = direct_allocator.allocator().toAny();.

Interfaces support implementations with 0-bit types. When creating its interface implementation the struct must set .impl to null, and instead of taking *Self must take Unused. This prevents a problem where the abstracted interface passes a pointer as the first (assumed as self) parameter when no value is expected (because it is 0-bit). Note that the implementation returning function (allocator(), random(), etc.) should take self as var to allow it to work with method call syntax.

Async is supported via a hack, where AnyAllocator supplies two additional fields that point a different realloc and shrink implementations to account for the expected behavior of the status-quo Allocator interface. So, when providing an allocator to async, it must be of type *AnyAllocator. As async is due to be redesigned to not require an allocator at all (#2377), this is temporary.

Please see interface.zig for a test case that demonstrates the pattern.

Pros compared to status-quo interfaces:

  • Safe to copy and pass as const
  • Optimize significantly better
  • ErrorSet information is not lost unless interface needs to be stored or passed at runtime

Cons compared status-quo interfaces:

Alternatives:

  • Remove ErrorSets or use a mandatory standard ErrorSet for interfaces (as Allocator does) and teach the compiler to better optimize through @fieldParentPtr
  • Introduce a new container type that sugars away a lot of this pattern, automatically degrades the type to Any, etc.

@andrewrk
Copy link
Member

Please see #130 (comment). I'm closing this pull request because it's not really mergable, but I do consider it to be an invaluable tool in evaluating how to proceed with that issue, once the time comes to do that. 👍 Thank you @tgschultz! (I'm going to copy this comment to the other 2 pull requests, for the benefit of anyone stumbling upon them.)

@andrewrk andrewrk closed this Aug 16, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants