Lesson 5: Visibility in Solidity

In the previous lessons, we’ve already got familiar with some basic concepts in Solidity programming, such as functions and reference types. In this one, we are going to look at how visibility works in Solidity.
First of all, it’s important to make one thing crystal clear right from the start. Everything on the blockchain is public and visible to everyone else. Just because you mark a variable as private or internal it does not mean that it is actually private or secret. Anyone can look into the storage of any contract ever deployed on Ethereum and see everything (we will show examples of how to do this in later lessons). There are various attempts to make storing stuff on the blockchain more private, but that is beyond the scope of this article.
So visibility clearly does not equal privacy. Visibility is rather a way for the smart contract developer to determine what variables and functions should be exposed to other smart contracts inheriting from and/or integrating the smart contract, or to say what regular users of the contract can and should interact with.
There are four possible modes of visibility in Solidity. These are public, private , internal and external.
private
private is the most restrictive visibility. private functions and state variables are only visible to the contract they were defined in. This means no inheriting contract, externally owned account (EOA), or another contract can call a function or read a state variable that is marked private.
Let’s see an example:

Here we have two contracts, ContractA and ContractB. ContractA defines a private function that adds two numbers. ContractB inherits from ContractA and tries to call that private function. However, this won’t work and ContractB won’t compile. We will get an error with the message Undeclared identifier , meaning ContractB can’t find our function addPrivate.
However, as we can see in addAndMulTwo, calling addPrivate inside ContractA works just fine.
internal
internal is the default visibility for state variables. internal means variables and functions are accessible within the contract and from deriving contracts. They are still inaccessible from EOAs and other contracts.
Let’s look at another example:

Here again, we have ContractB inheriting ContractA, but this time ContractA defines a state variable, an internal function, and a function that uses both the internal function and the state variable.
Then in ContractB we have a function that calls the internal function and another that reads the state variable. Nothing raises an error here and this works as expected.
However, we now have ContractC which tries to read meaningOfLife from the external contract ContractA. This will fail and raise the error that meaningOfLife is not visible.
internal functions can be called both within the contract and within inheriting contracts. If we would try to call addInternal from the outside, it would fail.
public
The public visibility has the least restrictions. State variables and functions defined as public can be accessed both from the inside and the outside. This means that the defining contract, inheriting contracts, EOAs, and other smart contracts can all access it.
For public state variables the compiler automatically generates a getter function, so if we have uint256 public meaningOfLife = 42; we can access it from the outside as if we would be calling a view function: meaningOfLife();

Here we ContractA, ContractB and ContractC again, but this time meaningOfLife is defined as public so all three contracts compile and work without errors. Both the inheriting contract and the outside contract can access the public state variables and functions just the same as the defining contract.
external
The external visibility modifier achieves what its name implies: the function can only be called externally, from the outside.
Only functions can be marked external. They can be called from other contracts or EOAs, and not from inside the contract or inheriting contracts.
One interesting and useful thing to note and remember is external functions are cheaper than public functions. This is because arguments of external functions can be read directly from calldata and they don’t need to be copied over to memory like public functions. Memory allocation is pricey while reading from calldata is cheap.
You may recall that public functions can also be called internally, and internal calls are executed via jumps in the code. The compiler expects all internal function arguments to be located in memory. For external calls, the compiler does not need to allow internal calls, so it can work with arguments being directly in calldata, therefore saving the cost of copying into memory.
Let’s see an example of an external function:

Here we ContractA defining an external function, which the inheriting ContractB tries to call unsuccessfully, and ContractC, which is an external contract, can call successfully.
Conclusion
In this lesson, we looked at visibility in Solidity. Specifying the correct visibility is really important and has serious implications in smart contract and protocol design so the right visibility for each function and state variable should be thought about deeply.
Thank you for staying with us till the end. If you enjoyed reading this piece please keep in touch and follow Solidify to keep up with our lessons on Solidity. In the upcoming articles, we will deep dive into the intricacies of the language, progressing from beginner to advanced level.
If you are new to Solidity, the previous lessons might be of value to you.
