Public and private: what a module shows
On this page
A part that shows everything can be depended on for everything, and then it can never change. So a part publishes an interface, the names others may use, and keeps the rest to itself: a black box. In Cone the line between the two is drawn at the module, with one word, pub. This page shows what is public and what is private, for a module's names and a type's members, and why the line is drawn where it is. Afterwards you can give a module an interface it can keep, and rules about its state that nothing outside it can break.
// src/account.cone
// A submodule: what it declares pub is all the rest of the program sees
mod account;
pub struct Account {
pub owner i32; // public: any module may read it
cents i64; // private: only this module's code reaches it
// The only way to add money: a deposit is never negative
pub fn deposit(self &mut, amount i64) {
if amount > 0 {
cents += amount;
}
}
pub fn balance(self &) i64 {
cents;
}
// A static function: a new account, with nothing in it
pub fn open(owner i32) Account {
new Account(owner, 0);
}
}
// src/bank.cone
mod bank;
import stdio use *;
use account;
fn main() {
var savings = Account.open(7);
savings.deposit(2500);
savings.deposit(-1000000); // refused by deposit's own rule
print <- "account ", savings.owner, " holds ", savings.balance(), " cents\n";
}
It prints:
account 7 holds 2500 cents
The rule that an account never goes up by a negative deposit lives in one place, and nothing outside account can get round it. Code in bank that reaches for the money directly is refused, whether it writes the field or builds an account with the field already filled in:
fn cheat(a &mut Account) {
a.cents = 1000000; // ERROR
}
fn forge() Account {
new Account(7, 1000000); // ERROR
}
Error 1042: May not access the private method/field `cents`. Error 1042: May not give a value to the private field cents from outside the module that declares Account.
Try this: change cents into two fields, whole dollars and cents. bank does not change at all, because it never could see the field.
Publish the interface, hide the rest#
This is isolation, or encapsulation: a component publishes an interface and hides everything else, the logic that does the work and the state it does it with. Done well, it pays three ways. It reduces coupling, because there is less for anyone outside to depend on. It lets a reader hold a part in mind without holding all of it. And it keeps most changes local, since what nobody outside could reach, nobody outside can be broken by. The part also gets to keep promises about its state, as Account does: a rule enforced by every way in is a rule that always holds.
In Cone the boundary is the module. A name declared pub is part of the module's interface, and every other name is private to the module, whatever kind of name it is: a function, a global, a type, or a type's field or method.
A module's names#
A module's names follow three rules.
- They are unique. No two items in a module may have the same name. Several functions may
nonetheless share one overload name, which is itself a
single name in the module that maps to those functions. A module may not bring one name in twice, even
for the same item: listing a name twice, writing the same use twice, importing
a module twice, or importing a module and also listing it through another module that re-exports it,
is an error. Core is imported into every module automatically, so import core
in any module but a C-named one is a second import of it, and the
error says so.
What a module never wrote is different. When a name arrives by a wildcard use *, by extends, or by the automatic import of core, and it is the same item the name already has, it is not two items. So when two wildcard imports each re-export the same function, when a module that extends this one hands this one's own names back, or when import libc use * brings the malloc that core already re-exports from it, the name is bound once, and it is public if either route made it public. - Their order does not matter. A function, variable or type may be used before it is declared, further down the file or in another file of the module. The only exceptions are permissions and regions, whose definitions must come before their use.
- They are private unless pub. A name declared pub may be used outside the module. Any other name may not: it is private to the module, however it is spelled. The interface of a module is what it says it is, and nothing more.
The same word decides the rest of what a module holds. A submodule is private to its parent unless it is written pub mod, and the names an import or a use folds in are private to the module that folds them unless it writes pub.
A type's members#
A type's members, its fields, methods and static functions, follow the module's rules, with the module still the boundary.
- Every field, function and method has a name unique within the type. Several methods or functions may additionally share one overload name, which they declare with overload. (That is why Account's field is cents and its method balance.)
- Their order does not matter. A name may be used before its declaration, and the order in which the methods or functions sharing an overload name are declared never affects which one a call selects.
- A member is private to the module unless pub. A field, method or function declared pub may be used anywhere the type's values reach. Any other member is private to the module that declares the type: every function, method and type of that module may use it, through any value of the type, in any file of the module's folder, and nothing outside the module may. A sister, a submodule and the parent are other modules, as is a file that opens with its own mod line. Fields and methods are reached on a value with the dot operator, and a static function with the same operator on the type's name. A private field may not be given a value in a new from outside the module either, as the example above shows, which is why Account offers open.
- A pub field of a pub type names only types the outside can name. Such a field is reached from outside the module, so a type private to the module may not be its type, alone or behind a reference, a pointer, an array or a type argument. Either the field is made private, which the module still reaches, or the type is made pub.
One kind of type is the exception: a type that extends another, a struct enriching its base or an enum extending its base, is inside its base's boundary, so its code reaches the base's private members wherever it is declared. Why, and what that costs the base's author, is in Name folding.
An enum's private members#
An enum's members follow the same rule. A member not declared pub is private to the module that declares the enum, as a struct's is. So code written anywhere inside the enum's braces (the enum's methods, its static functions, and every variant's methods) reads and writes the private fields of the enum and of every variant, calls their private methods, and gives private fields their values when it builds a variant, through any value and not only through self; and so does every other function and type of the module. Outside the module they stay private. So pub means what it means everywhere: part of the enum's interface to the rest of the program, and not a way for one variant to reach another.
enum Gauge {
struct Low {
lo i32;
pub fn above(self &, h &High) bool {h.hi > lo;} // High's private field
}
struct High {
hi i32;
}
pub fn level(g &imm Gauge) i32 { // a static function
match g {
case let l Low {l.lo;}
case let h High {h.hi;}
}
}
}
(This sentence is the compiler's current reading and is still to be reviewed.) An anonymous function written inside the braces is not part of this: like one written in a struct's method, it belongs to the module.
Why the module, and not the type#
A type's members and a module's names share one privacy boundary, the module. The module is the unit of trust: the code that may see a type's private members is decided by where it is written, not by a list the type declares, so there is no friend declaration. Two types that need each other's insides, such as a collection and its cursor, live in one module. And a type whose state must be kept even from its neighbours is declared in a module of its own, as Account is above.
Isolation at the other layers#
The same idea comes back wherever a part has a boundary:
- A block's local variables are private to it, and are gone when it ends (Types and blocks are namespaces too).
- An opaque type hides even its size and layout. Declared struct @opaque SocketHandle;, its values are reached only through references or pointers, and its fields are never read. It is mostly met at the boundary with C (Talking to C).
- An actor's state is private to it. A field of an actor is never pub, and nothing outside the actor reaches one: other code only sends it messages (Private state).
- A trait's private member is no part of its interface. It has no slot in the table a virtual reference dispatches through, so it is not reached through one.
- A package shows only what its root module re-exports. A submodule written pub mod is public within its package, never beyond it (Packages).
You can now decide what each module shows and keep the rest to itself. A module that others depend on is worth compiling and sharing on its own, which is what a package is for.