Calling a function to change a struct?

I am trying to use a function in order to modify a struct that is passed to it as a variable. Below is a minimal bit of code I have put together to illustrate my confusion.

struct a
    x::Int
    y::Int
end

function switchVariables!(b::a)
    newB = a(b.y, b.x)
    b = newB
    println(b)
end

b = a(1, 2)
println(b)
switchVariables!(b)
println(b)

The two println calls outside of the switchVariables! function return a(1, 2), whereas the one inside the function returns a(2, 1). So it looks like the proper operation is being done to b inside the function, but not outside of it. I’ve poked around with variants like b = deepcopy(newB) but have not had any luck. Would someone be able to explain to me what is going on here? I recognize that for this minimal example, it would probably be better to just return the desired modified struct rather than explicitly trying to modify b - while the same might be true for the actual code that I’m working on, I would at least like to understand why the above code doesn’t work the way I would naively expect it to. Thanks!

You must pass a reference to your struct to your fuction.

Type

?Ref

at the Julia prompt.

  1. The struct created is immutable, you cannot modify it.

  2. Doing b = new struct, inside the function, is just assigning the label b to a new struct.

  3. That label is local to the scope of the function.

You need to use b = switch(b) to assign the label b of the outer scope to the new struct created in the function. But that is not mutating anything. Usually that’s fine and the way to go, but you can also use a mutable struct and get actually the behavior you were expecting.

Ps. Think of an immutable struct as a number. You are doing:

function switch(b)
    newb = 2
    b = newb
    return b
end

b = 1
switch(b)

You will see that b does not change outside switch, because the label b of the outer scope was bound to the value 1 and that was not changed in that scope. You need to do here b = swich(b). The same goes for immutable objects, like your struct. In any case, you are not modifying the immutable number 1, which is an immutable value, corresponding to your struct.

7 Likes

To get the behaviour you ask for, you should do this:

mutable struct a
    x::Int
    y::Int
end

function switchVariables!(b::a)
   b.x, b.y = b.y, b.x
end

Unless you declare the struct as mutable it will be immutable, i.e. the content can’t be modified. This has a number of advantages for performance. But if you want modifiable struct you must explicitly use the mutable keyword. The way you wrote your function would have worked if arguments were “called by name”, Julia does not support that.

4 Likes
global b = newB

Try JuliaObjects/Accessors.jl: Update immutable data (github.com)

If you can get @set to turn iteration-based destructuring assignment b.x, b.y = b.y, b.x to instantiation a(b.y, b.x) then please provide the code because AFAIK Accessors.jl can’t do that yet. It’s not possible to do the changes separately because both the previous x and y fields must be retrieved before replacing either.

julia> @set b.x = b.y
a(2, 2)

julia> @set b.x, b.y = b.y, b.x
(2, 1)

EDIT: Answered my own question, but tbf, it is a longstanding missing feature in Accessors.jl and the predecessor Setfield.jl. setproperties is only mentioned in the docs under an explicitly experimental mapproperties, so it could easily be gone in minor revisions. I don’t know the internal workings of why @set can’t just do what setproperties can.

julia> b
a(1, 2)

julia> setproperties(b, (x = b.y, y = b.x))
a(2, 1)

Note also that Accessors, or Setfield, do not mutate immutable data, really, they are just convenience macros to redefine new immutable structs. The issue the OP has would be the same with it:


julia> using Accessors

julia> struct A
           i
           j
       end

julia> function set_i(a)
           a = @set a.i = 0
           return a
       end
set_i (generic function with 1 method)

julia> b = A(1,1)
A(1, 1)

julia> set_i(b)
A(0, 1)

julia> b # doesn´t  change
A(1, 1)
1 Like

Yeah you’re absolutely right!