Mutabilities.jl is a type-level tool for describing mutabilities and ownership of objects in a composable manner.
See more in the documentation.
readonly: create read-only viewfreeze,freezevalue,freezeindex,freezeproperties: create immutable copiesmelt,meltvalue,meltindex,meltproperties: create mutable copiesmove!: manually elides copies with freeze/melt APIs.
The most easy-to-use interface is readonly(x) which creates a
read-only "view" to x:
julia> using Mutabilities
julia> x = [1, 2, 3];
julia> z = readonly(x)
3-element readonly(::Array{Int64,1}) with eltype Int64:
 1
 2
 3
julia> z[1] = 111
ERROR: setindex! not defined for Mutabilities.ReadOnlyArray{Int64,1,Array{Int64,1}}Note that changes in x would still be reflected to z:
julia> x[1] = 111;
julia> z
3-element readonly(::Array{Int64,1}) with eltype Int64:
 111
   2
   3Use freeze(x) to get an independent immutable (shallow) copy of
x:
julia> x = [1, 2, 3];
julia> z = freeze(x)
3-element freeze(::Array{Int64,1}) with eltype Int64:
 1
 2
 3
julia> x[1] = 111;
julia> z
3-element freeze(::Array{Int64,1}) with eltype Int64:
 1
 2
 3freeze can be reverted by melt:
julia> y = melt(z)
3-element Array{Int64,1}:
 1
 2
 3It returns an independent mutable (shallow) copy of y.  Thus, y
can be safely mutated:
julia> y[1] = 111;
julia> z
3-element freeze(::Array{Int64,1}) with eltype Int64:
 1
 2
 3Julia's view is dangerous to use if the indices can be mutated after
creating it:
idx = [1, 1, 1]
x = view([1], idx)
x[1]  # OK
idx[1] = 10_000_000_000
x[1]  # segfaultThis can be avoided by freezing the index array:
view([1], freeze(idx))Note that readonly is not enough.
freeze and melt work both on indices (keys) and values.  It is
possible to create an append-only vector by freezing the values:
julia> append_only = freezevalue([1, 2, 3]);
julia> push!(append_only, 4)
4-element freezevalue(::Array{Int64,1}) with eltype Int64:
 1
 2
 3
 4
julia> append_only[1] = 1
ERROR: setindex! not defined for Mutabilities.AppendOnlyVector{Int64,Array{Int64,1}}It is possible to create a shape-frozen vector by freezing the indices:
julia> shape_frozen = freezeindex([1, 2, 3])
3-element freezeindex(::Array{Int64,1}) with eltype Int64:
 1
 2
 3
julia> shape_frozen .*= 10
3-element freezeindex(::Array{Int64,1}) with eltype Int64:
 10
 20
 30
julia> push!(shape_frozen, 4)
ERROR: push! on freezeindex(::Array{Int64,1}) not allowedUsing freeze and melt at API boundaries is a good way to ensure
correctness of the programs.  However,
until the julia compiler gets a borrow checker
and automatically elides such copies, it may be very expensive to use
them in some situations.  Until then, Mutabilities.jl provides
an "escape hatch"; i.e., an API to let the programmer declare that
there is no sharing of the given object:
julia> z = freeze(move!([1, 2, 3]))  # no copy
3-element freeze(::Array{Int64,1}) with eltype Int64:
 1
 2
 3
julia> melt(move!(z))  # no copy
3-element Array{Int64,1}:
 1
 2
 3This allows Julia programs to compose well, without defining immutable
f and mutable f! variants of the API and without documenting the
particular memory ownership for each function.
For example, melt is simply defined as
melt(x) = meltvalue(move!(meltindex(x)))move! can be useful when, e.g., input values can be re-used for
output values:
julia> function add(x, y)
           out = melt(x)
           out .+= y
           return freeze(move!(out))
       end;
julia> x = ones(3)
       y = ones(3);
julia> z = add(move!(x), y)  # no allocations
3-element freeze(::Array{Float64,1}) with eltype Float64:
 2.0
 2.0
 2.0
julia> melt(move!(z)) === x  # `x` is mutated
true(Note: Above example intentionally violates the rule for using
move! to show how it works.  After add(move!(x), y), it is not
allowed to use x, as done in the last statement.)
AbstractArrayAbstractDictAbstractSet- Data types
("plain 
struct") 
Static arrays are converted to appropriate types instead of the wrapper arrays:
julia> using StaticArrays
julia> a = SA[1, 2, 3]
3-element SArray{Tuple{3},Int64,1,3} with indices SOneTo(3):
 1
 2
 3
julia> melt(a)
3-element Array{Int64,1}:
 1
 2
 3
julia> meltvalue(a)
3-element MArray{Tuple{3},Int64,1,3} with indices SOneTo(3):
 1
 2
 3
julia> freeze(MVector(1, 2, 3))  # or freezevalue
3-element SArray{Tuple{3},Int64,1,3} with indices SOneTo(3):
 1
 2
 3Mutabilities.jl is aware of mutability of each field arrays wrapped in struct arrays:
julia> using StructArrays
julia> x = StructArray(a = 1:3);  # x.a is not mutable
julia> y = melt(x)
3-element StructArray(::Array{Int64,1}) with eltype NamedTuple{(:a,),Tuple{Int64}}:
 (a = 1,)
 (a = 2,)
 (a = 3,)
julia> y.a
3-element Array{Int64,1}:
 1
 2
 3
julia> z = freeze(StructArray(a = [1, 2, 3]))
3-element freeze(StructArray(::Array{Int64,1})) with eltype NamedTuple{(:a,),Tuple{Int64}}:
 (a = 1,)
 (a = 2,)
 (a = 3,)
julia> z.a
3-element freeze(::Array{Int64,1}) with eltype Int64:
 1
 2
 3