Solidity Dynamic Uint Array – Does push() Return Reference or New Memory Variable?

arrayssoliditysolidity-0.8.x

Solidity documentation 0.8.14:

push():

Dynamic storage arrays and bytes (not string) have a member
function called push() that you can use to append a zero-initialised
element at the end of the array. It returns a reference to the
element, so that it can be used like x.push().t = 2 or x.push() = b.

My contract:

pragma solidity ^0.8.0;

contract Demo {
    uint[] public storageDynamicUintArray;

    struct A {
        uint a;
    }
    A[] public storageDynamicArrayOfA;

    function pushToStorageDynamicUintArray() public {
        require(storageDynamicUintArray.length == 0, "storageDynamicUintArray.length == 0");
        
        uint item = storageDynamicUintArray.push(); // item is reference or new variable?
        require(storageDynamicUintArray.length == 1, "storageDynamicUintArray.length == 1");
        item = 1;
        require(item == 1, "item == 1");
        require(storageDynamicUintArray[0] == 0, "storageDynamicUintArray[0] == 0");
    }

    function pushToStorageDynamicArrayOfA() public {
        require(storageDynamicArrayOfA.length == 0, "storageDynamicArrayOfA.length == 0");
        
        A memory memoryItem = storageDynamicArrayOfA.push(); // item is reference or new variable?
        require(storageDynamicArrayOfA.length == 1, "storageDynamicArrayOfA.length == 1");
        memoryItem.a = 1;
        require(memoryItem.a == 1, "memoryItem.a == 1");
        require(storageDynamicArrayOfA[0].a == 0, "storageDynamicArrayOfA[0].a == 0");

        A storage storageItem = storageDynamicArrayOfA.push(); // item is reference!
        require(storageDynamicArrayOfA.length == 2, "storageDynamicArrayOfA.length == 2");
        storageItem.a = 2;
        require(storageItem.a == 2, "storageItem.a == 2");
        require(storageDynamicArrayOfA[1].a == 2, "storageDynamicArrayOfA[1].a == 2");
    }
}

In case uint array – push() retuns looks like a new variable. Not reference.
In case A array – push() retuns looks like a new variable if I using memory location for variable.

Does the documentation look wrong?

Best Answer

You are mixing storage references with memory references. When the doc states :

It returns a reference to the element

It's a storage reference, since .push() is only valid on dynamic storage arrays (at least currently).

However, when you take that storage reference as a memory variable (which is in itself a reference to a memory location to be precise) then some special rules apply as described in the documentation :

Assignments between storage and memory (or from calldata) always create an independent copy.

And :

Assignments from storage to a local storage variable also only assign a reference.

So, going over the 3 cases that you have using the rules above :

Case 1 :

uint item = storageDynamicUintArray.push(); assigns a storage reference (.push()) to a local uint variable living on the stack. This is an independent copy by definition.

Case 2 :

A memory memoryItem = storageDynamicArrayOfA.push(); assigns a storage reference to a memory variable, this is also an independent copy as per the first rule.

Case 3 :

A storage storageItem = storageDynamicArrayOfA.push(); assigns a storage reference to a local storage variable, this is a reference as per the second rule.

The documentation is not wrong, it's just that the very specific portion you were reading did not mention the caveats of data location and assignment behavior.

I hope that answers your question.

Related Topic