Renaissance Labs

发布于 2022-02-21到 Mirror 阅读

Learn Solidity: Variables (Part 3)

Welcome to another article in the Learn Solidity series. In the last article, we have seen how data location works and when you can use each of the three locations: memory, storage, and calldata. In this article, we will continue our journey of learning about variables in Solidity. This time we will focus on reference types, which should explicitly specify the data location, as we mentioned in previous articles. We will also see how you can define mappings, enumerations, and constants. Arrays In Solidity we have two types of arrays: storage arrays and memory arrays. Storage arrays These arrays are declared as state variables and can have either fixed or dynamic length. Storage arrays with dynamic length can be resized, which means they have access to the push() and pop() methods.

Memory arrays These arrays are declared with memory as their data location. They can also have either fixed or dynamic length, but dynamically sized memory arrays can’t be resized (i.e., can’t call push() and pop() methods); the size of the array must be calculated in advance. Dynamically sized memory arrays are declared using the new keyword as follows: Type[] memory a = new Type

One more point to mention here is about when you use memory arrays and you write something like this: uint256[] memory array; array[0] = 1; You won’t get any warnings, but you will end up with an invalid opcode since array will be pointing to the zero slot according to the description of layout in memory, which should never be written to. Remember always to initialize the array before using it so that you can get a valid address to use. Array slices Array slices can only be used with calldata arrays and are written as x[start:end]. The first element of the slice is x[start] and the last element is x[end - 1]. Both start and end are optional: start defaults to 0 and end defaults to the length of the array. Special Dynamically Sized Arrays byte[] or bytes These arrays can hold an arbitrary length of raw byte data. The difference between the two of them is that byte[] follows the rules of the array type, and as mentioned in this part of the documentation, elements in memory arrays in Solidity always occupy multiples of 32 bytes. This means if an element has less than a multiple of 32 bytes, it will be padded till it fits the necessary size. In the case of the byte array, this will waste 31 bytes for each element, which is not the case for bytes or string. I shall remind you that reading or writing a word (32 bytes) from memory costs 3 gas, and that’s why it is recommended to use bytes instead of byte[]. 2. string string is a dynamic array of UTF-8 data. As opposed to other languages, string in Solidity does not provide functions to get the length of the string or to perform concatenation or comparison of two strings (need to use a library). A string can be converted to a byte array using bytes(). This will return the low-level bytes of the UTF-8 representation of the string. Note: One character can be encoded to more than one byte, which implies that the length of the byte array is not necessarily the length of the string. string literals See this part of the documentation. string vs. bytes Most of the examples of the documentation use bytes32 instead of string, and they have also made it clear to use the value types bytes1 to bytes32 if the number of bytes of the string can be limited, since it’s much cheaper. Structs As in C and C++, structs allow you to define your own types, like the following: struct Donation { uint256 value; uint256 date; } Once you have defined your struct, you can start using it as a state variable or in your functions. In order to initialize a struct we have two ways: Using positional arguments: Donation donation = Donation( msg.value, block.timestamp ); Using the keywords: Donation donation = Donation({ value : msg.value, date: block.timestamp }); The second approach will avoid us having to remember the order of the members of the struct, so it might be more useful than the first one. Members of the struct are accessed using a dot: uint256 donationDate = myDonation.date; “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 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.” — Solidity documentation Mappings You can think of mappings as a massive key/value store where every possible key exists and any value can be set or retrieved in one move with the key. Mappings are declared as follows: mapping( KeyType => ValueType) VariableName The KeyType can be any built-in value type (the ones we saw in part 1), bytes or string, or any contract or enum type. The ValueType can be any type including mappings, arrays, and structs. One important thing to mention here is that the only data location allowed for mapping variables is storage, which means you can only declare them as state variables, storage pointers, or parameters for library functions. Enums Enumerations will allow you to group related values under a custom type, as in the following example: enum Color { green , blue, red } Access to an enum value is done using the following syntax: Color defaultColor = Color.green; Note: Enums can also be declared on the file level, outside of contract or library definitions. Constants and Immutable State Variables State variables can be declared as constant or immutable. In both cases, the variables cannot be modified after the contract has been constructed. For constant variables, the value has to be fixed at compile time, while for immutable, it can still be assigned at construction time. The compiler does not reserve a storage slot for these variables, and every occurrence is replaced by the respective value. Constants are declared using the keyword constant: uint256 constant maxParticipants = 10; For immutable state variables, they are declared using the keyword immutable: contract C { address immutable owner = msg.sender; uint256 immutable maxBalance;

  constructor(uint256 _maxBalance){
       maxBalance = _maxbalance;
  }

} You can find more details about constants and immutable state variables in this section from the documentation. Note: It is also possible to define constant variables at the file level. The delete Keyword One last thing I would like to add is the use of delete in Solidity. It serves for setting a variable to its initial value, which means that this statement delete a will behave as follows: For integers, it is equivalent to a = 0. For arrays, it assigns a dynamic array of length zero or a static array of the same length with all elements set to their initial value. delete a[x] deletes the item at index x of the array and leaves all other elements and the length of the array untouched. This especially means that it leaves a gap in the array. For structs, it assigns a struct with all members reset. delete has no effect on mappings (as the keys of mappings may be arbitrary and are generally unknown). Practice Time: Simple Crud In this exercise, we will create a contract for managing users. Here are the instructions: Create a new file and add a contract named Crud. Create a struct named User that contains the id and the name of the user. Add two state variables and make them public: 1) a dynamic array of users and 2) an id that’s going to be incremented each time we create a new user. The next step is to create the crud functions, but since I didn’t introduce you to Solidity functions, I will give you the syntax for declaring a function. In the next article, we will have a detailed discussion about them: function function_name(<param_type> <param_name>) [returns(<return_type>)]{ ... } The visibility can be either: public, private, internal, external. The state mutability can be either: view, pure, payable. Here is the description of the functions that you will create.

  1. add visibility: public state mutability: empty This function will take the name of the user as a parameter, create an instance of User with a new id (the id is auto-incremented each time we add a new user), and add the newly created user to the array.
  2. read visibility: public state mutability: view This function takes the id of the user to look for and returns the user name if found or reverts the transaction if not (more on exception handling later).
  3. update visibility: public state mutability: empty This function will take the id of the user and a new name, then update the corresponding user if found or revert the transaction if the user does not exist.
  4. destroy visibility: public state mutability: empty This function takes the id of the user to delete and removes it from the array if found or reverts the transaction if the user does not exist. Hint: Since all the three last functions need to look up the user, you will need to create a private function that will take the id of the user and returns their index in the array if found, to avoid repeating the same code. When you are done, you can find the solution here on GitHub. This concludes our discussion of variables. Next time we will look at functions and how you can use them in Solidity, so stay tuned for the upcoming articles if you want to learn more.

https://betterprogramming.pub/learn-solidity-variables-part-3-3b02ca71cf06