Zcore : an early (WIP) tensor library in Zig for Zig... looking for design feedback and help

Hey all,
I fell in love with zig while exploring it this summer and I kind of look at it as a language that can actually be the forefront of low level parts of ML/AI in the future. Hence, I have been working on zcore, a tensor library in Zig, and wanted to share it early while the core design is still open to change rather than waiting until it’s “finished.”

Where it’s at right now: shape/strides/indexing, bounds-checked get/at, slicing, transpose, resize, broadcasting shape logic, and zero/fill initializers. No arithmetic ops yet (add/mul/matmul etc.) : that’s next, and I’d genuinely like input on the approach before I commit to it.

A few design decisions I’d love pushback on:

  • Tensor owns an allocator per-instance rather than taking one per-call … trying to keep the API ergonomic but I dont know if this is the right tradeoff.
  • Strides/shape are computed and stored on the heap alongside data, with from_slice supporting adopting external memory.
  • Type-restricted at comptime to int/float/bool.

To be honest, a huge way that my this project is different for me is that i have tried to keep this production grade from the start, while still actually focusing on an aim of mine: a well enough documented code that every single beginner too can understand well.comments are meant to be thorough enough for the code to double as a learning resource, not just a working library.
There’s an implementation plan in the repo (implementation_plan.md) laying out the phases if you want the fuller picture, plus a style guide too.

Repo: https://github.com/ka1rav6/zcore

It’s early and small (~1000 lines), so if you’ve got 10 minutes to skim it, I’d rather hear “this approach will bite you later” now than after I’ve built arithmetic ops on top of it. It is the first time I am trying to scale a project like this so all my design decisions have been made looking at the code bases of other people… and i would love some honest criticism.


A note on me: I am a student at a tier one engineering college who loves math, ML and low level programming

Welcome to ziggit (:

I skimmed to code and got a bit of feedback on the code:

Using field names like _data is discouraged by the style guide: Documentation - The Zig Programming Language

It depends on your wanted API, but I’d expect IndexOutOfBounds not to be a runtime error, but a usage error. You could skip the whole error-mechanism and assert that the index is not out of bounds with a fn isInBounds or something like that.

At least you can allocate size and stride in one go to reduce allocations and potential fragmentation.

Then on the API itself:

Tensor owns an allocator per-instance rather than taking one per-call … trying to keep the API ergonomic but I dont know if this is the right tradeoff.

In most zig code the approach is the other way around (e.g. the std containers are moving to variants without allocator fields; you pass the allocator in the functions.) Though I see you are also trying to auto manage memory management with owns_memory. Just like you can const slice = buffer[10..20] I would trust the user to correctly manage this. Much less ergonomic for the API, but you could also have the tensors not own the data, e.g.

const tensor : Tensor(f32) = try .initEmpty(gpa, &.{ 100, 100 }); // data is null
defer tensor.deinit(gpa);

const data = try tensor.zeroes(gpa); // only now 100 * 100 f32s got allocated
defer gpa.free(data);

const view = tensor.view(gpa, &.{ 10, 10 }, &.{ 20, 20 });
defer view.deinit(gpa);

– not sure if it is worth it. (: (There is also the whole possibility to make dimensionality comptime known & not needing to allocate shape/stride – but that’s with different tradeoffs)