julia> mutable struct S x::Int end
julia> a = S(3)
S(3)
julia> b = a
S(3)
julia> b.x = 5
5
julia> a
S(5)
Where is it documented ? Every computer scientist is aware of the risks when writing b = a, and then modifiying elements of b, when a is an array; but seriously who knows that the pb is the same with mutable struct ?
You don’t imagine how much time I spent to discover and understand very subtle anomalies in a somewhat complex program, where some variables were cached in dictionaries, that evolved mysteriously because some of them were these evil mutable struct…
For variable a and expression b, a = b makes a refer to the value of b.
Almost all Julia users I assume, since it is the same semantics for all values. After a = b, no valid program should be able to distinguish a from b, ie a === b.
Incidentally, I wish we had evil as a syntax qualifier, as in
evil mutable struct S
x::Int
end
evil function foo(x::Int)
x + 1
end
I am still not quite sure what it would do though, but we could think of something.
In julia it is the same. That is, assignment of immutable struct is a copy. mutable struct means “pointer to heap-allocated struct with C layout, but everything managed by the julia garbage collector + allocator please”.
Structure layout is julia tries to be mostly C compatible (in-order, same alignment rules, …). Mutable structs, as “proper reified objects”, have an identity, a type-tag and a memory address (except if the compiler figures out that it can get away without assigning an address because nobody uses it…).
Fair enough. I think Java has similar behavior but they use the keyword class instead. If you want to think in terms of C structs, maybe the most sane analogy is that the problem is not the struct itself (mutable or immutable), but that all variables in Julia are NOT names of positions of memory, but instead, names of pointers to the memory. So, if your swap two variables i.e., a, b = b, a you just swap pointers instead (often you do not even pay the price of a pointer swap, the compiler elides this).
This is the best I can do using concepts of C, the truly best would be learning how Julia has bindings and not variables. @yuyichao has been answering this question for years. If they give a reference (maybe to a post of them), I will always link it whenever I see a variation of this question.
Oh that’s a delightful idea. Pretty easy to cook up at least on the documentation level as well:
julia> macro evil(ex)
quote
"""
$($(repr(ex)))
This is evil, avoid it!
"""
$ex
end |> esc
end
@evil (macro with 1 method)
julia> @evil mutable struct S
x::Int
end
S
help?> S
search: S Sys Set SVD sum sin sec Some svd sum! step stat sqrt sort skip size sinh sind sinc sign show seek sech secd svd! Schur
:(mutable struct S
#= REPL[1]:2 =#
x::Int
end)
This is evil, avoid it!
julia> @evil function foo(x::Int) = x + 1
ERROR: syntax: unexpected "="
Stacktrace:
[1] top-level scope at none:1
julia> @evil foo(x::Int) = x + 1
foo
help?> foo
search: foo floor pointer_from_objref OverflowError RoundFromZero unsafe_copyto! functionloc StackOverflowError @functionloc
:(foo(x::Int) = begin
#= REPL[4]:1 =#
x + 1
end)
This is evil, avoid it!
Since you asked, FWIW, struct assignment in Go is a copy. I suppose C and C++ are still popular and they behave the same. You would assign pointers (or references in C++) to have two variables reference the same instance.
I think that their “struct assignment” is not Julia’s =, it is closer in spirit to something like .=.
I think the misunderstanding in this topic comes from some languages allowing sophisticated semantics for a = b, with eg C++ allowing full overloading so that literally anything can happen, including side effects etc.
In contrast, Julia’s = is simple: all a = b does is ensure a === b — no tricks, just a few corner cases like type declarations.
example
julia> function f(x)
local y::Float64
y = x
x ≡ y
end
f (generic function with 1 method)
julia> f(1)
false
julia> f(1.0)
true
Julia language has a beautiful design, and is quick. That’s why I use it, without understanding (ot trying to understand) its internal structure. I think (and hope) I’m not the only one in this situation. Something like :
the truly best would be learning how Julia has bindings and not variables. @yuyichao has been answering this question for years
Sure using two names for an array, a dictionary, and so on, is hazardous, but any programmer with some experience knows that. At the opposite any programmer with some experience is aware of the composite data type struct when using the C language, that’s why I think Julia documentation should be completed.
Since you just encountered this problem, you still remember why you found it confusing, so it would be great if you could contribute to the docs.
I suspect that the Types section may not be the best place for this, since it is really general to assignment. Note that the Noteworthy differences from C/C++ has
Julia arrays are not copied when assigned to another variable. After A = B , changing elements of B will modify A as well. Updating operators like += do not operate in-place, they are equivalent to A = A + B which rebinds the left-hand side to the result of the right-hand side expression.
and
Julia values are not copied when assigned or passed to a function. If a function modifies an array, the changes will be visible in the caller.
Perhaps these could be reworded from “arrays” to “containers (including arrays and mutable structs)”.
Yes, that’s all true. I’m not making a case for or against how Julia works, just answering the question. Every language is different. One of the most important features to understand when learning a new language is its uses of value vs reference semantics.
Julia makes that easy. There are no copy constructor or move constructors etc. Assignment is always a no-op. It always just binds the symbol to the object on the right hand side. No implicit copies. Same when passing arguments to functions.