Immutable structs are mostly stack allocated, even if it’s fields are pointers. Semantically both mutable and immutable are passed by reference, but for immutables it doesn’t really matter, since they are immutable.
Mutable structs generally stay on the heap, and so they have slower performance, specially if you have an array of them, that is because immutable structs can be stored inline in an array, while mutable structs becomes an array of pointers.
Other threads that went into this are interesting:
Although “pass by sharing” is what most people think—completely reasonably, imo, based on the name—that “pass by reference” means. Unfortunately, “pass by reference”, at least in C++, means something entirely different, which is that you can apply a function to a variable in a calling scope and it can change what value that variable refers to. In sane languages, functions cannot do this at all (macros can though).
logical function swapnum(i,j)
integer :: i, j, temp
temp = i
i = j
j = temp
end
program main
logical :: swapnum, dummy
integer :: a, b
a = 10
b = 20
dummy = swapnum(a,b)
write(*,*) "a is ", a, " and b is ", b
end program main
Yeah, it was thought to be a good idea once upon a time, along with dynamically scoped local variables, but these days people have generally recognized that it’s a bad idea as it makes it much harder for a person or compiler to reason about what effect code can have on local variables. This StackOverflow answer has a really good rundown of the meaning and history of these features and terms. These days the most common, standard argument passing behavior is what Lisp (where it originated), Python, Java and Julia all do, which is called “pass by sharing”. A good mental model for this is that all objects are referenced via pointers and those pointers are assigned and passed by value.
More modern Fortran tried to remedy that with the intent syntax for the variables, if one declares those variables as intent(in) you cannot anymore change their values.