Objectives
By the end of this lesson you should be able to:- Categorize basic data types
- List the major differences between data types in Solidity as compared to other languages
- Compare and contrast signed and unsigned integers
Common Properties
In Solidity, types must always have a value and are neverundefined
, null
, or none
. Because of this, each type has a default value. If you declare a variable without assigning a value, it will instead have the default value for that type. This property can lead to some tricky bugs until you get used to it.
uint256
into a int8
, you need to cast twice:
Overflow/underflow protection (described below), does not provide protection when casting.
Boolean
Booleans can have a value oftrue
or false
. Solidity does not have the concept of truthy or falsey, and non-boolean values cannot be cast to bools by design. The short conversation in this issue explains why, and explains the philosophy why.
Logical Operators
Standard logical operators (!
, &&
, ||
, ==
, !=
) apply to booleans. Short-circuiting rules do apply, which can sometimes be used for gas savings since if the first operator in an &&
is false
or ||
is true
, the second will not be evaluated. For example, the following code will execute without an error, despite the divide by zero in the second statement.
>
or <
with booleans, because they cannot be implicitly or explicitly cast to a type that uses those operators.
Numbers
Solidity has a number of types for signed and unsigned integers, which are not ignored as much as they are in other languages, due to potential gas-savings when storing smaller numbers. Support for fixed point numbers is under development, but is not fully implemented as of version0.8.17
.
Floating point numbers are not supported and are not likely to be. Floating precision includes an inherent element of ambiguity that doesn’t work for explicit environments like blockchains.
Min, Max, and Overflow
Minimum and maximum values for each type can be accessed withtype(<type>).min
and type(<type>).max
. For example, type(uint).min
is 0, and type(uint).max
is equal to 2^256-1.
An overflow or underflow will cause a transaction to revert, unless it occurs in a code block that is marked as unchecked.
uint
vs. int
In Solidity, it is common practice to favor uint
over int
when it is known that a value will never (or should never) be below zero. This practice helps you write more secure code by requiring you to declare whether or not a given value should be allowed to be negative. Use uint
for values that should not, such as array indexes, account balances, etc. and int
for a value that does need to be negative.
Integer Variants
Smaller and larger variants of integers exist in many languages but have fallen out of favor in many instances, in part because memory and storage are relatively cheap. Solidity supports sizes in steps of eight fromuint8
to uint256
, and the same for int
.
Smaller sized integers are used to optimize gas usage in storage operations, but there is a cost. The EVM operates with 256 bit words, so operations involving smaller data types must be cast first, which costs gas.
uint
is an alias for uint256
and can be considered the default.
Operators
Comparisons (<=
, <
, ==
, !=
, >=
, >
) and arithmetic (+
, -
, *
, /
, %
, **
) operators are present and work as expected. You can also use bit and shift operators.
uint
and int
variants can be compared directly, such as uint8
and uint256
, but you must cast one value to compare a uint
to an int
.
Addresses
The address type is a relatively unique type representing a wallet or contract address. It holds a 20-byte value, similar to the one we explored when you deployed your Hello World contract in Remix.address payable
is a variant of address
that allows you to use the transfer
and send
methods. This distinction helps prevent sending Ether, or other tokens, to a contract that is not designed to receive it. If that were to happen, the Ether would be lost.
Addresses are not strings and do not need quotes when represented literally, but conversions from bytes20
and uint160
are allowed.
Members of Addresses
Addresses contain a number of functions.balance
returns the balance of an address, and transfer
, mentioned above, can be used to send ether
.
call
, delegatecall
, and staticcall
, which can be used to call functions deployed in other contracts.
Contracts
When you declare a contract, you are defining a type. This type can be used to instantiate one contract as a local variable inside a second contract, allowing the second to interact with the first.Byte Arrays and Strings
Byte arrays come as both fixed-size and dynamically-sized. They hold a sequence of bytes. Arrays are a little more complicated than in other languages and will be covered in-depth later.Strings
Strings are arrays in Solidity, not a type. You cannot concat them with+
, but as of 0.8.12, you can use string.concat(first, second)
. They are limited to printable characters and escaped characters. Casting other data types to string
is at best tricky, and sometimes impossible.
Generally speaking, you should be deliberate when working with strings inside of a smart contract. Don’t be afraid to use them when appropriate, but if possible, craft and display messages on the front end rather than spending gas to assemble them on the back end.
Enums
Enums allow you to apply human-readable labels to a list of unsigned integers.uint
, but not implicitly. They are limited to 256 members.
Constant and Immutable
The constant and immutable keywords allow you to declare variables that cannot be changed. Both result in gas savings because the compiler does not need to reserve a storage slot for these values. As of 0.8.17,constant
and immutable
are not fully implemented. Both are supported on value types, and constant
can also be used with strings.