Verse Wiki — the Verse handbook for Blueprint authors
Extension · EXTRA

When a struct Won't Change: Immutable Data Patterns in Practice

Add a var field to a struct and the compiler hurls error 3607 at you — and it isn't "not yet", it's "not ever". This piece collects the three ways out from community pitfall threads, gives the complete pattern for "updating one item in a struct array", and explains along the way why immutability is actually a gift the struct is handing you.

1. The Crash Site: Error 3607

The forum thread "Variable property inside Struct" records a classic wall-hit: someone wanted a changeable field (one you could wire a Set node to) on a struct, wrote var Count:int = 0, and got an unambiguous error back — Compile flags red on the spot:

error_3607.verse
# ✗ Compile error 3607:
# Structs may not contain mutable members
broken := struct:
    var Count:int = 0

More important is the verdict in the replies: this is not a temporary limitation but a deliberate design. In Verse, a struct's whole role is a bundle of data that never changes — pure data, no object identity, whole-parcel copies that isolate by nature. Around the same time, Epic engineer Andrew Scheidecker, answering "can structs have functions", would only say it is desired but has no concrete plan. In other words: don't wait for a version update to rescue you — learning to live with "can't change it" is the real answer.

2. Three Ways Out

▢ Way 1: replace, don't modify (functional update). Reframe "modify" as "replace": whenever you want to change a field, rebuild the whole parcel from the old one — the field being changed gets its new value, the rest are copied over verbatim. Structs are meant to hold little anyway (that is exactly their role), so copying the whole parcel costs next to nothing.

▢ Way 2: upgrade to a Blueprint class (class). If this data was always meant to have state that changes plus behavior (health drains, cooldowns tick), it probably should never have been a struct — switch to a Blueprint class and you instantly gain Set-able fields and functions; and if every field has a default, add <concrete> so you can build instances with the empty archetype my_class{}, then pair it with @editable (that little Instance Editable eye on a variable) to expose it in the level's Details panel for tweaking.

▢ Way 3: wrap the struct inside a class. The middle road: the data still lives in a struct, but a Blueprint class wraps around it and packages the whole "rebuild the parcel" business into one of its functions — from the outside all anyone sees is a handy call like Backpack.AddOne("Wood"), with no rebuild code hand-written all over the place.

immutable_update.verse
item_stack := struct:
    ItemName:string = ""
    Count:int = 0

# To modify = build a new parcel: copy the other fields, swap in the new value
(Stack:item_stack).AddOne():item_stack =
    item_stack{ItemName := Stack.ItemName, Count := Stack.Count + 1}

Reading the code: this uses an extension method (Lesson 19): a struct cannot contain functions, but (Stack:item_stack).AddOne() is a lone function hung off the outside of the struct that adds the "behavior" back — it copies every other field of the incoming parcel Stack, bumps only Count by one, and builds and returns a brand-new item_stack. Calling it feels exactly like a built-in member function, and it is also the only route in Verse for giving a struct behavior.

3. The Pattern: Updating One Item in a struct Array

The most common need in practice: the backpack is an []item_stack array and you want +1 on the "Wood" slot. Because the element you take out is a copy, changing it directly gets you nowhere; the standard move is to rebuild the entire array — run a ForEach over the old array, swap the item being updated for a freshly rebuilt parcel, carry the rest over as-is, and finish with one Set that replaces the whole thing:

backpack_device.verse
using { /Fortnite.com/Devices }
using { /UnrealEngine.com/Temporary/Diagnostics }

item_stack := struct:
    ItemName:string = ""
    Count:int = 0

(Stack:item_stack).AddOne():item_stack =
    item_stack{ItemName := Stack.ItemName, Count := Stack.Count + 1}

backpack_device := class(creative_device):

    var Stacks:[]item_stack = array{
        item_stack{ItemName := "Wood", Count := 30},
        item_stack{ItemName := "Brick", Count := 12}}

    # +1 the slot whose name matches: rebuild the array, replace it whole
    AddOneTo(Target:string):void =
        var Result:[]item_stack = array{}
        for (Stack : Stacks):
            if (Stack.ItemName = Target):
                set Result += array{Stack.AddOne()}
            else:
                set Result += array{Stack}
        set Stacks = Result

    OnBegin<override>()<suspends>:void =
        AddOneTo("Wood")
        Print("Backpack updated")

Reading the code: AddOneTo is the heart of it: it opens an empty array Result and walks the backpack Stacks with a ForEach; whenever a slot's name matches the target, it swaps in the new parcel built by Stack.AddOne() and appends that to Result; everything else is appended as-is; once the loop is done, one Set replaces Stacks wholesale with Result. In OnBegin (the equivalent of Event BeginPlay), a single AddOneTo("Wood") call bumps the Wood slot by one.

It looks roundabout at first: rebuilding a whole array just to change one number? But this code has zero hidden state — every step produces a new value, old values stay available to look back at, and anyone holding an item_stack never has to worry that "someone somewhere is quietly mutating it". Write this way for a while and you'll find the "replace, don't modify" mindset leaves you with astonishingly few bugs.

4. Why Immutability Is a Gift

Verse forces structs to be immutable and collects three very real dividends in return. First, isolation by copy: whole-parcel copies plus "can't change it" mean you can hand a struct to any function or store it in any container and no far-away piece of code will ever quietly rewrite it — "everyone sharing one editable copy", that great incubator of bugs, is simply taken off the board. Second, a natural fit for rollback: Verse's failure machinery works as "try the whole path once; only if every step goes through does it take effect, otherwise pretend nothing happened"; a parcel of data that never changes has no edits to undo, which makes it the cleanest partner for that rolling-back mechanism (<transacts>, detailed in Lesson 22). Third, save-friendly: a struct can carry <persistable> and go straight into cross-session saves (SaveGame) as stored data — pure data with no object identity and no mutation saves and loads without ambiguity.

So next time 3607 hits, don't rush to curse Compile: it is only reminding you that this data either belongs to "replace, don't modify" (stay a struct) or was always a stateful object (move to a Blueprint class). Get that straight and your type choices won't go wrong.

You want to add 1 to the Count field of an item_stack struct ("changing" one field inside a parcel that can't change). What is the correct move in Verse?

Sources & Further Reading

Compiled from the Epic developer forums and official documentation:

Forum: Variable property inside Struct (error 3607 in the wild) ↗

Forum: Structs and Enums in Verse (Epic engineer's reply) ↗

Official docs: Struct in Verse ↗