Functions
"Writing state" includes: modifying state variables, emitting events, creating contracts, selfdestruct, sending Ether, calling non-view functions, low-level calls, and assembly with state-changing opcodes.
13.1 Anatomy
function name(Type1 param1, Type2 param2)
<visibility> // external | public | internal | private
<mutability> // pure | view | payable | (none = non-payable, state-changing)
<modifiers> // onlyOwner, nonReentrant, ...
<virtual/override>
returns (ReturnType1, ReturnType2)
{
// body
}13.2 Visibility
Callable from outside only via this.f() (an external call) params can be calldata default for state vars; can take storage pointers still visible on-chain to everyone
13.3 State mutability
May read state May write state May receive Ether no — reverts if msg.value > 0
"Writing state" includes: modifying state variables, emitting events, creating contracts, selfdestruct, sending Ether, calling non-view functions, low-level calls, and assembly with state-changing opcodes. view functions are executed with STATICCALL when called externally, so state changes are impossible.
13.4 Parameters and return values
External functions cannot return mappings; internal functions can return storage pointers.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract Returns {
// Unnamed return
function add(uint256 a, uint256 b) public pure returns (uint256) {
return a + b;
}
// Named returns: implicitly returned at the end
function divmod(uint256 a, uint256 b) public pure returns (uint256 q, uint256 r) {
q = a / b;
r = a % b;
}
// Multiple returns, destructuring
function use() external pure returns (uint256) {
(uint256 q, ) = divmod(17, 5);
return add({a: q, b: 1}); // named arguments in calls
}
// Returning dynamic data
function names() external pure returns (string[] memory list) {
list = new string[](2);
list[0] = "alice";
list[1] = "bob";
}
}13.5 Overloading
Functions with the same name but different parameter types are allowed (return types alone don't count):
Calling f(1) is ambiguous if both f(uint8) and f(uint256) exist — literals fit multiple types. Overloads appear as separate entries in the ABI (ethers.js then requires the full signature: contract["f(uint256)"](1)).
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract Overload {
function f(uint256 x) public pure returns (uint256) { return x; }
function f(uint256 x, uint256 y) public pure returns (uint256) { return x + y; }
function f(address a) public pure returns (address) { return a; }
}13.6 Function types
Functions are first-class values with types like function (uint256) external returns (bool):
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
library ArrayUtils {
function map(uint256[] memory xs, function (uint256) pure returns (uint256) f)
internal pure returns (uint256[] memory r)
{
r = new uint256[](xs.length);
for (uint256 i; i < xs.length; ++i) r[i] = f(xs[i]);
}
function reduce(uint256[] memory xs, function (uint256, uint256) pure returns (uint256) f)
internal pure returns (uint256 acc)
{
for (uint256 i; i < xs.length; ++i) acc = f(acc, xs[i]);
}
}
contract FunctionTypes {
using ArrayUtils for uint256[];
function square(uint256 x) internal pure returns (uint256) { return x * x; }
function sum(uint256 a, uint256 b) internal pure returns (uint256) { return a + b; }
function sumOfSquares(uint256[] memory xs) external pure returns (uint256) {
return xs.map(square).reduce(sum);
}
// External function values carry an address + selector
function selectorOf() external view returns (bytes4, address) {
return (this.selectorOf.selector, this.selectorOf.address);
}
}13.7 Free functions
Functions defined outside contracts are always implicitly internal; their code is included in every contract that calls them. They cannot access this or state variables directly.
13.8 The getter / this gotcha
Calling this.f() performs a real external call (new call frame, more gas, msg.sender becomes the contract itself). Call internal/public functions directly by name instead.