Name folding: delegation, reuse and extension
On this page
- One idea: the names in there are names in here
- Delegation: reaching a part's members as your own
- Selective inheritance: naming what is folded
- Total inheritance: everything but
- What folds through a field, and what does not
- Receivers
- What a fold grants
- Delegating to a view the type lends
- Shorthand: folding through a global
- What the module shows of it
- Extending a type you did not write
- The two types interchange, both ways, at no cost
- The declaration is what licenses it
- A finalizing base finalizes its enrichments too
- An enrichment is inside its base's encapsulation
- Meeting a trait the type does not: retroactive conformance
- Generic types
- Reusing capabilities: several enrichments of one base
- Selective, and renaming
- What folds from a sibling
- Reusing a module: a module extending a module
- Extending an enum: more variants
- Generic enums
- Extending an extension
- What an extension may declare
- extend: methods added to an existing type in place (not built)
Building a type out of parts is the safe way to reuse code, but it has a price: a car that holds an engine makes everyone write car.engine.fuel, and wanting one more method on a type somebody else wrote seems to mean wrapping it. Cone answers with a family of mechanisms that all do one thing: they make the names in there names in here. This page goes through what each gives you: shorthand for another module's names, automatic delegation to a part, extending a type you did not write, and reusing another type's or module's capabilities. Here is the first, delegation:
mod car;
import stdio use *;
struct Engine {
pub fuel f32;
pub torque f32;
pub fn thrust(self &mut, amt f32) {
fuel -= amt;
}
pub fn power(self &) f32 {
torque * 2.;
}
}
struct Wheels {
pub count i32;
}
// A Car is made of parts. The use clause folds two of the
// engine's names in, so they are reached as the car's own
struct Car {
pub engine Engine use fuel, thrust;
pub wheels Wheels;
}
fn main() {
var car = new Car(new Engine(50., 300.), new Wheels(4));
car.thrust(12.5); // car.engine.thrust(12.5)
print <- "fuel left: ", car.fuel, "\n"; // car.engine.fuel
print <- "power: ", car.engine.power(), "\n"; // not folded: the path is written
}
It prints:
fuel left: 37.5 power: 600
Try this: add power to the use clause, and call car.power().
One idea: the names in there are names in here#
Each mechanism on this page takes names that belong to something else and makes them names of the place where the clause is written. The something else may be another module, an enum, a global, a field, a type's base, a sibling type, or a view the type lends. This manual calls the whole family name folding, because the one operation, written with the same use clause nearly everywhere, comes back at every layer:
| Written on | Folds in | What it gives |
|---|---|---|
| an import: import collections use List; | another module's public names | shorthand: Modules |
| a use statement: use Colors; | an enum's variants, or a submodule's names | shorthand: Modules |
| a global: let config Config = ... use *; | a struct's members, as names of the module | shorthand, and a module made out of a struct: below |
| a field: pub engine Engine use fuel; | a part's members | delegation: below |
| a type's body: use str via view; | the members of a view the type lends | delegation: below |
| a type: struct Gauge extends Meter | everything its base has | extending a type you did not write: below |
| a type's body: use Trig; | what a sibling enrichment adds | reusing capabilities: below |
| a module: mod solids extends shapes; | everything the other module has | reusing a module: below |
Two things are true of every one of them. A fold grants names: no value is copied, no forwarding function is generated, and a folded name is a second route to the one thing it names. And a fold is checked where it is written: a name that would collide is an error at the clause, and is settled there, by leaving a name out with but or renaming it with as. What none of them does is make one struct a subtype of another: a struct is a subtype only of the traits it meets, as the next page, Subtyping, explains.
Delegation: reaching a part's members as your own#
Reusing another type's fields and methods is usually called inheritance. Cone's version is built on composition: a type holds the part as a field, and the field's use clause forwards named requests to it. That makes reuse easy where it is wanted, without the tight coupling and fragility that inheriting an implementation can bring, which is why it is called delegated inheritance.
Selective inheritance: naming what is folded#
In Car above, use fuel, thrust on the engine field makes Engine's fuel field and thrust method names of Car, reached by way of engine:
- car.fuel is car.engine.fuel. It reads and writes the engine's own field; no second copy of the value exists.
- car.thrust(12.5) is car.engine.thrust(12.5), and thrust runs with self being the engine, not the car. If you need a thrust that works on the whole car, don't fold it: write it on Car, and call the engine's from it if you want to.
No forwarding method is generated for a folded name: a call to thrust through a Car is a call to Engine's own method, with its receiver shifted to the engine. Inside Car's own methods a folded name is named bare, exactly as Car's own members are.
A type may fold names from more than one field. One clause reaches one level: use temp admits engine.temp, not engine.core.temp, unless Engine itself folded temp in from core. Folds chain through the types, not through the clause.
Folded names must be unique. Folding a name the type declares itself is an error, and so is folding the same name from two fields. To fold one anyway, give it another name here with as: use fuel as fuelamt, thrust makes car.fuelamt a name for car.engine.fuel.
Total inheritance: everything but#
With use *, every public field and method of the field's type is folded in, less any named after but:
struct SuperEngine {
pub engine Engine use * but power;
pub fn power(self &) f32 { torque * 3.; }
}
SuperEngine has the fields fuel and torque and the thrust method from its engine, but not power, since it has its own and says so. The rule for names is the listed fold's: an inherited name may not collide with a name the type declares, so a type that implements a method of its own leaves the inherited one out by name. Should Engine later gain a member whose name SuperEngine also declares, the fold is refused where it is written rather than one quietly overriding the other. A name after but must be a public member of the type: one it does not have is an error, and so is a private one, a static, or the type's final or clone, none of which * ever folds, so there is nothing to leave out.
What folds through a field, and what does not#
- The field must be pub, and only public members fold. Visibility is transitive: a folded name is reached through the field, so a private field cannot lend its members out, and a private member of the field's type stays private. A folded name is public because its path is.
- Fields, methods and macro methods fold. A static function or variable of the field's type does not. A field is the route to something each value holds, and a static is one thing for the whole type, never reached through a value, so it has no business arriving through one; it folds only on a type, by extends or a sibling use, and is otherwise named through its type. Neither does a macro without a self parameter, nor Self, nor the field type's own final or clone.
- A field held through a reference folds. With pub engine &'a mut Engine use fuel, thrust, the hop reaches through the reference as car.engine.fuel does, and the reference itself is the receiver of a folded method.
- Only a struct folds. A trait and an enum are abstractions over their implementers, with no organizing details of their own to fold through; a use clause on one of their fields is refused.
- A fold needs the field's type complete. A type cannot fold from a field of its own type, nor may two types each fold from the other.
Receivers#
A folded method is reached exactly as the path written out would reach it. Through a reference to the folding type, the field is reborrowed with the reference's own permission, so (&mut car).thrust(1.0) and c.thrust(1.0) for a c &mut Car both hand thrust a &mut Engine. From a value of the folding type, the receiver is the field's value: a method that takes self by value copies the field, as car.engine.rating() would, and one that declares self &mut borrows it from a mutable car, so car.thrust(1.0) is car.engine.thrust(1.0) (see Implicit coercion of self). The fold adds no borrowing rule of its own.
What a fold grants#
A fold grants names, and with them structural compliance with any trait those names satisfy: a type whose folded power matches a trait's requirement meets that trait, and may be reached through a virtual reference to it. Behind the virtual reference the compiler supplies the shift to the field, since the call site cannot know the type folded the method; nothing in the language can name what it supplies.
A fold grants no subtyping to the field's concrete type, and no layout: a Car is not an Engine, and &Car does not recast to &Engine. A struct is a subtype only of the traits it meets, never of another struct (Structs and traits).
A folded field is not a field of the type. It is a name for storage inside another type's value, so it cannot be named in the type's construction (new SuperEngine(engine: new Engine(fuel: 1.0, torque: 2.0)) is the construction), and it meets no trait's field requirement. A folded method does count toward conformance.
Delegating to a view the type lends#
Some types hold a thing and show the world a view of it. A String holds a block of bytes, and lends them as a &str, a borrow of text. Everything one can ask of text (its length, whether it starts with something, where something is in it, a part of it) is a method of str, written once. A type that lends a body can make those methods its own names, without writing any of them, by saying which of its methods does the lending:
struct Title {
text Uni[str];
// The borrow of text this type lends
pub fn view(self &) &str { text; }
// Every public method of str is now a name of Title, reached through view()
use str via view;
}
let t = new Title("Dune"); // a literal is copied into the Uni[str]
t.len // 4: t.view().len
t.startsWith("Du") // true: t.view().startsWith("Du")
&t[1..<3] // "un": t.view().slice(1, 3), a &str of the bytes 1 and 2
t < "Dunes" // true: t.view() < "Dunes", compared byte by byte
It is the field's fold with the receiver rewritten differently. A field's fold reaches a part of the value, car.engine; this one reaches what a method gives, title.view(), so no field is involved and no storage is added. As with a field, no forwarding method is generated: t.len is a call of str's own len, with the borrow view gave as its self, and the same method serves every type that lends a str. The borrow of the call is the view's: while a result of t.trim() is used, t stays borrowed, as it does for t.view() itself.
- The clause is a sibling's with a via. use str via view; admits every public member; a list after it admits just those, and but leaves some out, as everywhere (use str via view but len, find;). A name the type declares itself is the type's own, and folding the same name is a collision, so the type leaves it out by but and keeps its own. String does this for the few that take a &Array[u8] or answer a copy.
- The lending method is the type's own: one the type declares, taking the type as self and giving the borrow. via naming nothing the type declares, or a field, is an error.
- Only methods fold, and only a struct is lent: a field of the body is not reached through a borrow, a static has no value to lend, and an enum or a trait has no body.
- Every holder reaches them. A value, a borrow, a Uni[Title] or an Rc[Title] calls a folded method as it calls the type's own, because every owner already lends its value as a borrow to a method's self (see Implicit coercion of self). Nothing is written per region or per permission.
- Operators come with them, and compare the lent text. == and the orderings between two kinds of text (a literal, a String, a Uni[str], an Rc[str]) compare their bytes, whichever are which, so title == "Dune" and "Dune" < title both work; a type that lends text is replaced by its borrow on either side. === is still whether two references are the same place.
- Ranges are the lent text's. &t[a..<b], &t[a..] and &t[a..b] call slice, sliceFrom and sliceThrough, which refuse a bound that falls inside a character.
- Where a literal is wanted as the type, a static function fromLiteral(text &str) the type declares makes one from it (let t Title = "Dune";, or "Dune" passed where a Title is a parameter); that is how a literal becomes a String wherever one is wanted. Where a read-only borrow of the type is wanted (&Title), the literal is lent as a temporary Title.fromLiteral("Dune") that lasts to the end of the statement, or of the block when a local's initializer holds it; a borrow that writes (&mut Title) is refused. The same holds for a parameter's default, t &Title = "Dune": each call that omits the argument makes its own temporary. Where another candidate takes the literal as the &str it is, that one is chosen, and nothing is made.
- What it does not do: a lend grants names and no subtyping (a Title is not a str, and is passed where a &str is wanted as t.view()), and a folded method fills a trait's slot, so is reached through a virtual reference to the trait, only if it is not inline: the slot points at a small function that lends the view and calls the body's method on it, and an inline method has no function to point at.
The body of a run-time length, Array[T], is lent the same way. A List[T] lends the &Array[T] that its view() gives, and folds the methods of Array[T] in with use Array[T] via view;: its len is Array[T]'s, written once, which every owner of an Array[T] reaches too (dynamically sized bodies).
Shorthand: folding through a global#
A module is a namespace too, and a global variable is to a module what a field is to a type, with one difference: there is exactly one of it. So a global carries the same clause, and folds its type's members in as names of the module:
struct Config {
pub width i64;
pub height i64;
pub fn area(self) i64 { width * height; }
}
let config Config = new Config(80i64, 24i64) use *;
fn banner() i64 {
area(); // config.area()
}
width, height and area are now names of this module, reached through config. Every form the field's clause takes is here: use width, area as size lists and renames, use * but height leaves one out, and a long list may be written as a block, use { width, height, area as size }. What folds and what does not is the field's rule, unchanged: only public members, no static, no final or clone, and the source must be a struct rather than a trait or an enum.
What this buys, that folding one module into another cannot, is a module made out of a struct: the module presents a singleton's interface as its own names, and a caller writing area() need not know there is a Config behind it. It costs nothing at run time either. A folded name is a name for one thing at one address, known when the program is compiled, so there is no receiver to find and nothing extra is generated: the call is the one you would have written by hand.
What the module shows of it#
A fold is private to the module that made it, whatever the member's visibility in its own type. That is the rule a folded name has everywhere: the name is this module's, so this module says who may see it. Writing pub before the clause's use opts in, first as it is on every declaration:
pub let config Config = new Config(80i64, 24i64) pub use width, area as size;
Now another module may write display.width and display.size() (for a module named display). The global itself must be pub for that, since it is what the folded name is reached through. Folding never widens visibility past the origin either: a member that is private in Config does not fold at all, with or without pub.
An extern global carries the clause too, since the fold reads only the global's type, which an extern global declares. That is how a package's include file passes on what a folded global puts into the package: it declares the global extern, with no value, and repeats the source's clause, pub extern let config Config pub use width, area as size;. A program compiled against the include file then reaches display.width, or width where it folds the package in, exactly as it would against the package's source; each is the package's one config. Nothing checks that a hand-written include file folds what the source does.
Extending a type you did not write#
Sometimes what you want is not a part but a better version of a whole. You have somebody's floating-point type, or matrix type, or parser state, and you want it plus trigonometry, plus logarithms, as if they had always been one type. You do not reach into their package and add methods to it. You declare a type of your own that names theirs as its base, an enrichment:
struct Meter {
pub var cm i32;
scale i32;
pub fn scaled(self) i32 { cm * scale; }
pub fn make() Meter { new Meter(4, 5); }
}
struct Gauge extends Meter {
pub fn doubled(self) i32 { cm + cm; }
pub fn reset(self &mut) { cm = 0; }
}
Gauge adds methods and no fields. Everything Meter has is a name of Gauge (its fields, its methods, its static functions), and Gauge's own methods read and write those fields by bare name, as doubled and reset do. What it may not do is declare a field. Reading and writing the fields it starts with is exactly what it is for; adding one is what would make the two types different shapes.
The two types interchange, both ways, at no cost#
Because the representations are identical, a Gauge may be used wherever a Meter is wanted, and a Meter wherever a Gauge is wanted: by value, through a & reference, and through a &mut reference. No conversion runs; there is nothing to convert.
fn wantsMeter(m Meter) i32 { m.scaled(); }
let g Gauge = Meter.make(); // a Meter value, bound to the enriched name
wantsMeter(g); // and a Gauge, passed as a Meter: 20
g.doubled(); // 8
That pair is how values of an enriched type are obtained at all. The base's own constructors and static functions still return the base type (Gauge.make() hands back a Meter), and binding the result to the enriched type is all that is needed. A library that never heard of your enrichment can still produce values for it.
They remain two distinct types, and which one you name decides what is in view. A value typed as Meter cannot call doubled; you reach the added methods by naming Gauge somewhere. Methods do not drift into scope because some module somewhere added them: an enrichment is owned, and it is reached deliberately.
The declaration is what licenses it#
Two types interchange because one of them said the other was its base, not because they happen to look alike. Identical representation is why the license is safe to grant; the declaration is why it is granted at all.
- Two look-alikes that named no base stay unrelated. A struct with the same fields in the same order as Meter is not a Meter and does not substitute for one. Cone has no structural typing between concrete types.
- Nothing lifts through a container. Pair[Gauge] and Pair[Meter] are two instances of one generic, neither of which extends anything, so they are unrelated however their components are declared. Substitute at the element, never at the container.
- A type enriches one base, and a chain is allowed: Fancy extends Gauge extends Meter. Every hop adds methods only, so all three are one representation and any of them substitutes for any other. Siblings interchange too: two types that both extend Meter both have its representation.
- An enrichment may not override. A name the base already has may not be redeclared, whether the base's is a field or a method. One value would otherwise mean two different things depending on which of the two names was used to reach it.
- Only a concrete struct may be enriched. An abstraction holds no value, so there is nothing of it to enrich: what a type asserts about an abstraction is conformance, which is is, and a type may write both clauses, in either order. An enum's variant set is its identity, so adding to an enum means adding variants, which is a separate relationship (Extending an enum).
A finalizing base finalizes its enrichments too#
A base may declare a finalizer, and enriching it changes nothing about how its values are destroyed:
struct File {
pub fd i32;
fn final(self &uni) { print <- "closing ", fd, "\n"; }
}
struct LoggedFile extends File {
pub fn describe(self &) i32 { fd; }
}
{
let f LoggedFile = new File(3); // closed once, at the end of f's life
print <- "fd ", f.describe(), "\n";
}
prints fd 3, then closing 3. LoggedFile has its own copy of File's final, running on a LoggedFile, so a value is finalized by the same logic whichever of the two names holds it, and, since a finalizer makes a type move rather than copy, a LoggedFile moves too. Where the base also holds fields that are finalized, the finalizer runs first and then theirs, exactly as for the base. A clone method comes across the same way, and one that builds a Self builds the enrichment.
Two consequences of rules already stated: the enrichment may not write a final of its own over the base's, because that is overriding a name the base has; and it may not add a field, even one that owns a resource, so what its values have to release is exactly what the base's do.
What the base declares about its values comes across the same way. An enrichment of a type declaring is Move moves, and an enrichment of an @opaque type is opaque: the declaration is written on the base, but what it describes is the one representation both names share. So an enrichment of a type that moves may not declare is Copy.
An enrichment is inside its base's encapsulation#
An enrichment is acting as the base, so it sees what the base's own methods see. Above, scale is private to Meter's module and Gauge's methods may read and write it, even when Meter lives in another module or another package. What comes across keeps the base's visibility, so it is private to Gauge's module, and Gauge's clients outside it cannot reach scale at all. The asymmetry with a field's fold is deliberate: a type that folds through a field is a client of the part and sees only what is public, while an enrichment is the whole.
A type that may be extended has its representation in its contract. This is the price of the paragraph above, and it is worth stating plainly because nothing in the language can warn you about it. extends is an intimate relationship, and its author takes the coupling knowingly. Adding a field to a base changes the size and shifts the positional literal, so it is a major version change and never a quiet one. But so is renaming or retyping a private field at the same size, invisible to everyone except an enrichment reading it, and the base's author has every reason to think private means free to change. If you publish a type others may extend, treat its field names and types as part of what you promised. Semantic versioning is the protection, and it works because two versions of one package may coexist in a binary.
Meeting a trait the type does not: retroactive conformance#
Cone notices trait conformance structurally: a type that happens to have the methods a trait requires may be passed where that trait is wanted, without declaring anything (Traits). Put that together with enrichment and something useful falls out. Suppose a library's type lacks one method a trait requires. You supply it in an enrichment of your own:
trait Area { pub fn area(self &) i32; }
struct Framed extends Meter {
pub fn area(self &) i32 { cm * cm; }
}
fn areaOf(a &<Area) i32 { a.area(); }
let f Framed = Meter.make(); // a Meter value
areaOf(&f); // 16: reaching a trait Meter does not satisfy
The library's own values reach the trait by being bound to your name. You did not modify their package and you did not need their permission. And because the conformance hangs off your named type and its own table of methods, two people can enrich the same base in conflicting ways without colliding: each one's claim is reached only through the name that made it.
Generic types#
A generic may enrich a generic, struct Rich[T] extends Holder[T], and the license sits on that one declaration rather than on each instance, because no field may be added and so the shapes agree instance by instance. Inside such an enrichment, a member taken from the base is reached as self.name rather than by bare name, because the base is not a type yet when the enrichment's bodies are read. This is the same limit an inherited member of a generic trait has.
Reusing capabilities: several enrichments of one base#
The motivating case for enrichment was never one library. You have somebody's floating-point type. One package has already enriched it with trigonometry. Another has enriched it with logarithms. Neither knows about the other, and you want all three at once, plus a method or two of your own. So you enrich that same base and use their enrichments in your body:
struct Trig extends Meter {
pub fn sine(self) i32 { cm * scale; }
pub fn span(self) i32 { cm * 2; }
pub fn tangent(self) i32 { cm * 3; }
}
struct LogExp extends Meter {
pub fn logOf(self) i32 { cm + scale; }
pub fn span(self) i32 { cm * 4; }
}
struct Rich extends Meter {
use Trig but tangent, span;
use LogExp logOf, span as wide;
pub fn both(self) i32 { sine() + logOf(); }
}
let r Rich = Meter.make();
r.both(); // 29: sine, 20, plus logOf, 9
r.wide(); // 16: LogExp's span
A type named by a use here is a sibling: it declared the same base this type declares. That is the whole rule, and it is what makes the fold sound: a sibling's method takes a receiver of the sibling's type, and values of a type that shares the base substitute for that type, so sine may run on a Rich exactly as it runs on a Trig. Nothing is copied, nothing is forwarded, and no wrapper is generated. One declaration, in your namespace, and neither library was touched. Naming Rich is what puts all of it in view, and values still come from the base the ordinary way.
Selective, and renaming#
A bare use Trig takes every member the sibling adds. Naming the sibling is what asks for its members, so there is no * to write here, and the two ways to narrow it are the two a field's clause has:
- use Trig but tangent, span: everything except those.
- use LogExp logOf, span as wide: only those, the second under a spelling of your own.
The example needs both. Trig and LogExp each declare span, and neither is wrong to. A collision between two siblings is reported at your declaration, and your declaration is where it is settled, by leaving one out or renaming it, without asking either package to change anything. That is the case this construct exists for.
What folds from a sibling#
Only what the sibling adds. Everything it took from the base is already yours by the same route, so it does not arrive twice: use Trig cm and use Trig scaled are both errors, and a bare use Trig passes them over rather than colliding on every one. What does fold is the sibling's own methods, its overload sets, its macro methods, and its static functions and variables. A static keeps its owner and its signature, exactly as a base's does: a static folded from Trig that returns a Meter still returns a Meter. The other limits:
- A sibling may not add fields, and cannot: it is an enrichment, so it has none of its own. Every type sharing a base has one representation, which is what the whole construct rests on. Folding a type that could add fields is a restriction for now, not a wall.
- Only a type with a base has siblings. A use in a type that declares no extends is refused, and so is one naming a type whose base is a different one: its methods would read fields you have not got. To reuse an unrelated type, declare a field of it and fold through the field.
- Only extends takes siblings. is names abstractions, which hold no value for a folded name to be reached through, and it has no use for them.
- The base itself is not a sibling of yours, nor is any type along the chain down to it. extends has brought all of it already.
- A sibling's private members stay its own. You and it are both inside the base's encapsulation, which is why its methods may read the base's scale on your value. Neither of you is inside the other, so only what the sibling declares pub folds, and naming a private member is an error, whether in a list or after but. And final and clone belong to its own values' lifecycle and never fold. Nor does what the sibling has from the shared base, which you have already, so but naming one of those is an error too: there is nothing to leave out. (A base's private member is the base's: the message says private to Meter, not to the sibling.)
A use inside a type is always this: composition, on the producer's side, saying what this type is built out of. It is never an import: ordinary name lookup inside a type's methods comes from the enclosing module, as it always did.
Reusing a module: a module extending a module#
A module may extend another, reusing its names as its own.
A module can be built out of another one the way a type is enriched from a base: it takes in everything the other declares, and adds its own beside it. The module's declaration names the module it extends:
// src/shapes.cone
mod shapes;
pub var @threadlocal count = 0i64;
pub fn area(w i64, h i64) i64 {
count += 1i64;
w * h;
}
fn scale() i64 { 2i64; }
// src/solids.cone
mod solids extends shapes;
// area and count are shapes' public names, and now names of solids too;
// scale is private to shapes, and solids may use it as well
pub fn volume(w i64, h i64, d i64) i64 {
area(w, h) * d * scale();
}
Everything shapes declares is now a name of solids, and inside solids it is written bare. So is every name a use clause of shapes folded in: if shapes writes import units use metre, then metre is a name of solids too. solids is inside shapes' boundary, the way a type that extends a type is inside its base's, so it reaches shapes' private names too.
What shapes imports stays shapes'. A module's imports are what it depends on, not what it contains, and each module says for itself what it depends on. So the name units, which that import binds in shapes, is not a name of solids: to write units.inch, solids imports units itself.
What solids shows is what shapes shows. A public name of shapes is a public name of solids: a module that imports solids reaches it as solids.area, or bare after import solids use *, exactly as it reaches what solids declares itself. A private name of shapes is private in solids, and no module outside solids reaches it through solids. A chain works the same way: if shapes extends another module, that module's names reach solids too, each as visible as it was.
For a module, extending is inheriting. A module has one instance, so there is nothing to copy: each name is a second name for the same thing, and the function, the type and the global stay shapes'. A global changed through solids is the one shapes.count reads: after volume(2i64, 3i64, 4i64), which is 48, and area(1i64, 1i64), both count and shapes.count are 2.
The module it extends must already be in reach (a sister, found where an import of her would find her, or a module this one imports) and is named by that one name. The rest of the rules:
- A module extends one module.
- It adds and does not redeclare. A declaration spelled like one of the extended module's names, public or private, is an error, as it is for a type that extends a type: one name would otherwise mean two things. That includes a name the extended module folded in with a use clause, but not the name of a module it imports, which is not one of the names it passes on.
- extends brings names, not the module's own name. shapes is a name of solids only if solids imports it.
- No loops. A module may not extend itself, a module it contains, or its parent. A module depends on the module it extends, so a chain of extends may not come back round, and a module may not import one that extends it: each is a loop.
- extends names a module to reuse. A module conforming to a module's abstraction is a different reading, spelled is: mod plain is Runner. On the mod line, extends comes first, then is, then a default fold.
Extending an enum: more variants#
Note: All of this is implemented except calling a method that takes self by value on a value typed as the enum itself.
An enrichment adds methods and never variants. The other direction a type can grow in is more representations, and for a closed set of variants that is an enum extension: a new enum that starts with the base's variants and adds its own. (For a set that other modules may add to without anyone declaring a new enum, use an open trait instead.)
enum Colors {
Red, Green, Blue;
}
enum RichColors extends Colors {
Cyan, Magenta;
}
The extension has its own copy of each of the base's variants, followed by the ones it adds. So RichColors has five variants, RichColors.Red, RichColors.Green, RichColors.Blue, RichColors.Cyan and RichColors.Magenta, each a type of its own. The copies come first and keep the base's tag values, and each carries whatever the base's variant does: its fields, and its methods.
A variant is a value of its own enum and of no other. Colors.Green is a Colors; the RichColors value is RichColors.Green:
let plain Colors = Colors.Green; let rich RichColors = RichColors.Green; // the extension's own Green let added RichColors = RichColors.Magenta;
All five are names of RichColors, so use RichColors; makes all five bare. A module that also folds Colors in would have two of each name the two enums share, so it chooses with as or but. A pattern on a RichColors names all five bare with neither, and means the extension's own. So does a variant written inside RichColors's braces: a method of Cyan that writes Red builds RichColors.Red. A method a copy brought with it was written inside Colors's braces, so its bare Green is still Colors.Green, as its signature, written for Colors, expects.
No substitution runs between a base enum and its extension, in either direction. They are two distinct types, and the compiler enforces it: neither a value nor a reference nor a virtual reference of one may stand where the other is expected, and neither may one's variant. So a match on the base is still exhaustive over the base's own variants, which is the property the whole construct rests on: a value typed as the base can only ever hold one of the base's variants, so a match naming those needs no else. A match on the extension must account for the added variants as well.
This is worth saying outright because the intuition points the other way. Elsewhere the subtype is the narrower type, whose value stands in for the wider one: a variant for its enum, a struct for a trait it meets (Subtyping); here, adding variants would make a supertype, since every value of the base is a valid value of the extension, and no value of the extension is a valid value of the base. Cone does not take that direction, and the two types stay unrelated.
Generic enums#
A generic enum may extend and be extended. Since a generic enum is a family of types, what an enum extends is one of its instances, so the base is always written with its type arguments. A generic extension usually passes its own parameters on:
enum Pending[T] extends Option[T] {
Waiting;
}
Pending[i32] then has three variants: its copies of Option[i32]'s None and Some, holding an i32, and Waiting. They are built, named and matched as any generic enum's are:
let p Pending[i32] = Pending.Some(5);
let w Pending[i32] = Pending.Waiting;
match p {
case let s Some {s;} // Pending[i32]'s Some, holding an i32
case is None {0;}
case is Waiting {-1;}
}
An enum that is not generic may extend one instance of a generic enum. Its copies are then ordinary variants, each holding what that instance's does:
enum IntOrWait extends Option[i32] {
Waiting;
}
let n IntOrWait = IntOrWait.Some(9);
Everything said above holds instance by instance. Pending[i32] and Option[i32] are two types, and neither's values are the other's. Each is laid out on its own, so Option[&i32] is still a bare pointer while Pending[&i32], with a third variant, carries a tag. A generic base named with no type arguments, as in extends Option, is an error.
Extending an extension#
An extension may itself be extended, plain or generic. Each level copies the one below it, that level's own copies included, and adds its own variants:
enum RicherColors extends RichColors {
Gold;
}
RicherColors has six variants: its copies of RichColors's five, then Gold. Every copy keeps the tag value it had where it was first declared, and the whole chain shares the discriminant of the enum at its bottom. Each level is a type of its own, as a base and its extension always are, so a match on a RichColors is still exhaustive with no arm for Gold.
What an extension may declare#
Beside the variants it adds, an extension may declare methods with a body, static functions and statics of its own:
enum RichColors extends Colors {
Cyan, Magenta;
static shades i32 = 4;
pub fn palette() i32 {shades * 2;}
pub fn weight(self &) i32 {palette() + 1;}
}
A method with a body is given to every variant of the extension: the ones it adds, and its copies of the base's too, so RichColors.Red has a weight method. Inside each, self is that variant, as it is for a method declared on the base. The base's own variants are untouched: Colors.Red has no weight, and neither does Colors. If the base's variant already declared a method of that name itself, its copy keeps its own. A static function and a static stay the extension's own: bare inside its braces, and RichColors.palette() from outside.
Inside an extension's braces, its base's names are bare too, exactly as its own are: the base's static functions, statics and methods, whether the base is generic or not, in the extension's methods and in the variants it adds. Down a chain, every level's names are. So if Colors declares a static function make(), a method of RichColors or of Cyan calls it as make(), and Colors.make() still works too. Where a name is both the extension's and the base's (the copy of a variant is), the extension's is meant, as a nearer name always wins.
What cannot be given to the copies is declared on the base instead, whose members come along with the variants the extension copies:
- A method with no body, a requirement: the copies were written for the base, and have no body of their own to implement it in.
- A common field: it would change the layout of the copies, whose methods were written for the base's.
- The discriminant, which is the base's: an extension writes neither a tag field nor the integer type tag values are laid out in.
- An is, naming traits for the whole set: it would give the copies requirements they have no body to meet. The base's own is comes along with its variants, so an extension of an enum that is a trait is that trait too, copies and added variants alike. A variant the extension adds may still name traits of its own.
And an extension adds names to its base's, and never redeclares or overloads one. A name the base has (a method, a variant, a static, or one it inherits from further down a chain) means the same thing in every enum that extends it, so an extension declaring it again is an error, and so is an added variant taking the name of one the extension already has from its base.
The base's common members work through the extension exactly as through the base: a common field is readable from a value whose variant is not yet known, a default method is available, and a requirement dispatches on the variant, which an added variant must therefore implement, as the base's own variants do.
An extension is inside its base's privacy boundary. Its own methods and static functions, its added variants' methods, and the methods its copies brought with them, reach the private members of its own variants (the copies among them) and of the base's, as the base's own code does, even where the extension is declared in another module. From another module the reach runs one way: the base's code does not see what an extension adds, and two extensions of one base do not see each other's variants, copies included. In one module, all of them are the module's code, and see everything.
Each of the two enums is laid out on its own, since neither holds the other's variants. So extending an enum never changes the base: an Option-shaped enum of an empty variant and one holding a single reference stays a bare pointer, however many enums extend it. And the base never constrains the extension: an added variant may be larger than anything the base holds, and the two may differ about @unsized.
enum Packet {
pub id i32;
struct Ping;
struct Payload {pub a i64; pub b i64;}
}
enum @unsized MorePackets extends Packet {
struct Wide {pub a i64; pub b i64; pub c i64; pub d i64;} // larger than either
}
A tag value pinned by an added variant must still fit the discriminant the base lays its variants out in, sign included, and must not be one the set already holds. If you publish an enum others may extend, adding a variant to it in a later version shifts the values of every extension's own variants, since those are numbered after yours; writing the values out is how to keep them stable across that boundary.
extend: methods added to an existing type in place (not built)#
Not built yet: the compiler refuses an extend block ("Invalid global area statement").
An enrichment adds methods under a new name. The planned extend block would add methods to an existing type itself, after its declaration:
extend i32 {
pub fn double() Self {
self+self;
}
}
You can now fold a part's names into a type or a module, extend a type you did not write, combine other people's extensions, and grow an enum. None of it made one concrete type stand in for another; that takes an abstraction both can meet, which is what traits are for.