4 ms·
Re: Comptime interfaces I wonder why Zig didn't decide on the convention of making the allocator type comptime-known. It would make code faster by eliminating
by TakeBlaster16 4y ago
Re: Comptime interfaces
I wonder why Zig didn't decide on the convention of making the allocator type comptime-known. It would make code faster by eliminating a runtime indirect call, and it would let you lower memory usage if you choose a zero-sized type as your allocator, since e.g. every single ArrayList in your app would no longer need to reserve space for the allocator field.
- david2ndaccount 4y agoIndeed, you could have a type-erased allocator on top of that for preserving dynamic functionality.
- Arnavion 4y agoHaving the allocator type be part of the collection type means every thing that needs to work with that collection also needs to be generic on the allocator type. It's just busywork. (Rust has made the decision to have the A: Allocator be part of the collection type, and it's already causing the problem where all existing code only works with A = GlobalAlloc) You don't need to use ArrayList if you know which allocator you want to use with it from some other means. You can use ArrayListUnmanaged. An ArrayList is equivalent to an ArrayListUnmanaged and Allocator.
- TakeBlaster16 4y agoZig already encourages you to pass the allocator around as a value. Passing it as a type would be about the same amount of typing. Instead of this: var list = ArrayList(u8).init(my_allocator); you do this: var list = ArrayList(u8, MyAllocator).init(); How is that more busywork? Rust's issue is not really related. They added the generic after 1.0 and so it needed to have a default for backwards compatibility. If it had been generic from the start with a matching lint (like `implicit_hasher`) things would be fine. Zig on the other hand is not yet 1.0, so they can afford to make allocgate-like changes.
- Arnavion 4y ago>Zig already encourages you to pass the allocator around as a value. That has nothing to do with what I said. I said the Allocator is not part of the type. An ArrayList(u8) is the same as another ArrayList(u8) regardless of what allocator they use. >How is that more busywork? Write a function that operates on any ArrayList(u8). It doesn't need to be generic itself. If the Allocator was part of the type it would also need to be generic. And its caller would need to be generic, and so on. >Rust's issue is not really related. They added the generic after 1.0 and so it needed to have a default for backwards compatibility. False. std::io::Read::read_to_end takes a Vec<u8>. It cannot be changed to take a Vec<u8, A> because that would require read_to_end to be generic on A, which cannot be done because that would make std::io::Read no longer be object-safe. And to be clear, the point is not that std::io::Read can't change without being backward-compatible. The point is about needing to be generic on the allocator causes all sorts of problems, eg the infectious genericity (which also affects Zig) and the lack of object-safe-ness.
- TakeBlaster16 4y ago> And its caller would need to be generic, and so on. My point though, was that it's extremely similar to code you already write. Zig encourages you to write `fn foo(allocator: Allocator, ...)`. That would just become something like `fn foo(allocator_type: anytype, ...)`. Sure one is generic and one isn't, but since types are just normal parameters, it works out to the same amount of typing. > std::io::Read::read_to_end takes a Vec<u8> In Rust, read_to_end is part of a trait and appears in a vtable, so it can't be generic. The Zig equivalent `Reader.readAllArrayList` however, is a free function that is statically resolved. Object safety isn't a concern. In fact, Reader already has generic functions, such as `readInt`. `readAllArrayList` could be generic for the same reason that `readInt` can be.
- Arnavion 4y ago>My point though, was that it's extremely similar to code you already write. Zig encourages you to write `fn foo(allocator: Allocator, ...)`. No function that takes an ArrayList(u8) needs to have an Allocator parameter. >In Rust, read_to_end is part of a trait and appears in a vtable, so it can't be generic. That's what my comment already said, yes. >Object safety isn't a concern. ??? It was an example of the downside of Rust's choice. Object safety is a concern for std::io::Read::read_to_end. Rust's choice of having the Allocator be part of the type has forever doomed std::io::Read::read_to_end users from being unable to use any allocator for the Vec other than GlobalAlloc.
- throwawaymaths 4y ago> Having the allocator type be part of the collection type means every thing that needs to work with that collection also needs to be generic on the allocator type. Maybe I'm misunderstanding but... Not necessarily? since zig is duck-typed at comptime, you could in principle pass the allocator in as a parameter "at runtime" and let the compiler figure out the allocator type.
- Arnavion 4y agofn reticulate(splines: std.ArrayList(u8)) void {} fn ArrayList2(comptime T: type, comptime A: type) type { _ = T; _ = A; return struct {}; } fn reticulate2(???) {} reticulate2 must work with all ArrayList(u8, A) regardless of what A is. So what is `???` ?
- AnIdiotOnTheNet 4y agofn reticulate2(splines2: anytype) void {} The compiler would ducktype splines2 and everything would work fine.
- Arnavion 4y agoYou lose a lot of type safety by making the whole parameter `anytype`. If Zig supported `ArrayList2(u8, anytype)` that would be marginally better, but it doesn't.
- throwawaymaths 4y ago> You lose a lot of type safety by making the whole parameter `anytype`. Pretty sure that's not the case. You can also put further compile time type assertions downstream of the function head. > If Zig supported I believe the argument of the thread is that it should support that.
- markisus 4y agoI believe that the language designers did some experimentation and designed the vtable struct so that calls to it can often be devirtualized. See this blog article for more details https://pithlessly.github.io/allocgate.html https://pithlessly.github.io/allocgate.html
- slimsag 4y agoThis is correct, my understanding is devirtualization is quite sufficient in general and fine for Zig's allocator interface. The challenge/solution outlined in my article should not be read as "vtable bad!" but rather "vtable bad..if it means you lose ABI compatibility and have to copy struct data for ABI compatibility everywhere in the end"
- TakeBlaster16 4y agoIf devirtualization does reliably occur then that's probably good enough. I find myself surprised that they would rely on compiler magic for that, it seems to go against the ethos of the language. But in the grand scheme of things, I guess it's a pretty minor cost.
- anonymoushn 4y agoWe've found that even the vtable-less Writer interface is extremely costly. For ArrayLists, you can maybe use ArrayListUnmanaged to get the space savings.