Initiate a struct without its name but only by its type in local scope

Hi together,

i try to define a function which creates a new composite based on a copy of a given composite, but with a single field changed, and without using mutable structs.

I’ve attached a code snipped which tries to solve this with a string and eval, but I’ve realized that it is only valid in global scope. But maybe it helps to understand the intended functionality.

The final string which would be send to eval, defining a new stuct ChangeMe with a different input for its :y field, is as follows:

"out = ChangeMe(inStruct.x, newValue, inStruct.z, inStruct.s)"

So does anybody have suggestions how to solve this problem? Is there a possibility to initiate such a struct without calling its name, so instead of ChangeMe(x,y,z,s) something like initiateStruct(typeOfThatParticularStruct, args...)? The function is supposed to be general applicable, meaning I never know the name of the composite or how many fields it actually has.

Kind regards
Sebastian

Import Test

struct ChangeMe
   x::Int64
   y::Int64
   z::Int64
   s::String
end

function cpStructWithChangedField(inStruct, chField::Symbol, newValue)
   # type of struct
   inType = typeof(inStruct)

   # field count and names
   fc = fieldcount(inType)
   fn = fieldnames(inType)

   # create new struct via meta programming
   metaString = String[]

   push!(metaString,"out = " * string(inType)*"(")
   for field in 1:fc
      # if chFiled is found, apply new value
      if fn[field] == chField
         push!(metaString,"newValue")
      else
      # otherwise old value from original composite
         push!(metaString,"inStruct."*string(fn[field]))
      end

      # add separation symbol in string
      if field != fc
         push!(metaString,", ")
      else
      # no separation after last element
         push!(metaString,")")
      end
   end
   # combine array of strings to single string
   metaString = join(metaString)
   # eval >> not working, only GLOBAL scope!
   eval(Meta.parse(metaString))
   return out
end

# input composite
inp = ChangeMe(1,2,3,"Hello")
# expected composite
exp = ChangeMe(1,99,3,"Hello")
Test.@test cpStructWithChangedField(inp, :y ,99) == exp

If you just want the functionality, I believe the Accessors package solves the problem. If you want to learn how to do this yourself, I would recommend either trying to do it with a regular function or with a macro, rather than with Meta.parse and eval.

2 Likes

@GunnarFarneback Didn’t know that package. Works like a charm. I will tag your answer as solution, but would be still interested how to solve it with a regular function.

Thank you very much!

Try this:

function cpStructWithChangedField(inStruct, chField::Symbol, newValue)
    # type of struct
    inType = typeof(inStruct)
 
    # field count and names
    fc = fieldcount(inType)
    fn = fieldnames(inType)

    args = (field_name == chField ? newValue : getfield(inStruct, field_name)
            for field_name in fn)
    return inType(args...)
end
1 Like

That’s really cool. Didn’t know that you can just initiate a new struct by just calling the variable which stores the intended type. Thank you again for the effort and insights. I will close this. I can’t, but anyways, suggested the last answer as solution.