Assign a struct variable to a new variable

Hi, I defined a struct. Then I create a new variable like
A = Pos(“id1”, 1, 2, 1, 2)
Then I assign the A to another variable B.
B = A;
Next, I tried to modify B,
B.posX1 = 3
B.posY1 = 4

However, when I check A, I find A is also modified to B.
A and B are same after I modified B. It’s weird and dangerous. I think julia should create a new memory for B. But A and B are using same memory space.

What should I do if I want to create a new memory space for B? Thanks.

Pos(“id1”, 3, 4, 1, 2) # A
Pos(“id1”, 3, 4, 1, 2) #B

mutable struct Pos
id
posX1::Int32
posY1::Int32
posX2::Int32
posY2::Int32
end

This is always how = works in Julia, in every case. If you do a = b, then a and b are the same value, and modifying one will modify the other.

If you want to copy b, then you need to be explicit about that. First, you need to define a way to copy your struct:

function Base.copy(p::Pos)
  Pos(p.id, p.posX1, ...)
end

and then call it:

a = copy(b)
1 Like

Thanks rdeits. It’s reasonble. I will do create a new variable for it.

Sorry for the basic question treated here many times, but why are scalars different?

scalar are just special case of immutable, any immutable behaves like “scalars”

It is in some sense, yes, and it is even more confusing when we notice that immutable variables behave “differently” (like scalars).

But note that this is the exact same behavior in Python:

In [5]: a = [1, 2, 3]                                                                                                                  

In [6]: b = a                                                                                                                          

In [7]: b[0] = 0                                                                                                                       

In [8]: a                                                                                                                              
Out[8]: [0, 2, 3]

In [9]: a = 1                                                                                                                          

In [10]: b = a                                                                                                                         

In [11]: b = 2                                                                                                                         

In [12]: a                                                                                                                             
Out[12]: 1

Thus, it is something that one has to understand and get used to, not only in Julia. The difference between mutable and immutable objects and when one or other are useful, is important in code design. And soon enough you will face the additional complication on mutability vs. assignment, which appears as frequently as a new user question.

They’re not different, at least not in terms of the observable behavior of the program.

If you do a = b, then a and b are the same value. Any mutations of b affect a as well. It just happens that for immutable objects (i.e. numbers), you can’t mutate b (or a), so the question of whether mutating one affects the other never arises.

2 Likes

Maybe it’s just the classic confusion between reassignment of a variable and mutation of an instance; this is common for people coming from languages where “a variable” is basically a named instance. Although the separation of variable and instance along with mutability/immutability is a safer and simpler abstraction than pointer logic, it’s not entirely straightforward, to be fair.

It helped me to first imagine an immutable instance to have a primary sequence of bytes, and that’s exactly how they are identified; if two immutable instances have the same sequence, they are the same, even if you constructed them separately. A mutable instance however is an address to a remote data, and it’s identified by that address; separate copies of equal data at separate addresses are different instances. This works out for immutable structs with mutable fields, because its primary sequence only contains the address of the mutable field instance, not its remote data. When you mutate an instance, you change the remote data, not its address, so the identity remains the same.

A variable is just a named reference to an instance so that your code has access; 1 instance can have many references. Assignments are how you make a reference to an instance. You use a variable to access an instance in order to mutate it, e.g. am.num = 1, arr[2] = 5, push!(arr, 3), but that is not the same as reassigning that variable to a possibly different instance e.g. am = MutInt(1). It might look similar sometimes, and indeed in am.num = 1 you are reassigning the field .num of the instance to mutate it, but the variable am itself is just used for access, not being reassigned.

By this point, it should be clear that B = A in the OP accesses the variable A to get an instance, then assigns the same instance to variable B. That instance has 2 variables A and B, and there was only 1 instance to mutate, no matter which variable is used to access it. To assign a separate instance to B, you need to create a separate instance, e.g. B = copy(A) or B = A + 0.

Open to see behavior of variables with immutable instances. Note that no instance is ever mutated.
julia> a = 3
3

julia> b = a # access variable a, get instance 3, assign 3 to variable b
3

julia> a === b # same instance has 2 variables
true

julia> a += 1 # compute a+1, then reassign result to a
4

julia> a === b # different instance because immutable value differs
false

julia> b = 4 # make instance 4, assign 4 to variable b
4

julia> a === b # same instance because immutable value is same
true
Open to see behavior of variables with mutable instances. Note that the reassignment behavior is the same as immutable ones, the difference is mutability and identity rules.
julia> mutable struct MutInt num::Int end

julia> Base.:+(x::MutInt, y::Int) = MutInt(x.num+y)

julia> am = MutInt(3)
MutInt(3)

julia> bm = am # access variable am, get instance, assign it to variable bm
MutInt(3)

julia> am === bm # same instance has 2 variables
true

julia> am.num = 1 # access variable am, mutate its instance by reassigning its field num
1

julia> am === bm # 2 variables still access the same instance
true

julia> am += 1 # compute am+1, then reassign result to am
MutInt(2)

julia> am === bm # different instance because mutable addresses differ
false

julia> bm.num = am.num # mutate so fields are equal
2

julia> am === bm # different instance because mutable addresses differ
false
2 Likes