Julia DISCUSSION

Julia functions ending in !: why does push!(v, x) change my array but v = v .* 2 inside a function does not?

Started by su Julia bang conventionmutating functionspass by sharingin-place broadcastingarray copy
4 replies 248 views 5 participants
Latest activity · 30 Sep 2026

Julia functions ending in !: why does push!(v, x) change my array but v = v .* 2 inside a function does not?

su Julia Forum
#1

I wrote function scale(v); v = v .* 2; end and expected the caller's array to be doubled, but it is unchanged after the call. On the other hand, push!(v, 1.0) and sort!(v) inside a function do change the caller's array.

What does the exclamation mark actually do, and what decides whether a function modifies the array I pass in or works on a copy?

Community replies 4

Re: Julia functions ending in !: why does push!(v, x) change my array but v = v .* 2 inside a function does not?

#2

The exclamation mark does nothing by itself. It is part of the function name and a naming convention: functions that modify one or more of their arguments, normally the first, end in !. sort(v) returns a new sorted array and leaves v alone; sort!(v) sorts v in place. The compiler does not enforce it, so a function without the mark can still mutate its arguments; it is just bad manners. By convention a mutating function still returns the modified object, as push! does.

Re: Julia functions ending in !: why does push!(v, x) change my array but v = v .* 2 inside a function does not?

#3

Julia passes arguments by sharing: the function receives a reference to the same array object, not a copy. What matters is the difference between mutating that object and rebinding the local name.

v = v .* 2 builds a new array and makes the local variable v point to it; the caller's variable still points to the old one. v .= v .* 2, or equivalently v .*= 2, writes the results into the existing array, so the caller sees them. v[1] = 0 and push!(v, x) are also mutations of the shared object.

Re: Julia functions ending in !: why does push!(v, x) change my array but v = v .* 2 inside a function does not?

#4

This only applies to mutable objects. Numbers, strings, tuples and instances of a plain struct are immutable, so a function can never change the caller's x = 5 through an argument; it has to return the new value and the caller assigns it: x = increment(x). For several results, return a tuple, return a, b, and destructure at the call.

If you need a field you can change in place, declare the type with mutable struct, or keep the value in a one-element container such as r = Ref(0) and update it with r[] += 1.

Re: Julia functions ending in !: why does push!(v, x) change my array but v = v .* 2 inside a function does not?

#5

A useful pattern is to provide both versions, with the allocating one built on the in-place one: scale!(v, k) = (v .*= k; v) and scale(v, k) = scale!(copy(v), k). In a loop that runs thousands of times, calling the ! version on a preallocated buffer avoids allocating a new array per iteration; for a vector of 1,000 Float64 values that is 8 kB saved on every call.

One caution: b = a does not copy an array either, so mutating b changes a. Use copy(a), and deepcopy for nested containers. And do not confuse the suffix with the prefix operator: !done is logical negation.

TEP COMMUNITY