From 0869167786c7e3c06501c6e4bd4b64d5c1ed28d2 Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 20 Sep 2018 11:41:20 +0200 Subject: [DOCS] More on reference types. --- docs/types.rst | 116 ++++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 78 insertions(+), 38 deletions(-) diff --git a/docs/types.rst b/docs/types.rst index 03896a96..a3e782d8 100644 --- a/docs/types.rst +++ b/docs/types.rst @@ -596,8 +596,8 @@ also accepts a payment of zero Ether, so it also is ``non-payable``. On the other hand, a ``non-payable`` function will reject Ether sent to it, so ``non-payable`` functions cannot be converted to ``payable`` functions. -If a function type variable is not initialized, calling it will result -in an exception. The same happens if you call a function after using ``delete`` +If a function type variable is not initialised, calling it results +in a failed assertion. The same happens if you call a function after using ``delete`` on it. If external function types are used outside of the context of Solidity, @@ -746,14 +746,14 @@ non-persistent area where function arguments are stored, and behaves mostly like depending on the kind of variable, function type, etc., but all complex types must now give an explicit data location. -Data locations are important because they change how assignments behave: -assignments between storage and memory and also to a state variable (even from other state variables) -always create an independent copy. -Assignments to local storage variables only assign a reference though, and -this reference always points to the state variable even if the latter is changed -in the meantime. -On the other hand, assignments from a memory stored reference type to another -memory-stored reference type do not create a copy. +Data locations are not only relevant for persistency of data, but also for the semantics of assignments: +assignments between storage and memory (or from calldata) always create an independent copy. +Assignments from memory to memory only create references. This means that changes to one memory variable +are also visible in all other memory variables that refer to the same data. +Assignments from storage to a local storage variables also only assign a reference. +In contrast, all other assignments to storage always copy. Examples for this case +are assignments to state variables or to members of local variables of storage struct type, even +if the local variable itself is just a reference. :: @@ -783,13 +783,6 @@ memory-stored reference type do not create a copy. function h(uint[] memory) public pure {} } -Summary -^^^^^^^ - -Forced data location: - - parameters (not return) of external functions: calldata - - state variables: storage - .. index:: ! array .. _arrays: @@ -798,9 +791,10 @@ Arrays ------ Arrays can have a compile-time fixed size or they can be dynamic. -For storage arrays, the element type can be arbitrary (i.e. also other -arrays, mappings or structs). For memory arrays, it cannot be a mapping and -has to be an ABI type if it is an argument of a publicly-visible function. +The are few restrictions for the element, it can also be +another array, a mapping or a struct. The general restrictions for +types apply, though, in that mappings can only be used in storage +and publicly-visible functions need parameters that are ABI types. An array of fixed size ``k`` and element type ``T`` is written as ``T[k]``, an array of dynamic size as ``T[]``. As an example, an array of 5 dynamic @@ -810,9 +804,13 @@ third dynamic array, you use ``x[2][1]`` (indices are zero-based and access works in the opposite way of the declaration, i.e. ``x[2]`` shaves off one level in the type from the right). +Accessing an array past its end causes a revert. If you want to add +new elements, you have to use ``.push()`` or increase the ``.length`` +member (see below). + Variables of type ``bytes`` and ``string`` are special arrays. A ``bytes`` is similar to ``byte[]``, -but it is packed tightly in calldata. ``string`` is equal to ``bytes`` but does not allow -length or index access (for now). +but it is packed tightly in calldata and memory. ``string`` is equal to ``bytes`` but does not allow +length or index access. So ``bytes`` should always be preferred over ``byte[]`` because it is cheaper. As a rule of thumb, use ``bytes`` for arbitrary-length raw byte data and ``string`` for arbitrary-length string (UTF-8) data. If you can limit the length to a certain @@ -832,7 +830,7 @@ The numeric index will become a required parameter for the getter. Allocating Memory Arrays ^^^^^^^^^^^^^^^^^^^^^^^^ -Creating arrays with variable length in memory can be done using the ``new`` keyword. +You can use the ``new`` keyword to create arrays with a runtime-dependent length in memory. As opposed to storage arrays, it is **not** possible to resize memory arrays (e.g. by assigning to the ``.length`` member). You either have to calculate the required size in advance or create a new memory array and copy every element. @@ -875,7 +873,7 @@ assigned to a variable right away. The type of an array literal is a memory array of fixed size whose base type is the common type of the given elements. The type of ``[1, 2, 3]`` is ``uint8[3] memory``, because the type of each of these constants is ``uint8``. -Because of that, it was necessary to convert the first element in the example +Because of that, it is necessary to convert the first element in the example above to ``uint``. Note that currently, fixed size memory arrays cannot be assigned to dynamically-sized memory arrays, i.e. the following is not possible: @@ -902,15 +900,31 @@ Members ^^^^^^^ **length**: - Arrays have a ``length`` member to read their number of elements. - Dynamically-sized arrays (only available for storage) have a read-write ``length`` member to resize the array. Increasing the length adds uninitialized elements to the array, this has *O(1)* complexity. Reducing the length performs :ref:``delete`` on each removed element and has *O(n)* complexity where *n* is the number of elements being deleted. Please note that calling ``length--`` on an empty array will set the length of the array to 2^256-1 due to ``uint256`` underflow wrapping. + Arrays have a ``length`` member that contains their number of elements. + The length of memory arrays is fixed (but dynamic, i.e. it can depend on runtime parameters) once they are created. + For dynamically-sized arrays (only available for storage), this member can be assigned to resize the array. + Accessing elements outside the current length does not automatically resize the array and instead causes a failing assertion. + Increasing the length adds new zero-initialised elements to the array. + Reducing the length performs an implicit :ref:``delete`` on each of the removed elements. **push**: - Dynamic storage arrays and ``bytes`` (not ``string``) have a member function called ``push`` that can be used to append an element at the end of the array. The function returns the new length. + Dynamic storage arrays and ``bytes`` (not ``string``) have a member function called ``push`` that you can use to append an element at the end of the array. The element will be zero-initialised. The function returns the new length. **pop**: - Dynamic storage arrays and ``bytes`` (not ``string``) have a member function called ``pop`` that can be used to remove an element from the end of the array. This will also implicitly call :ref:``delete`` on the removed element. + Dynamic storage arrays and ``bytes`` (not ``string``) have a member function called ``pop`` that you can use to remove an element from the end of the array. This also implicitly calls :ref:``delete`` on the removed element. .. warning:: - It is not yet possible to use arrays of arrays in external functions. + If you use ``.length--`` on an empty array, it causes an underflow and + thus sets the length to ``2**256-1``. + +.. note:: + Increasing the length of a storage array has constant gas costs because + storage is assumed to be zero-initialised, while decreasing + the length has at least linear cost (but in most cases worse than linear), + because it includes explicitly clearing the removed + elements similar to calling :ref:``delete`` on them. + +.. note:: + It is not yet possible to use arrays of arrays in external functions + (but they are supported in public functions). .. note:: In EVM versions before Byzantium, it was not possible to access @@ -928,15 +942,34 @@ Members // dynamic array of pairs (i.e. of fixed size arrays of length two). // Because of that, T[] is always a dynamic array of T, even if T // itself is an array. + // Data location for all state variables is storage. bool[2][] m_pairsOfFlags; // newPairs is stored in memory - the only possibility - // for public function arguments + // for public contract function arguments function setAllFlagPairs(bool[2][] memory newPairs) public { - // assignment to a storage array replaces the complete array + // assignment to a storage array performs a copy of ``newPairs`` and + // replaces the complete array ``m_pairsOfFlags``. m_pairsOfFlags = newPairs; } + struct StructType { + uint[] contents; + uint moreInfo; + } + StructType s; + + function f(uint[] memory c) public { + // stores a reference to ``s`` in ``g`` + StructType storage g = s; + // also changes ``s.moreInfo``. + g.moreInfo = 2; + // assigns a copy because ``g.contents`` + // is not a local variable, but a member of + // a local variable. + g.contents = c; + } + function setFlagPair(uint index, bool flagA, bool flagB) public { // access to a non-existing index will throw an exception m_pairsOfFlags[index][0] = flagA; @@ -1022,7 +1055,10 @@ shown in the following example: function newCampaign(address payable beneficiary, uint goal) public returns (uint campaignID) { campaignID = numCampaigns++; // campaignID is return variable - // Creates new struct and saves in storage. We leave out the mapping type. + // Creates new struct in memory and copies it to storage. + // We leave out the mapping type, because it is not valid in memory. + // If structs are copied (even from storage to storage), mapping types + // are always omitted, because they cannot be enumerated. campaigns[campaignID] = Campaign(beneficiary, goal, 0, 0); } @@ -1052,11 +1088,12 @@ Struct types can be used inside mappings and arrays and they can itself contain mappings and arrays. It is not possible for a struct to contain a member of its own type, -although the struct itself can be the value type of a mapping member. +although the struct itself can be the value type of a mapping member +or it can contain a dynamically-sized array of its type. This restriction is necessary, as the size of the struct has to be finite. Note how in all the functions, a struct type is assigned to a local variable -(of the default storage data location). +with data location ``storage``. This does not copy the struct but only stores a reference so that assignments to members of the local variable actually write to the state. @@ -1067,7 +1104,7 @@ assigning it to a local variable, as in .. index:: !mapping Mappings -======== +-------- You declare mapping types with the syntax ``mapping(_KeyType => _ValueType)``. The ``_KeyType`` can be any elementary type. This means it can be any of @@ -1076,7 +1113,7 @@ or complex types like contract types, enums, mappings, structs and any array typ apart from ``bytes`` and ``string`` are not allowed. ``_ValueType`` can be any type, including mappings. -You can think of mappings as `hash tables `_, which are virtually initialized +You can think of mappings as `hash tables `_, which are virtually initialised such that every possible key exists and is mapped to a value whose byte-representation is all zeros, a type's :ref:`default value `. The similarity ends there, the key data is not stored in a mapping, only its ``keccak256`` hash is used to look up the value. @@ -1084,8 +1121,11 @@ mapping, only its ``keccak256`` hash is used to look up the value. Because of this, mappings do not have a length or a concept of a key or value being set. -Mappings are **only** allowed for state variables (or as storage reference types -in internal functions). +Mappings can only have a data location of ``storage`` and thus +are allowed for state variables, as storage reference types +in functions, or as parameters for library functions. +They cannot be used as parameters or return parameters +of contract functions that are publicly visible. You can mark variables of mapping type as ``public`` and Solidity creates a :ref:`getter ` for you. The ``_KeyType`` becomes a -- cgit