top of page
  • Writer's pictureShane Duncan

Solidity Functions — Everything You Need To Know About Visibility

Updated: Mar 15


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

Part One — Visibility

Smart contracts are widely considered to be the future of decentralized finance. Their potential use cases include trading, investing, lending, and even extend to gaming, healthcare, and real estate. A foray into smart contract development leads you to the Solidity programming language and, if you’ve begun the fulfilling process of learning it, you may have found yourself in the documentation. It’s a great place to start. Once you venture out into public forums though, things get a little more difficult to nail down. The terms tend to vary from developer to developer. One dev may speak about function visibilities and types, another about function scopes and usages, and some just bypass terminology altogether and call them by their designations: view, pure, external, internal, etc. This loose terminology can muddy the waters and hinder our understanding as we learn Solidity.


Coming from a traditional Java and Web2 background myself, I found this very confusing. Much like any other language I’ve learned, I relate it to my native, Java, so Solidity function definitions were difficult to reconcile. It took a couple of months and several code experiments before I truly understood functions in Solidity. That’s when it all started to come together and I began to appreciate the language. In this series, I use that understanding to break function keywords into their preferred vernacular, detail their usages, and give examples to illustrate them.


The best place to start is with Visibility. If you can’t access a function the way it was intended, it can cause irreparable damage to the applications. It’s the guardian of the function.


Visibilities, sometimes referred to as scopes or access modifiers, declare the scope at which a function is accessible or able to be seen externally. It controls who/what can call the function from within and outside of the contract itself and can also be used as a mechanism to easily secure parts of the contract without writing custom logic to do so.


Solidity functions are invoked in two ways: external calls and internal calls. This means that the function may be called from outside of the contract, through an EVM (Ethereum Virtual Machine) call, or from within the contract without an EVM call. This can add to the confusion since the valid visibilities include external and internal. So let’s go through each possibility starting with least visible to most visible.


PRIVATE

private functions can only be accessed from within the contract that they’re defined. It may not be used in an inherited (sometimes called a derived) contract and can be considered the most restrictive visibility. It is not possible to access them from an EVM call even though they are in the ABI (Application Binary Interface) and can be seen on Etherscan.


POSSIBLE USES: work functions that should only be used by the main contract, getters for local memory variables, setters for variables never used externally or in inherited contracts, etc.


EXAMPLE:


function increment(uint a) private pure returns(uint) { 	return a + 1; }

INTERNAL

internal functions are accessed from within the contract itself. It’s impossible to access an internal function with an EVM call from outside the contract. It may be used in an inherited contract. This could be compared to a protected method in java.


POSSIBLE USES: work functions you don’t want to be reused by external entities but wish to be inherited by child contracts, getters that need access from child contracts, etc.


EXAMPLE:


function square(uint x) internal pure returns (uint) {    return x * x;}

EXTERNAL

external functions can only be accessed from other contracts using transactions. It’s an EVM call only. If you try to access an external function within the contract, it will give a compile error. May not be used in an inherited contract.

POSSIBLE USES: getters that will only be accessed externally, withdrawals, deposits, etc.


EXAMPLE:


function totalSupply() external view returns (uint) {  return totalSupply;}

PUBLIC

public functions may be called from within the contract that it’s defined or from another contract using an EVM call. It’s almost like an all-access pass. By default, functions are public so the public keyword may be omitted. They may also be used in an inherited contract. You might be wondering why anyone would use external if public can do the same thing. For one, public functions use more gas. The reason is explained below.

POSSIBLE USES: getters that need access inside and outside of the contract, pausing a contract, etc.


EXAMPLE:


function setPaused(bool _paused) public {  require(msg.sender == owner, "You are not the owner");  paused = _paused;}

public Uses More Gas Than external

The difference is, in public functions, Solidity copies the arguments to memory whereas external functions read calldata directly. In-depth Solidity memory concepts are beyond the scope of this discussion but the high-level takeaway is that reading calldata is cheaper than memory allocation. external functions simply don’t need to allocate that memory so they cost less gas.

You should use external if you expect that the function will only ever be called externally, and use public if you need to call the function internally. You will see performance benefits with external functions any time you are only calling a function externally, and passing in a lot of calldata like large arrays.


Visibility Implications and Things to Remember

You should always use the principle of least privilege when determining which level of visibility to use. That is, assign only as much visibility as is necessary and no more. This is a good security practice for any language. Not allowing enough access is easier to deal with than allowing too much, which could be catastrophic.

private and internal functions only restrict access from other contracts. The data is still viewable.


Conclusion

It’s not often that I’ve gotten to be part of a software movement, but that certainly seems to be what is happening with smart contracts and Solidity. The more I learn, the more I appreciate the language and how it is inspiring innovation across multiple sectors. I’m excited to see where it goes.


Up Next

In upcoming installments, I’ll be discussing function types and modifiers.

bottom of page