Source guide/modules.simd

1# Modules and Imports
2
3Silo organises code into **modules**: one per source file,
4grouped into **namespaces** by directory, collected into
5**packages** that sit under a top-level **namespace** name.
6Every declaration carries a visibility mode that controls who
7can see it; every file controls what it sees via a single
8`:use` block at the top.
9
10## Namespaces must have a slash
11
12External package paths follow the shape
13
14```
15namespace/sub:package.module.submodule.Name
16```
17
18The **namespace** part — everything before the `:` — **must
19contain at least one `/`**. This is how the compiler
20distinguishes an external import from an internal one: an
21internal module reference never has a `/` in it, while any
22external package reference always does.
23
24Valid external namespaces:
25
26```
27foo-org/foo          # a namespace with one slash
28deep/ns/path         # deeper nesting is fine
29```
30
31Invalid:
32
33```
34foo                  # no slash: not a valid external namespace
35```
36
37There is **one reserved exception**: `silo`. The `silo`
38namespace is reserved for the language itself. `silo:std` is
39the standard library; `silo:lang` is a conventional place for
40language-built-in items. No other no-slash namespace is
41allowed.
42
43Putting the pieces together, a fully-qualified external path
44looks like:
45
46```
47silo:std.iter.CharsIter           # namespace "silo", package "std",
48                                   # module "iter", name "CharsIter"
49foo-org/foo:types.User             # namespace "foo-org/foo",
50                                   # package "types", name "User"
51deep/ns/path:pkg.mod.Thing         # multi-segment namespace
52```
53
54Within a package you use short paths — just the module chain
55from the package root. The `/` rule is specifically for
56referring to *other* packages.
57
58## Distinctive features
59
60Aside from the namespace rule, the module system's parts worth
61knowing up front are the lack of an orphan rule on trait
62impls, the way imports are explicitly scoped rather than
63globally visible, and the `sealed` visibility that lets a
64declarer allow use without allowing implementation. The rest
65of the chapter walks through the mechanics.
66
67## One file, one module
68
69A `.si` file is a module. Directory structure forms namespaces.
70If a package has:
71
72```
73src/
74├── lib.si              # package root
75├── geometry.si         # module `geometry`
76├── math/
77│   ├── mod.si          # module `math`
78│   └── trig.si         # module `math.trig`
79└── net/
80    ├── mod.si          # module `net`
81    └── http.si         # module `net.http`
82```
83
84then the qualified path `math.trig.sin` refers to the `sin` word
85in `src/math/trig.si`. The dots in the path are namespace
86separators.
87
88Fully qualified external paths additionally include the package
89name: `silo:std.iter.CharsIter` refers to the `CharsIter` type
90in the `iter` module of the `std` package in the `silo`
91namespace. See [the standard-library guide's
92`silo:std.lib`](./mod.simd) for the canonical shape.
93
94Within a module, declaration order doesn't matter — every
95declaration is visible throughout the file. Circular
96dependencies between modules are allowed too; the compiler
97resolves them at link time.
98
99## The `:use` block
100
101Imports live in a `:use` block at the top of the file. Paths
102inside a `:use` are always qualified the same way external
103references are elsewhere in the language — with the namespace
104prefix for other packages, or with the module chain for things
105inside the current package:
106
107```silo
108:use
109  :open silo:std.collections Vec HashMap
110  :open silo:std.traits Eq Ord
111  :open foo-org/geometry:shapes Point distance
112  :open my-app.internal.helpers Logger
113  :open silo:std.iter Iterator => Iter    # aliased
114  :impl foo-org/logging:adapters (Display Point)   # orphan impl
115  :aspect foo-org/telemetry:tracing Trace          # aspect
116:end
117```
118
119Four import directives:
120
121- **`:open <module-path> <names...>`** — bring specific words
122  / types into scope from another module. The arrow form
123  `Iterator => Iter` aliases the import locally (useful when
124  two modules export the same name or you want a shorter
125  handle).
126- **`:impl <package-path> (Trait Type)`** — bring an **orphan
127  impl** into scope. See below.
128- **`:aspect <package-path> Aspect`** — bring an aspect into
129  scope so its operations are callable without qualification.
130
131Every name a `:use` block brings in must be explicit — there
132is no glob import. If you need twenty things from a module you
133name twenty things, or you reach for an **aggregation module**
134that re-exports them in one place using `:open(pub)`, and then
135`:open` from the aggregation module instead.
136
137The one exception to the explicit-import rule is
138`silo:std.prelude`: it is **automatically imported into every
139module** (`si[module.prelude+4]`), so the common stdlib types
140and traits are always in scope without an explicit `:use`. The
141prelude itself is an ordinary module — the only special thing
142about it is the automatic import. It is not a mechanism
143packages can opt into for their own preludes; your own
144aggregation modules still need an explicit `:use` at each
145consumer.
146
147## Visibility modes
148
149Every top-level declaration has a visibility, given in parens
150after the keyword:
151
152| Mode       | Meaning                                              |
153|------------|------------------------------------------------------|
154| `mod`      | Module-private. The default.                         |
155| `pkg`      | Visible within the same package.                     |
156| `pub`      | Visible everywhere.                                  |
157| `sealed`   | Visible everywhere, but **impl-restricted to the declaring package**. |
158
159Examples:
160
161```silo
162:fn(pub) public-api ( -> Int )   42 :end
163:fn(pkg) internal-helper ( -> Int )  7 :end
164:fn private-detail ( -> Int ) 3 :end          # defaults to mod
165```
166
167### `sealed`
168
169`sealed` is the one mode that doesn't just hide things. It
170applies to traits, effects, and aspects, and means "anyone can
171*use* me, but only my package can *implement* me."
172
173```silo
174:trait(sealed) Error self { (Display self) }
175  .message ( self -> Str ) "{}" format ;
176  .source  ( self -> (Option Error) ) drop None ;
177:end
178```
179
180In the same package, impls of `Error` are fine:
181
182```silo
183:impl Error MyError
184  .message ( MyError -> Str ) .msg ;
185:end
186```
187
188In a different package, the compiler rejects the attempt:
189
190```silo
191# In some other package:
192:impl Error ForeignType ...       # compile error: Error is sealed
193```
194
195Same story for `:effect(sealed)` — callers can constrain on the
196effect, but only the declaring package can `:with` a handler.
197Useful for host-level effects where the host is the only valid
198implementer.
199
200## No orphan rule
201
202**Any package can implement any trait on any type** — including
203standard-library traits on your own types, and your own traits
204on standard-library types, and third-party traits on fourth-
205party types. Coherence is handled per-import-scope rather than
206per-program: an impl is only *in scope* where an `:impl` import
207brings it in.
208
209Rust forbids this — you can only implement a trait for a type
210if you own one of them — because Rust's resolution is global
211and overlapping impls from different crates would break that
212global view. Silo resolves impls from the import scope at the
213call site, so "crate A's impl of (Display T)" and "crate B's
214impl of (Display T)" can coexist in the world as long as no
215single file tries to bring both in at once.
216
217- **Automatic scope**: an impl defined in the same package as
218  the trait, or in the same package as the type, is in scope
219  whenever the trait or the type is imported. That covers the
220  common case and you rarely need explicit imports.
221- **Orphan impls**: impls defined in a package that owns
222  neither the trait nor the type must be brought in via
223  `:impl package (Trait Type)` in the `:use` block.
224
225```silo
226:use
227  :open foo-org/geometry:shapes Point
228  :impl bar-org/logging:adapters (Display Point)
229  # Display from silo:std, Point from foo-org/geometry,
230  # impl defined in bar-org/logging — orphan, explicit import
231:end
232```
233
234If two orphan impls of the same `(Trait Type)` pair both try to
235be in scope at the same call site, it's a compile error and the
236importer disambiguates by dropping one of the `:impl` imports.
237:gloss[Specialisation](./A1-glossary.simd#specialisation) handles the
238cases where one impl is strictly more specific than another.
239
240The benefit: third-party glue code is possible. You can add
241`Display` to someone else's type, add a custom `From` between
242two types you didn't write, without "newtype wrappers"
243everywhere. The cost: importers sometimes have to say which
244orphan impls they want.
245
246## Sealed vs orphan
247
248The two mechanisms address different concerns:
249
250- `sealed` is **about the author**: "I don't want anyone
251  implementing this trait because I need invariants." The author
252  restricts downstream behaviour.
253- No orphan rule + scoped impls is **about the ecosystem**:
254  "anyone can add impls but coherence is per-import-scope." The
255  ecosystem gets mix-and-match flexibility.
256
257Use `sealed` for traits where implementer control matters
258(error taxonomies, capability traits). Default to non-sealed for
259everything else.
260
261## Preludes
262
263The standard library has a prelude module (`silo:std.prelude`)
264that re-exports the commonly-used names:
265
266- Core types (`Int`, `Str`, `Bool`, `Vec`, `HashMap`, etc.).
267- Common traits (`Eq`, `Ord`, `Hash`, `Display`, `Debug`,
268  `From`, `Into`, `Foldable`, `Iterator`, …).
269- Common aspects (`CurrentLocale`, tracing, logging where
270  appropriate).
271
272Every package auto-imports the prelude. Your own package can
273define its own prelude module that extends or overrides the
274standard one for internal code.
275
276## Key points
277
278- One module per `.si` file; directories form namespaces.
279  `mod.si` under a directory is the directory's own module.
280- Imports go in a single `:use` block at the top of the file:
281  `:open`, `:impl`, `:aspect` directives.
282- Visibility modes: `mod` (default, module-private), `pkg`,
283  `pub`, `sealed`. `sealed` restricts **implementation**, not
284  use.
285- No orphan rule. Instead, `:impl package (Trait Type)` imports
286  orphan impls explicitly, and automatic scope covers the
287  common case.
288- :gloss[Specialisation](./A1-glossary.simd#specialisation) breaks
289  ties when multiple impls match; ambiguous cases are compile
290  errors.
291- External paths are `namespace:package.module.Name`; the
292  namespace part **must contain a `/`** (`foo-org/foo`,
293  `deep/ns/path`) except for the reserved `silo` namespace.
294  Internal paths drop the `namespace:package` prefix.
295
296Next: [lifecycle](./17-lifecycle.simd) — locality modes,
297`:dispose`, and how Silo handles memory without a garbage
298collector.