How to modify value of *scalar* argument passed to function so that it persists in calling function?

I want to obtain standard C behaviour of following minimal example:

int a = 5;
void f (int *a)
{
    *a = *a+1;
}
f(&a);

after calling the function, variable “a” will still be equal 6 in outer scope.

I am aware that in Julia, functionally, I can obtain it by

a::Int = 5
function f(a)
    return a+1
end
a = f(a)

but I believe it is less efficient due to unnecessary memory allocation and assignments.

No, the heap will not be used here in Julia. You don’t even need that ::Int for that to optimize.

2 Likes

What you probably want is to wrap the value in an Array or in a mutable struct.

1 Like

Well, 1x1 array or one-field struct are not as elegant as plain variable. I am aware of these workarounds, bot still looking for non-awkward solution.

An elegant, idiomatic way to do this is to wrap the value you want to make mutable in a Ref (like a = Ref{Int}(5), and then mutate the value within your function like so: a[] += 1. This is actually pretty much how passing object pointers to ccall is done internally by lowering, as wrapping an immutable value with Ref forces that value to take on a fixed address in memory for the duration of the Ref's lifetime.

EDIT: Full example:

a = Ref{Int}(5)
function f(a)
    a[] += 1
end
f(a)
1 Like

Note that the mutable struct approach suggested is possibly even more idiomatic than my suggested Ref approach if you plan to dispatch on the type of the value.

But the poster is asking for this for performance reasons. Is there any reason to believe that it will be better for performance? As far as I understand it will not, so it’s just a bad idea all around.

A benchmark test

julia> foo(a) = a + 1
foo (generic function with 2 methods)

julia> bar(a) = a[] += 1
bar (generic function with 1 method)

julia> @btime foo(b) setup=(b=5)
  0.022 ns (0 allocations: 0 bytes)
6

julia> @btime bar(a) setup=(a=Ref{Int}(5))
  1.662 ns (0 allocations: 0 bytes)
6

Preliminary conclusion: Don’t do it, @Rafal_Machalica, it’s a bad idea. (I guess the benchmark isn’t really working here, but you should really use immutables as much as you can, it’s better programming practice, and probably more efficient.)

3 Likes

Sure, for performance reasons passing and returning values (instead of manually boxing them) is going to be more efficient. I was just responding directly to the title of the post :slightly_smiling_face:

Just a minor comment: this is not a matter of being scalar or not, but of the type being mutable or immutable: mutable structs can be mutated by a function, immutable structs cannot. Julia uses “call by sharing” evaluation strategy, like Python to name one. That’s why above you’ve been suggested to wrap the object in a mutable struct. You’ve been told why boxing your immutable object in a mutable struct is a bad idea for performance. Just make sure that your function f doesn’t change the type of your object a, otherwise that could be another performance trap.

2 Likes