The language problem here is that, at the time you write the code, you don’t have access to the information to make the optimal decision. There are two cases:
First, for concrete type Foo, either fn f(foo: *const Foo) or fn f(foo: Foo) might be optimal, depending on specific CPU architecture. If CPU has a lot of registers, you might pass larger structs in registers. If there are few registers, you might want to use a pointer. The conventional wisdom is that you want to pass “small” types by value, but the exact boundary of “small” depends on the target ABI.
Second, the code might be generic. Eg, ArrayList append method is:
pub fn append(self: *Self, item: T) Allocator.Error!void {
which is good for “small” Ts, but you’ll want *const T for large ones. You have to make a choice when picking the signature, and the choice will be suboptimal for some values of type parameters!
One would think that it doesn’t matter, and that we can just rely on compiler to optimize things regardless of the original form we write the code in, but that’s not so simple, because two forms come with different semantic constraints for optimizer, and, ideally, we want to have a union of constraints.
Eg, when you write *const T, you make the address of parameter significant, the T now has both the value and identity, and the compiler can’t just replace the with T, as that erases the address.
Conversely, when you write T, you claim that you need an independent, unaliased copy, so going from *const T to T generally requires copying data.
Ideally, you’d be able to express parameter passing mode where you don’t care about address of the parameter, but also don’t care whether it isn’t an independent copy of data, but rather just an alias of data stored elsewhere.