Using Accessors to mutate an incomplete initialized struct

Hi,
I am trying to write a method for finalizing incomplete initialization of a struct. I want to avoid using a mutable struct.
I found Setfield.jl or Accessors.jl which seem to fulfill my requirements, but it fails somehow.
Is there a simple fix for this or is there an alternative ?

Here is a mwe.jl:

using Accessors

struct Foo{T, N}
  a::Array{T, N}
  b::Int

  # incomplete ctor & finalize
  Foo(a::Array{T, N}) where {T, N} = begin
    println("incomplete")
    this = new{T, N}(a)
    return complete(this)
  end

  # forward ctor
  Foo(a::Array{T, N}, args...) where {T, N} = begin
    println("forward $(args)")
    return new{T, N}(a, args...)
  end
end

complete(f::Foo{T, N}) where {T, N} = begin
  @set f.b = 100
  @show f.b  # <== something is wrong
  return f
end

function main()
  @show Foo(ones(Float32, (1, 1)), 50)  # OK
  @show Foo(ones(Float32, (1, 2)))  # NOK
  return
end

main()

Output:

$ julia mwe.jl
forward (50,)
Foo(ones(Float32, (1, 1)), 50) = Foo{Float32, 2}(Float32[1.0], 50)
incomplete
forward (100,)
f.b = 0
Foo(ones(Float32, (1, 2))) = Foo{Float32, 2}(Float32[1.0 1.0], 0)

Sorry this might be a bit late, but a few things. First, try not to do this! there is probably a better way than having undefined fields. :slight_smile:

Second, you are not actually assigning a new f. But this version gives the result you expect:

complete(f::Foo{T, N}) where {T, N} = begin
  f = @set f.b = 100
  @show f.b  # <== should be 100 now
  return f
end

Remember these are immutable objects.

2 Likes

LazilyInitializedFields.jl might be useful.

4 Likes

@kristoffer.carlsson thanks, LazilyInitializedFields perfectly solves the issue.

Updated / working code for posterity:

using LazilyInitializedFields

@lazy struct Foo{T, N}
  a::Array{T, N}
  @lazy b::Int

  # incomplete ctor & finalize
  Foo(a::Array{T, N}) where {T, N} = begin
    println(">> incomplete")
    this = new{T, N}(a)
    return complete(this)
  end

  # forward ctor
  Foo(a::Array{T, N}, args...) where {T, N} = begin
    println(">> forward $(length(args)) $args")
    return new{T, N}(a, args...)
  end
end

complete(f::Foo{T, N}) where {T, N} = begin
  @init! f.b = 100
  @show f.b
  return f
end

function main()
  @show Foo(ones(Float32, (1, 1)), 50)
  @show Foo(ones(Float32, (1, 2)))
  return
end

main()

Output:

$ julia mwe.jl
>> forward 1 (50,)
Foo(ones(Float32, (1, 1)), 50) = Foo{Float32, 2}(Float32[1.0], 50)
>> incomplete
f.b = 100
Foo(ones(Float32, (1, 2))) = Foo{Float32, 2}(Float32[1.0 1.0], 100)