Immutable types

I have an immutable struct designed like this:

struct crystal
    lVecs::Matrix{Float64}
    nBasis:: Vector{Int64}
    latpar::Float64
    atomicBasis::Vector{Matrix{Float64}}
    coordSys::Vector{String}
end

and an instance of that type called myCrystal. I’m having a hard time understanding why the following is allowed:

myCrystal.nBasis .= [1,4,8]

but this is not:

myCrystal.nBasis = [1,4,8]

What aspects exactly of an immutable type are immutable? I suppose I couldn’t change the type of variable or the size/shape, but as long as I’m only mutating values I’m OK?

You can’t replace any of the fields of an immutable type. Hence, when you do

myCrystal.nBasis = [1,4,8]

it fails, because you are trying to take a new array (namely [1,4,8]) and assigning it to a field of an immutable struct.

The thing that may confuse you is that immutable structs can have mutable structs as fields. So, in your example, you can’t mutate a crystal since it’s immutable, but you CAN mutate a Vector{Int64}, and therefore you can mutate crystal.nBasis, including changing its size and data content.

It’s not possible to declare a struct “recursively immutable”.

5 Likes

It’s never possible to modify an object that is an instance of an immutable type. These kinds of objects are sometimes also called immutable objects.

The part that I think may be confusing here is that immutable objects may contain references. A reference may refer to a mutable object, which may be mutated even though a reference to it is contained in some immutable object.

The terminology is perhaps confusing/imprecise because the same exact situation will sometimes be described as a mutable object being contained in another object, while on another occasion one would say that just the reference is what’s contained in the parent object.

I think it is possible to enforce isbits status in an inner constructor, though, thus ensuring that all instances of the type are “recursively immutable”. (Except that incomplete initialization may be used to punch a hole through that, maybe.)

1 Like

You cannot modify the components of an immutable type but if a component is mutable you can modify the components of this component. Thus:

julia> struct A; x; end

julia> mutable struct B; y; end

julia> a=A(B(3))
A(B(3))

julia> a.x = B(7)    # Bad! Cannot redefine a component (x here) of an immutable struct 
ERROR: setfield!: immutable struct of type A cannot be changed
Stacktrace:
 [1] setproperty!(x::A, f::Symbol, v::B)
   @ Base ./Base.jl:39
 [2] top-level scope
   @ REPL[6]:1

julia> a.x.y = 7    # Good! Redefinition of components of a.x (a mutable struct) is OK
7

Similarly, if you go back to your original example, you cannot redefine myCrystal.nBasis but you can redefine the components of myCrystal.nBasis. That is, this is OK:

myCrystal.nBasis[1] = 1 
myCrystal.nBasis[2] = 4 
myCrystal.nBasis[3] = 8

which is equivalent to myCrystal.nBasis .= [1,4,8].

Notice that trying to change the size of the vector of myCrystal.nBasis is not possible due to the immutability of the crystal struct.

From the manual Types · The Julia Language

  • For composite types, this (immutability) means that the identity of the values of its fields will never change. When the fields are bits types, that means their bits will never change, for fields whose values are mutable types like arrays, that means the fields will always refer to the same mutable value even though that mutable value’s content may itself be modified. (emphesis added)
1 Like