I know youβre focused on the specific issue of thread safety with allocators, but this is just one possible attribute that one would like to have embodied in the type system and checked at compile time. Iβd want to see a more general solution, but this example is perfectly good motivating example for the need.
We can embed anything at all into a type (declarations) and check it however we need to at comptime, by writing more Zig.
I have yet to see a problem this canβt solve, but you need to be able to get the type with @TypeOf, which wonβt work on an *anyopaque. So we can use it on βanything which can make an Allocatorβ but not on Allocator itself.
Some people (not to say youβre one of them!) think that this βdoesnβt countβ, like if user code writes its own type check thatβs somehow not part of the type system. If it doesnβt compile then itβs a type failure, the way I see it. Comptime is just so much more powerful than the usual way of doing this that it doesnβt look like a type check.
So the trick is how to arrange the code that does the type check. I can imagine either the library provides a function to generate compatible allocators, or a allocator registration function. These do the type check and save debug-only data to check that a passed allocator is one that has been registered.
For me, I donβt mind documenting the constraints and itβs up to users of my library to RTFM. But I understand in some environments that level of trust in your clients is a no-go, or the documentation may not be translated into a language the users understand, for example.
If the code can reach the type, youβre golden. The callee is in complete control: anything you can determine about the specifics of the type, and thatβs a great deal, can be turned into a compile failure with an informative message about whatβs missing. If the caller wants to use that code, they can get that stuff right or it wonβt compile. I suppose they could fork it, but letβs make the simplifying assumption that these constraints are load-bearing.
The type-erased vtable pattern poses a problem for that approach: you canβt get the type given the interface. A severe problem? Well, no. But not a made-up one.
Documentation can go a long way. But the best documentation of type-level contract violation is an informative compile error. Otherwise itβs either a mysterious, or hopefully informative, runtime failure: those are not as nice.
While you are correct that one can implement their own ad-hoc type system, you do lose the benefit that types are checked automatically. So if I forget to run my own type-checking code the program might crash at runtime, which would be impossible if certain things were expressable in the type system. So while I understand your point I do not consider such compile-time checks as part of the type system, because they might cause runtime failures whereas type errors are caught during compilation. Having some way to βinfuseβ compile-checks into a type would be awesome.
Regarding the points about separating storage from strategies, that does remind me of the little I know about C++'s polymorphic memory resources, maybe something for some smart person to look into?Otherwise I think the current model with the Allocator interface works well enough for me.
We have this, itβs functions which return types and take type parameters.
If you want to use the strategy on anytype generic functions, then you do have to narrow things functionally. You can write one function for this purpose and reuse it everywhere which makes sense.
A programmable type system means by definition that itβs possible to make type-level logical errors. Thatβs because itβs strictly more powerful than weaker approaches to the problem.
For an explanation of the terms used in this section, see
attributes(7).
ββββββββββββββββββββββββββββββββββββββββ¬ββββββββββββββββ¬ββββββββββ
β Interface β Attribute β Value β
ββββββββββββββββββββββββββββββββββββββββΌββββββββββββββββΌββββββββββ€
β malloc(), free(), calloc(), β Thread safety β MT-Safe β
β realloc() β β β
ββββββββββββββββββββββββββββββββββββββββ΄ββββββββββββββββ΄ββββββββββ
Two other βattributesβ of malloc are implicit:
used heap memory persists for the duration of the applicationβs runtime
free - releases memory to heap for further reuse
In the same manner we can say that Zig replacement for malloc should also to have these attributes:
The documentation you quote for these allocators is just that: documentation. It isnβt something anywhere in the type system related to these functions.
Itβs the same problem, as problem of ThreadSafeAllocator - if allocator is not thread-safe, any wrapping with lock/unlock will not prevent non thread-safe usage.
With my proposal std code can check thread-safety of allocator and return error.