Solidity function types

Yang Nana
6 min readMar 29, 2018

--

Reference: http://solidity.readthedocs.io/en/v0.4.21/types.html

Function types are the types of functions. Variables of function type can be assigned from functions and function parameters of function type can be used to pass functions to and return functions from function calls. Function types come in four flavours — internal, external, public and private functions:

Internal functions can only be called inside the current contract (more specifically, inside the current code unit, which also includes internal library functions and inherited functions) because they cannot be executed outside of the context of the current contract. Calling an internal function is realized by jumping to its entry label, just like when calling a function of the current contract internally.

External functions consist of an address and a function signature and they can be passed via and returned from external function calls. Can be called from outside, can’t be call from inside (functions from same contract, functions from inherited contracts)

Private functions can only be called from inside the contract, even the inherited contracts can’t call them.

Public functions can be called from anywhere.

external:External functions are part of the contract interface, which means they can be called from other contracts and via transactions. An external function f cannot be called internally (i.e. f()does not work, but this.f() works). External functions are sometimes more efficient when they receive large arrays of data.

public:Public functions are part of the contract interface and can be either called internally or via messages. For public state variables, an automatic getter function is generated.

internal:Those functions and state variables can only be accessed internally (i.e. from within the current contract or contracts deriving from it), without using this.

private:Private functions and state variables are only visible for the contract they are defined in and not in derived contracts.

Function types are notated as follows:

function (<parameter types>) {internal|external|public|private} [pure|constant|view|payable] [(modifiers)] [returns (<return types>)]

