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
structdefaults topublicinheritance and member visibility; aclassdefaults toprivate. - File-Level Visibility: An anonymous namespace (
namespace { ... }) orstatickeyword 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 aninternal/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 withpub.
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__secretto 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 executesfrom 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) |