Programming Languages - Visibility and Access Control

Visibility (access control) defines which scopes, files, packages, or assemblies can access specific variables, types, functions, and class members. Encapsulation protects an internal implementation from unintended outside mutation.

1. Java

Java uses four levels of access control:

Modifier Class Itself Same Package Subclasses (Any Package) World (Public)
public Yes Yes Yes Yes
protected Yes Yes Yes No
(default / package-private) Yes Yes No No
private Yes No No No

In Java, if no modifier is specified, the member is package-private, meaning only classes within the same package directory can access it.

2. C++

C++ controls visibility at the class and struct level via access specifiers, and at the translation unit level via namespaces and anonymous namespaces.

class Account {
public:
    // Accessible from any code
    void deposit(double amount);

protected:
    // Accessible by Account and derived classes
    double balance;

private:
    // Accessible only within Account member functions and friends
    std::string internalAccountToken;

    // Grants another class or function full access to private members
    friend class Auditor;
};
  • Struct vs. Class: A struct defaults to public inheritance and member visibility; a class defaults to private.
  • File-Level Visibility: An anonymous namespace (namespace { ... }) or static keyword restricts symbols to the current Translation Unit (TU), giving them internal linkage.

3. Go

Go eschews access keywords (public/private) entirely, relying instead on identifier casing and the package boundary:

  • Exported (Public): Identifiers starting with a capital letter (e.g. Println, User, Config) are visible outside their package.
  • Unexported (Package-Private): Identifiers starting with a lowercase letter (e.g. balance, newClient) are accessible only within the defining package.
package bank

type Account struct {
    Owner   string  // Exported (Public)
    balance float64 // Unexported (Private to package bank)
}

func (a *Account) Deposit(amount float64) { // Exported method
    a.balance += amount
}
  • internal/ Packages: Go runtime compiler enforces that any package inside an internal/ directory can only be imported by packages within the immediate parent tree.

4. Rust

In Rust, all items (functions, structs, traits, modules) are private by default to their immediate parent module. The pub keyword opens visibility with fine-grained scoping:

mod network {
    // Visible anywhere network is visible
    pub fn connect() {}

    // Visible only within the parent crate (not to external consumers)
    pub(crate) fn verify_checksum() {}

    // Visible only to the parent module
    pub(super) fn internal_helper() {}

    // Visible only to a specific path
    pub(in crate::network) fn packet_buffer() {}

    // Private to module network
    fn raw_socket() {}
}
  • Struct fields in Rust are also private by default, even if the struct itself is marked pub. Each field must be explicitly decorated with pub.

5. Python

Python does not have compiler-enforced access modifiers. It relies on community conventions and name mangling:

  • Public: Standard names (self.name).
  • Protected (Convention): Single leading underscore (self._cache). Tells developers "treat this as private implementation detail".
  • Private (Name Mangling): Double leading underscore (self.__secret). The Python runtime automatically mangles the attribute name to _ClassName__secret to prevent accidental subclass overrides.
class Database:
    def __init__(self):
        self.host = "localhost"          # Public
        self._pool_size = 10             # Protected (convention)
        self.__api_key = "secret_123"    # Name-mangled to _Database__api_key
  • Module Exports (__all__): Defining __all__ = ['Client', 'connect'] restricts what is exported when a consumer executes from module import *.

6. JavaScript & TypeScript

Native JavaScript (ECMAScript)

Modern JavaScript features native private class fields using the # prefix, enforced by the JavaScript runtime engine:

class Wallet {
  #balance = 0; // Truly private: cannot be accessed or inspected outside the class

  deposit(amount) {
    this.#balance += amount;
  }
}

TypeScript

TypeScript adds compile-time access modifiers (public, private, protected). Note that these are stripped at compile time and are not enforced at runtime (unless using native # private fields):

class Service {
  public name: string;
  protected timeout: number;
  private token: string; // Only checked at TypeScript compile time
}

Summary Comparison

Language Primary Unit of Isolation Private by Default? Fine-Grained Scopes
Java Class & Package No (Defaults to package-private) public, protected, private
C++ Class & File (TU) Yes (for class, no for struct) friend, anonymous namespaces
Go Package Yes (lowercase is unexported) Capital letter vs lowercase; internal/
Rust Module & Crate Yes pub, pub(crate), pub(super), pub(in path)
Python Module & Class No (All accessible) _name (convention), __name (mangling)
JavaScript Class & Module No #privateField (runtime private)
Loading...