(Please refer this post if you consider using multiple modifiers: https://medium.com/@yangnana11/how-to-use-multiple-modifiers-in-dapp-767df776051f )

Functions can be declared pure in which case they promise not to read from or modify the state.

In addition to the list of state modifying statements explained above, the following are considered reading from the state:

  1. Reading from state variables.
  2. Accessing this.balance or <address>.balance.
  3. Accessing any of the members of block, tx, msg (with the exception of msg.sig and msg.data).
  4. Calling any function not marked pure.
  5. Using inline assembly that contains certain opcodes.
pragma solidity ^0.4.16;contract C {
function f(uint a, uint b) public pure returns (uint) {
return a * (b + 42);
}
}

In contrast to the parameter types, the return types cannot be empty — if the function type should not return anything, the whole returns (<return types>) part has to be omitted.

By default, function types are internal, so the internal keyword can be omitted. In contrast, contract functions themselves are public by default, only when used as the name of a type, the default is internal.

There are two ways to access a function in the current contract: Either directly by its name, f, or using this.f. The former will result in an internal function, the latter in an external function.

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 on it.

If external function types are used outside of the context of Solidity, they are treated as the function type, which encodes the address followed by the function identifier together in a single bytes24 type.

Note that public functions of the current contract can be used both as an internal and as an external function. To use f as an internal function, just use f, if you want to use its external form, use this.f.

Additionally, public (or external) functions also have a special member called selector, which returns the ABI function selector:

pragma solidity ^0.4.16;contract Selector {
function f() public view returns (bytes4) {
return this.f.selector;
}
}

Example that shows how to use internal function types:

pragma solidity ^0.4.16;library ArrayUtils {
// internal functions can be used in internal library functions because
// they will be part of the same code context
function map(uint[] memory self, function (uint) pure returns (uint) f)
internal
pure
returns (uint[] memory r)
{
r = new uint[](self.length);
for (uint i = 0; i < self.length; i++) {
r[i] = f(self[i]);
}
}
function reduce(
uint[] memory self,
function (uint, uint) pure returns (uint) f
)
internal
pure
returns (uint r)
{
r = self[0];
for (uint i = 1; i < self.length; i++) {
r = f(r, self[i]);
}
}
function range(uint length) internal pure returns (uint[] memory r) {
r = new uint[](length);
for (uint i = 0; i < r.length; i++) {
r[i] = i;
}
}
}
contract Pyramid {
using ArrayUtils for *;
function pyramid(uint l) public pure returns (uint) {
return ArrayUtils.range(l).map(square).reduce(sum);
}
function square(uint x) internal pure returns (uint) {
return x * x;
}
function sum(uint x, uint y) internal pure returns (uint) {
return x + y;
}
}

Another example that uses external function types:

pragma solidity ^0.4.21;contract Oracle {
struct Request {
bytes data;
function(bytes memory) external callback;
}
Request[] requests;
event NewRequest(uint);
function query(bytes data, function(bytes memory) external callback) public {
requests.push(Request(data, callback));
emit NewRequest(requests.length - 1);
}
function reply(uint requestID, bytes response) public {
// Here goes the check that the reply comes from a trusted source
requests[requestID].callback(response);
}
}
contract OracleUser {
Oracle constant oracle = Oracle(0x1234567); // known contract
function buySomething() {
oracle.query("USD", this.oracleResponse);
}
function oracleResponse(bytes response) public {
require(msg.sender == address(oracle));
// Use the data
}
}

And another example:

CryptoKitties source code has this external function. The function looks like this:

function getKitty(uint256 _id) external view returns (
bool isGestating,
bool isReady,
uint256 cooldownIndex,
uint256 nextActionAt,
uint256 siringWithId,
uint256 birthTime,
uint256 matronId,
uint256 sireId,
uint256 generation,
uint256 genes
) {
Kitty storage kit = kitties[_id];
// if this variable is 0 then it's not gestating
isGestating = (kit.siringWithId != 0);
isReady = (kit.cooldownEndBlock <= block.number);
cooldownIndex = uint256(kit.cooldownIndex);
nextActionAt = uint256(kit.cooldownEndBlock);
siringWithId = uint256(kit.siringWithId);
birthTime = uint256(kit.birthTime);
matronId = uint256(kit.matronId);
sireId = uint256(kit.sireId);
generation = uint256(kit.generation);
genes = kit.genes;
}

And we have another sol file from another contract call this function

pragma solidity ^0.4.19;import "./zombiefactory.sol";contract KittyInterface {   function getKitty(uint256 _id) external view returns (      bool isGestating,      bool isReady,      uint256 cooldownIndex,      uint256 nextActionAt,      uint256 siringWithId,      uint256 birthTime,      uint256 matronId,      uint256 sireId,      uint256 generation,      uint256 genes   );}contract ZombieFeeding is ZombieFactory {   address ckAddress = 0x06012c8cf97BEaD5deAe237070F9587f8E7A266d;   KittyInterface kittyContract = KittyInterface(ckAddress);   function feedOnKitty(uint _zombieId, uint _kittyId) public {      uint kittyDna;      (,,,,,,,,,kittyDna) = kittyContract.getKitty(_kittyId);      feedAndMultiply(_zombieId, kittyDna);   }}

Getter Functions

The compiler automatically creates getter functions for all public state variables. For the contract given below, the compiler will generate a function called data that does not take any arguments and returns a uint, the value of the state variable data. The initialization of state variables can be done at declaration.

pragma solidity ^0.4.0;contract C {
uint public data = 42;
}
contract Caller {
C c = new C();
function f() public {
uint local = c.data();
}
}

The getter functions have external visibility. If the symbol is accessed internally (i.e. without this.), it is evaluated as a state variable. If it is accessed externally (i.e. with this.), it is evaluated as a function.

pragma solidity ^0.4.0;contract C {
uint public data;
function x() public {
data = 3; // internal access
uint val = this.data(); // external access
}
}

The next example is a bit more complex:

pragma solidity ^0.4.0;contract Complex {
struct Data {
uint a;
bytes3 b;
mapping (uint => uint) map;
}
mapping (uint => mapping(bool => Data[])) public data;
}

It will generate a function of the following form:

function data(uint arg1, bool arg2, uint arg3) public returns (uint a, bytes3 b) {
a = data[arg1][arg2][arg3].a;
b = data[arg1][arg2][arg3].b;
}

Note that the mapping in the struct is omitted because there is no good way to provide the key for the mapping.

--

--