You must not return a pointer to stack memory from a function, because that pointer is no longer valid. So if an initializer returns a *Foo, it had better take an Allocator so that the pointer lives in heap. By convention we call those create btw.
It’s fine to return a Foo, though, because that ends up in the result location: conceptually, the struct is built inside the function call and copied back to the variable (or ‘place’ at least) where the result goes. The optimizer might elide the copy but it’s better to assume it won’t.
A Foo all by itself can only coerce to a *const Foo, not a mutable *Foo:
const my_foo: Foo = .init(stuff).doThing(more_stuff);
This will only work if doThing has the receiver types Foo or *const Foo. Any parameter type which is not a pointer is immutable, and this is consistent with that.
So you’d have to do this:
var my_foo: Foo = .init(stuff);
_ = my_foo.doThing(more_stuff);
Not so convenient! Zig does not encourage or reward chain-heavy code. I think of that as more of a consequence than a design goal.
It’s always best to work with the language. If you want to write something as chained code, but the process is cumbersome, ugly, hard to understand: maybe don’t do it that way.
var foo: Foo = .init;
try foo.preheat(allocator, param1);
try foo.doTheNextThing(allocator, more, stuff);
if (foo.readyForAction()) {
// ...
}
If you absolutely, positively must write this kind of code as a series of method chains, you’ll need to use another language. That’s just how it is. But I guarantee you: the CPU? Doesn’t care. Users? Don’t care. Do you care?