In C++, if there weren't automatic destructor calls, you could still clean things up manually with a pseudo-destructor call, x.~T(), where T is the type of x.
In C, there are no (user-defined) generics, so you always know the concrete type of x, so you know whether it needs to have a destructor called and what the name of that destructor is.
In Zig, how would generic code create an instance of a generic type T that may or may not need to have a destructor called, ie. how do you know if you need to use defer and what the deferred expression should be? How would tooling be able to check that destructors were called if there is no special way of marking that a destructor exists?
> You can always add a no-op dealloc method to your type if you want a generic function to handle it correctly
You're looking at it from the perspective of the generic consumer, not the generic author. The generic author generally does not have the ability to edit the type. Plus there are many types that do not have methods at all (integers, points, etc.).
> Moreover, Zig has awesome metaprogramming support so you can probably think of a way to check the type parameter for a dealloc method and only generate the dealloc call for those types.
Yes, you can detect and call a method called deinit that has the appropriate signature using @reflect. You can even put this in a function and call it a pseudo-destructor. But there's no guarantee that deinit has the actual semantics of a destructor without some kind of language-level agreement between class authors.
This will work fine. Do you have a more specific example?
To answer my question, no, they're not deinited. All deinit does is call self.allocator.free on the slice of elements, and for many allocators that's a nop. In fact none of ArrayLists methods take any kind of ownership of its elements. If you shrink an ArrayList(ArrayList(i32)) by one you leak the last ArrayList(i32). None of the methods call destructors on the elements because there is no generic notion of a destructor, only particular ad-hoc ones like deinit methods. ArrayList appears to solve the problem I mentioned above about generics not knowing if a generic type needs to have a destructor called by only supporting types that don't.
In C++, you'd write the function that clears a vector something like
void clear() {
for (auto p = ptr; p != ptr + len; ++p) {
p->~T();
}
len = 0;
}
For a vector<vector<int>> the syntax p->~T() calls the destructor on a vector<int> element. While for an vector<int> the syntax p->~T() is a pseudo-destructor call, ie. it does nothing. This makes the same generic code work when the elements of a vector need to have a destructor called and when they don't.I believe what he was wasking was "given an ArrayList(i32), how would you expect it to call deinit on the member i32s?". The answer, of course, is that you don't, which is also true of ArrayList(ArrayList(i32)). ArrayList absolutely supports heap-allocated types, you just have to free them yourself before calling deinit() on the ArrayList itself.
But if you did, you could use `@TypeInfo` to inspect the inner type for a function named `deinit`, or some other criteria that made sense for this determination.
I already mentioned that hack. All you're doing there is introducing the ad-hoc notion of a destructors as a method named deinit. Again, in order for that to work in generic code that convention needs to be blessed by the language.
Technically it only needs to be blessed by the standard library in this case.
I'm not sure how to reconcile this statement with the following claim from the linked article: "Zig has no macros and no metaprogramming yet still is powerful enough to express complex programs in a clear, non-repetitive way." Not only does it claim that Zig has no metaprogramming, it suggests that it sees metaprogramming as undesirable.
fn max(comptime T: type, a: T, b: T) T {
if (T == bool) {
return a or b;
} else if (a > b) {
return a;
} else {
return b;
}
}
The type variable isn't used at run-time.C has had generics since C11. I’ve got a clone of “the good parts” of the std C++ template library (vector, unordered_map, ...) laying around somewhere.
_Generic(x, type : value, ..., [default : value])
For a list of unique types, with an optional 'default' for any unrecognized type.You usually wrap the call to '_Generic' in a macro. For instance:
#define vec_push_back(vec, val) \
_Generic(&vec, \
vecint : vecint_push_back, \
vecdbl : vecdbl_push_back, \
default : assert_vwrap)(&vec, val)
Assuming that types 'vecint' and 'vecdbl' have been defined by the user. This does single-parameter parametric type dispatch. There's way to allow the user to use table-driven macros ('X-macros') to allow per-translation-unit definition of an arbitrary number of instantiations.The nice part, over C++, is that the generic instantiation is localized to a single translation-unit file, so you don't have to yell at the junior devs for instantiating templates without using an 'extern' construct.