Skip Navigation

Can someone explain to me the advantage of mutable objects?

It makes the code icky and hard to debug, and you can simply return new immutable objects for every state change.

EDIT: why not just create a new object and reassign variable to point to the new object

27 comments
  • So your writing a game. This game has what I'm going to call "entities" which are the dynamic NPCs and such objects. So these objects are most easily conceptualized as mutable things. Why mutable? Well they move around, change states depending on game events ect. If this object is immutable you'd have to tie the in world representation to a new object, constantly just because it moved slightly or something else. This object is mutable not just because it's easier to understand but there are even efficiency gains due to not needing to constantly create a new version just because it moved a little bit.

    In contrast the object which holds the position data (in this case we'll have 3 doubles x, y, z) makes a lot of sense as an immutable object. This kind object is small making it cheap to replace (it's just 3 doubles, so 3*64 bits or a total of 24 bytes) and it's representing something that naturally makes sense as being immutable, it's a set of 3 numbers.

    Now another comparison your typical dynamic array type container (this is your std::vector std::vec ArrayList and friends). These are mutable objects mainly due to efficiency (it's expensive to copy the contents when adding new values) yet they also are easier to conceptualize when mutable. It's an object containing a collection of stuff like a box, you can put things in, take stuff out but it's still the same box, just it's contents have changed. If these objects are immutable to put something into the box you must first create a brand new box, and create a copy of the old boxes contents, and then put your new item into the box. Every time. Sometimes this kind of thing makes sense but it's certainly not a common situation.

    Some functional languages do have immutable data structures however in reality the compiler usually does some magic and ends up using a mutable type as it's simply so much more efficient.

  • I'm gonna hazard a guess, just cause I'm curious, that you're coming from JavaScript.

    Regardless, the answer's basically the same across all similar languages where this question makes sense. That is, languages that are largely, if not completely, object-oriented, where memory is managed for you.

    Bottom line, object allocation is VERY expensive. Generally, objects are allocated on a heap, so the allocation process itself, in its most basic form, involves walking some portion of a linked list to find an available heap block, updating a header or other info block to track that the block is now in use, maybe sub-dividing the block to avoid wasting space, any making any updates that might be necessary to nodes of the linked list that we traversed.

    THEN, we have to run similar operations later for de-allocation. And if we're talking about a memory-managed language, well, that means running a garbage collector algorithm, periodically, that needs to somehow inspect blocks that are in use to see if they're still in use, or can be automatically de-allocated. The most common garbage-collector I know of involves tagging all references within other objects, so that the GC can start at the "root" objects and walk the entire tree of references within references, in order to find any that are orphaned, and identify them as collectable.

    My bread and butter is C#, so let's look at an actual example.

     cs
        
    public class MyMutableObject
    {
        public required ulong Id { get; set; }
    
        public required string Name { get; set; }
    }
    
    public record MyImmutableObject
    {
        public required ulong Id { get; init; }
    
        public required string Name { get; init; }
    }
    
      
     cs
        
    _immutableInstance = new()
    {
        Id      = 1,
        Name    = "First"
    };
    
    _mutableInstance = new()
    {
        Id      = 1,
        Name    = "First"
    };
    
      
     cs
        
    [Benchmark(Baseline = true)]
    public MyMutableObject MutableEdit()
    {
        _mutableInstance.Name = "Second";
    
        return _mutableInstance;
    }
    
    [Benchmark]
    public MyImmutableObject ImmutableEdit()
        => _immutableInstance with
        {
            Name = "Second"
        };
    
      
    MethodMeanErrorStdDevRatioRatioSDGen0AllocatedAlloc Ratio
    MutableEdit1.080 ns0.0876 ns0.1439 ns1.020.19--NA
    ImmutableEdit8.282 ns0.2287 ns0.3353 ns7.791.030.007632 BNA

    Even for the most basic edit operation, immutable copying is slower by more than 7 times, and (obviously) allocates more memory, which translates to more cost to be spent on garbage collection later.

    Let's scale it up to a slightly-more realistic immutable data structure.

     cs
        
    public class MyMutableParentObject
    {
        public required ulong Id { get; set; }
    
        public required string Name { get; set; }
    
        public required MyMutableChildObject Child { get; set; }
    }
    
    public class MyMutableChildObject
    {
        public required ulong Id { get; set; }
    
        public required string Name { get; set; }
    
        public required MyMutableGrandchildObject FirstGrandchild { get; set; }
                
        public required MyMutableGrandchildObject SecondGrandchild { get; set; }
                
        public required MyMutableGrandchildObject ThirdGrandchild { get; set; }
    }
    
    public class MyMutableGrandchildObject
    {
        public required ulong Id { get; set; }
    
        public required string Name { get; set; }
    }
    
    public record MyImmutableParentObject
    {
        public required ulong Id { get; set; }
    
        public required string Name { get; set; }
    
        public required MyImmutableChildObject Child { get; set; }
    }
    
    public record MyImmutableChildObject
    {
        public required ulong Id { get; set; }
    
        public required string Name { get; set; }
    
        public required MyImmutableGrandchildObject FirstGrandchild { get; set; }
                
        public required MyImmutableGrandchildObject SecondGrandchild { get; set; }
                
        public required MyImmutableGrandchildObject ThirdGrandchild { get; set; }
    }
    
    public record MyImmutableGrandchildObject
    {
        public required ulong Id { get; set; }
    
        public required string Name { get; set; }
    }
    
      
     cs
        
    _immutableTree = new()
    {
        Id      = 1,
        Name    = "Parent",
        Child   = new()
        {
            Id                  = 2,
            Name                = "Child",
            FirstGrandchild     = new()
            {
                Id      = 3,
                Name    = "First Grandchild"
            },
            SecondGrandchild    = new()
            {
                Id      = 4,
                Name    = "Second Grandchild"
            },
            ThirdGrandchild     = new()
            {
                Id      = 5,
                Name    = "Third Grandchild"
            },
        }
    };
    
    _mutableTree = new()
    {
        Id      = 1,
        Name    = "Parent",
        Child   = new()
        {
            Id                  = 2,
            Name                = "Child",
            FirstGrandchild     = new()
            {
                Id      = 3,
                Name    = "First Grandchild"
            },
            SecondGrandchild    = new()
            {
                Id      = 4,
                Name    = "Second Grandchild"
            },
            ThirdGrandchild     = new()
            {
                Id      = 5,
                Name    = "Third Grandchild"
            },
        }
    };
    
      
     cs
        
    [Benchmark(Baseline = true)]
    public MyMutableParentObject MutableEdit()
    {
        _mutableTree.Child.SecondGrandchild.Name = "Second Grandchild Edited";
    
        return _mutableTree;
    }
    
    [Benchmark]
    public MyImmutableParentObject ImmutableEdit()
        => _immutableTree with
        {
            Child = _immutableTree.Child with
            {
                SecondGrandchild = _immutableTree.Child.SecondGrandchild with
                {
                    Name = "Second Grandchild Edited"
                }
            }
        };
    
      
    MethodMeanErrorStdDevRatioRatioSDGen0AllocatedAlloc Ratio
    MutableEdit1.129 ns0.0840 ns0.0825 ns1.000.10--NA
    ImmutableEdit32.685 ns0.8503 ns2.4534 ns29.092.950.0306128 BNA

    Not only is performance worse, but it drops off exponentially, as you scale out the size of your immutable structures.


    Now, all this being said, I myself use the immutable object pattern FREQUENTLY, in both C# and JavaScript. There's a lot of problems you encounter in business logic that it solves really well, and it's basically the ideal type of data structure for use in reactive programming, which is extremely effective for building GUIs. In other words, I use immutable objects a ton when I'm building out the business layer of a UI, where data is king. If I were writing code within any of the frameworks I use to BUILD those UIs (.NET, WPF, ReactiveExtensions) you can bet I'd be using immutable objects way more sparingly.

  • multiple other objects might be holding a reference to the object you want to change, so you'd either have to recreate those too or mutate them to let them point to the new object.

    however if you can do what you want to do in a side effect free way i suggest doing that, as it is indeed easier to reason about what happens this way.

27 comments