Learn About the Four Types of Visibility Specifiers in Solidity

ยท

3 min read

In Solidity, visibility specifiers define how and from where a function or variable can be accessed. There are four visibility levels:


1. public

  • Accessible everywhere:

    • Inside the contract.

    • Derived (inherited) contracts.

    • External accounts (users, dApps).

    • Other contracts.

Use Cases:

  • When you want a function or variable to be accessible by anyone, including external users.

  • For public state variables, Solidity automatically generates a getter function.

Example:

uint public num; // Auto-generated getter for this variable
function add(uint a, uint b) public pure returns (uint) {
    return a + b;
}

2. private

  • Accessible only within the contract where it is defined.

  • Not accessible in derived (inherited) contracts.

  • Most restrictive visibility level.

Use Cases:

  • When you want to restrict a function or variable to the current contract only.

  • For internal logic or sensitive data that shouldn't be exposed to other contracts or external users.

Example:

contract Example {
    uint private secret = 42; // Only this contract can access it

    function getSecret() public view returns (uint) {
        return secret; // Allowed within the same contract
    }
}

contract Derived is Example {
    // Cannot access `secret` directly
    // uint mySecret = secret; // This will throw an error
}

3. internal

  • Accessible:

    • Within the contract where it is defined.

    • In derived (inherited) contracts.

  • Not accessible externally or by other contracts.

Use Cases:

  • When you want to share logic or data between the contract and its derived contracts but prevent access by external entities or unrelated contracts.

Example:

contract Parent {
    uint internal value = 10; // Accessible to derived contracts

    function increment() internal view returns (uint) {
        return value + 1; // Internal function
    }
}

contract Child is Parent {
    function useIncrement() public view returns (uint) {
        return increment(); // Allowed in derived contracts
    }
}

4. external

  • Accessible:

    • By other contracts or external accounts (users, dApps).
  • Not accessible internally using direct calls.

    • To call an external function internally, use this.functionName().

Use Cases:

  • Functions designed to interact with external entities.

  • Saves gas compared to public for external function calls.

Example:

contract Example {
    function externalFunction(uint a) external pure returns (uint) {
        return a * 2;
    }

    function callExternalFunction() public view returns (uint) {
        // Using `this` to call an external function internally
        return this.externalFunction(10);
    }
}

Comparison Table:

VisibilityAccessible Within ContractAccessible by Derived ContractsAccessible ExternallyNotes
publicโœ…โœ…โœ…State variables auto-get getter functions.
privateโœ…โŒโŒMost restrictive.
internalโœ…โœ…โŒShared with derived contracts.
externalโŒ (direct calls)โŒโœ…Use this for internal calls.

Best Practices:

  1. Use public for functions/variables meant for external interactions.

  2. Use private for sensitive logic or data.

  3. Use internal for shared logic/data across inherited contracts.

  4. Use external for functions primarily called from outside, especially if they donโ€™t need internal access.

ย