Trying a bit of programming in Odin I’ve realized something curious:
When accessing a member through a pointer, I can omit the explicit dereference, basically allowing me to write p.field instead of p^.field, which according to a post of the language creator is to make writing this common pattern easier. It seems that the same argument would apply when passing a pointer to a proc that expects a non-pointer value of the same type, and indeed some built-in functions like len and cap allow you to call them even with a pointer. I wonder if there is a specific language design reason why this auto-dereferencing only works for a few builtin procs rather then in general. I think this would be useful for user-written procs, as without it any proc that mutates a value (thus requiring a pointer parameter), but also might call some readonly procs on that value will now be littered with ^ sigils in the readonly proc calls.
I think you should realize what you pass to proc and auto dereferencing does not work for parameters, you can create overloaded procedures. And all that grater 16 bytes are passed by reference automatically. Odin is very efficient in parameters passing. Everything already done right.
Read all the docs, standard library and program more, you will catch your zen.
I know about the parameter passing style, and I think it’s one of the coolest things about Odin, making some small compromises to eliminate all of C/C++'s viral const complexity. However, this also introduces the need to dereference existing pointer variables when passing them to such value parameters:
Foo :: struct { i: int }
foo_len :: proc(f: Foo) -> int { return f.i }
mutate_foo :: proc(f: ^Foo) {
i := foo_len(f^) // <- requires explicit deref here
f.i += i
}
mutate_array :: proc(a: ^[dynamic]int) {
i := len(a) // <- but doesn't require explicit deref here, much nicer
append(a, i)
}
I wonder why this special case is there for len, but not for user-written procs. Of course, I can always simulate the same behavior as len with overloads:
foo_len_val :: proc(f: Foo) -> int { return f.i }
foo_len_ptr :: proc(f: ^Foo) -> int { return f.i }
foo_len :: proc{foo_len_val, foo_len_ptr}
However, this is a lot of code duplication and boilerplate, and scales poorly (requires 2^num_of_val_parameters overloads).
I’m interested if, from a programming language design perspective, there would be some danger in allowing autodereference on parameter passing, or if it simply was deemed too small of an issue to be implemented generally.
Since Foo is a struct and foo_len only returns a value and does not modify it, you can get away with the below in this particular case and have only 1 definition.
foo_len :: proc(f: $T) -> int where T == Foo || T == ^Foo { return f.i }
For the case that foo does not mutate the parameter and the parameter is not a struct
foo_int :: proc(num: union #no_nil{int, ^int}) -> int {
i: int
switch t in num {
case int: i = t
case ^int: i = t^
}
return i
}
mutate_int :: proc(num: ^int) {
num^ += foo_int(num)
}
i := 1
fmt.println(foo_int(&i))
mutate_int(&i)
fmt.println(foo_int(i))
With type assertions, foo_len and foo_int can be reduced to
foo_len :: proc(f: union #no_nil{Foo, ^Foo}) -> int {
f := f.(Foo) or_else f.(^Foo)^ //safe shadowing that does not trigger -vet
return f.i
}
foo_int :: proc(num: union #no_nil{int, ^int}) -> int {
return num.(int) or_else num.(^int)^
}
I suspect that the core/base procedures that auto de-reference, do so only when the parameter is not mutated, and do something similar behind the scenes as the above union examples, and for only procedures where it’s safe and/or makes sense to do so.