Perfecting WebGPU/Dawn native graphics for Zig
devlog.hexops.com
devlog.hexops.com
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.
(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.
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.
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.
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.
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.
I clearly said there are two downsides of having the Allocator be part of the type. One, the busywork from having to propagate the type parameter, applies to Zig and Rust. The other, the fact that generic methods can't go in vtables, only applies to Rust.
{
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit(); // All allocations made by the above allocator will be freed at the end of scope
const allocator = arena.allocator();
var i = 0;
while (i < 1000000) : (i += 1) {
_ = try allocator.create(i32);
}
// all allocations are freed
}
var buffer: [1000]u8 = undefined;
var fba = std.heap.FixedBufferAllocator.init(&buffer);
const allocator = fba.allocator();
// the allocator will return an address inside of `buffer`
_ = try allocator.create([512]u8);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.
fn 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 `???` ?The compiler would ducktype splines2 and everything would work fine.
If Zig supported `ArrayList2(u8, anytype)` that would be marginally better, but it doesn't.
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.
For ArrayLists, you can maybe use ArrayListUnmanaged to get the space savings.