Change a struct in place

I made a struct with Float32

julia> struct Foo 
       x::Float32
       end

julia> foo = Foo(1)
Foo(1.0f0)

But now I realized Float32 isn’t big enough and I want Float64, and I want the change to propagate to the instantiated foo object (not just creating a bigger NewFoo). I believe this isn’t supported.

julia> Foo.types
svec(Float32)

julia> typeof(Foo.types)
Core.SimpleVector

julia> ismutable(Core.SimpleVector)
true

julia> Foo.types[1]
Float32

julia> Foo.types[1] = Float64
ERROR: MethodError: no method matching setindex!(::Core.SimpleVector, ::Type{Float64}, ::Int64)
Stacktrace:
 [1] top-level scope
   @ REPL[10]:1 

Is there an (unsupported, hacky) way to do it anyway?

1 Like
mutable struct Foo
    x::Float32
end

I’m looking for something different that changes the type itself, rather than the values of the instances.

julia> mutable struct Foo
           x::Float32
       end

julia> Foo.types[1] = Float64
ERROR: MethodError: no method matching setindex!(::Core.SimpleVector, ::Type{Float64}, ::Int64)

Ah sorry for not reading your question more closely.

No, I do not believe that is possible. Of course, you can always re-define the type entirely to allow for other types.

1 Like

Or you could have defined a parametric type from the start:

struct Foo{T}
    x :: T
end
3 Likes

Not sure it is what you need, but just in case, check this package by @FedericoStra

1 Like

200
I mean because of parametric types.

1 Like

Parametric types make multiple concrete types, not one type that can be changed in place. For example say I’ve made a struct, instantiated it, moved it around in my program. Now I want to add a new field to the type and to existing instances, in place.

I don’t think there’s easy way to do this. Changing the definition of a type, would require finding every instance of that type (including in boxes and closures, etc) and also recompiling every method that uses it.

If you are just prototyping code, and don’t care that it runs a bit slower, you could do this:

struct Foo
    x::Any
end
foo = Foo(1)

Base.getproperty(v::Foo, s::Symbol) = Float32(getfield(v, s))
typeof(foo.x) # Float32

Base.getproperty(v::Foo, s::Symbol) = Float64(getfield(v, s))
typeof(foo.x) # Float64

This does not change what is actually stored in foo, but it does change what it appears to be.

2 Likes

Sorry, my message was meant as a direct reply to @rafael.guerra because to me it seems that package kind of mimics parametric types, or rather when you define a struct in a manner that allows any field type so that you can dispatch on the parametric type.

I’m unsure that the solution proposed by @Per actually solves your problem though:

But now I realized Float32 isn’t big enough and I want Float64

because you wouldn’t be able to change the underlying concrete type of the field, just how it appears “outside” (as he mentions himself).

You should really try the route of parametric types (like @Henrique_Becker said) as in

mutable struct Foo{T}
    x::T
end

And then simply recast your data whenever necessary:

a = Foo(Float32(1))

# later
a = Foo(Float64(a.x))

You can even write convenience functions for that and if you need to recast an array of your data, use list comprehensions, etc.

Foo(T::Type,foo::Foo) = Foo(T(foo.x))

# Float32
r = Foo.(Float32.(rand(3)))

# Float64
r = Foo.(Float64,r)

There are probably better ways to do this still.

Edit:
You should probably make the struct mutable because it seems to me that you want to change the contents of the field after the fact.

Structs in Julia are not meant to be used like structs in Matlab for example. Structs are meant to represent user-definable types on which you want to dispatch to do very specific things. That’s why you can’t change their definition afterwards but you can “always” dispatch on parametric types to do more specific things.

If you want a container that has a mutable number of fields, you should use Dicts probably, shouldn’t you?

1 Like

For Julia type system beginners, would you mind commenting the problem with code below? Thank you.

using RedefStructs

@redef struct Foo
    x::Float32
end

y = Foo(1.0).x    #  1.0f0
julia> typeof(y)
Float32

@redef struct Foo
    x::Float64
end
y = Foo(1.0).x
julia> typeof(y)
Float64

I think this might an XY problem. Instead of asking “how can I change the definition of a struct in Julia?”, it might be more appropriate to ask “what Julia construct can be used in place of the re-definable structs of [some other language]?” to which the answer is:

This would be a lot easier than the solution that I gave above, and not have any worse performance.

3 Likes

I think dicts have a different layout than structs and different performance characteristics.

The point of this question is about a long-running interactive system where I define a struct as normal, and then change the application without restarting. That is, using struct as intended, and then evolving the application – just without restarting. Consider large interactive Lisp systems to compare.

I’m not a CLOS person but I believe it has this feature.

http://www.lispworks.com/documentation/HyperSpec/Body/f_mk_i_1.htm#make-instances-obsolete

http://www.lispworks.com/documentation/HyperSpec/Body/f_upda_1.htm

I think for this very specific example RedefStructs.jl is overkill because you can alleviate the problem of the internally strictly defined type of field x via the use of parametric types.
If I understand RedefStructs.jl correctly (and please, someone correct me if I am wrong), it defines new surrogate structs with the correct type signature with a new name anyway and simply reassigns the name so to you it appears as if you have redefined the struct.
Under the hood what it is doing is equivalent to you writing

struct FooA
    x::Float32
end

struct FooB
    x::Float64
end

But it additionally updates the name table so that Foo refers to FooB instead of FooA. In this specific case though, you can use parametric types and evade the problem of defining multiple instances of Foo.

Of course, changing the fieldnames or the number of fields in a struct cannot be done after the fact. To reiterate, though, that’s not what structs are meant to do.

If you want to change the application of your programm while it is running, it makes more sense to have defined an appropriate new type and then convert your old type into the new type by, for example, filling fields appropriately.
That way you can still effectively use dispatch.

Edit: Of course, if you very frequently “evolve” your program that might become ineffictive at some point, I guess :man_shrugging:

Of course this is horrible for performance, but I don’t think take “in place” can mean anything except that a pointer is kept if the type of the field changes. If you need to add more fields of different types, use vectors of union types.

What people often use for this is some kind of database backend. That also makes it easier to recover from accidents and power outages.

I agree and just want to heavily emphasize again that you really shouldn’t define types like this for performance reasons.
In case it’s necessary, you can restrict parametric types:

mutable struct Foo{T<:AbstractFloat}
    x::T
end

Foo(3) # ERROR: MethodError: no method matching Foo(::Int64)

Foo("string") # ERROR: MethodError: no method matching Foo(::String)

Foo(3.0) # Foo{Float64}(3.0)

Sure, but in that case you cannot change the type of an instance of the object to another type.

But, again, nobody should do that anyway.

1 Like