Visibility Rules
🎯 In Python, encapsulation is by convention. You use a leading underscore to signal "private," but anyone can still access it:
class Sensor:
def __init__(self, name, value):
self.name = name
self._value = value # "private" — but not really
@property
def value(self):
return self._value
s = Sensor("temp", 72)
s._value = -999 # oops — nothing stops this
Python's @property gives you a getter, but there's no enforcement. A determined caller can always reach in and modify _value directly.
Rust enforces privacy at compile time. If a field isn't pub, code outside the module literally cannot access it — the compiler rejects it.
Struct field visibility
In Rust, struct fields have their own visibility, independent of the struct:
pub struct Config {
pub name: String, // accessible from outside
secret_key: String, // private — only this module can see it
}
A struct can be pub (visible) while having private fields (hidden internals). Outside code can see the struct type but can't construct it directly or read private fields — they must use methods:
impl Config {
pub fn new(name: &str) -> Self {
Config {
name: name.to_string(),
secret_key: generate_key(), // set internally
}
}
}
This is like Python's @property pattern but enforced. You can't bypass the API.
Why private fields matter
Private fields let you enforce invariants — rules that must always hold. For a thermostat, "temperature must stay within min/max bounds" is an invariant. With public fields, any code could set temp = 9999. With private fields and methods:
pub fn set_temp(&mut self, temp: f64) -> bool {
if temp >= self.min && temp <= self.max {
self.current = temp;
true
} else {
false // out of range
}
}
The only way to change the temperature is through controlled methods that enforce the bounds. The compiler guarantees this — not a code review, not a convention, the compiler.
Visibility levels beyond pub
Rust has finer-grained visibility than just public/private:
| Syntax | Visible to |
|---|---|
| (no keyword) | Current module only |
pub | Everywhere |
pub(crate) | Current crate only (not exposed to users of your library) |
pub(super) | Parent module |
pub(crate) is particularly useful for library code: internal functions that other modules in your crate need, but that shouldn't be part of the public API. Python has no equivalent — __all__ controls what from module import * exposes, but doesn't prevent direct access.
Constructors and private fields
When a struct has any private fields, it can't be constructed outside the module using struct literal syntax:
// Outside the module:
let config = Config { name: "app".to_string(), secret_key: "key".to_string() };
// ERROR: secret_key is private
You must provide a constructor (like new()). This is Rust's way of ensuring that all invariants are established at creation time.
Login to see the full task and start coding.
Topics
This is a premium exercise
Log in to unlock the full exercise and start coding.
Login to access this exercise