# Modules across files: `import`, and the names it brings into scope. # # ./bin/iyi run samples/iyi/modules.iyi # # Everything below compiles and runs today. `hello.iyi` covers traits and # `impl` in a single file; this one covers the part a single file cannot show. module samples/modules # R-1: `import` names a module and loads it at most once, so the import graph # is a DAG rather than textual inclusion. Bare, it brings the module into # scope under its qualified name and nothing more: after `import app/greeter` # alone, the module's names are reachable only as `App::Greeter::Greet` and # `App::Greeter.polite`. import app/greeter::* import app/formal::{address} # II.3: `::*` and `::{...}` are what make those names available unqualified. # They are written by the consumer, not performed by the library: a bare # import never changes what the importer's unqualified names mean, so a # library cannot capture a name in your file by adding one to itself. # The selective form narrows an import's names to the ones listed. Here it # is not decoration — `app/greeter` and `app/formal` both export `title`, and # taking both whole would make every unqualified `title` in this module # ambiguous. pub struct User getter name : String def initialize(@name : String) end end # `Greet` resolves through `import app/greeter::*` above, so `impl` needs no # qualification. # # R-3's orphan rule allows this impl here because `User` is declared here: # this is the type's module. It would equally be allowed in `app/greeter`, # the trait's module. Nowhere else — a third module that imports both is # refused, which is what makes two modules unable to define the same impl. impl Greet for User def greet : String # `polite` comes from `app/greeter`, `address` from `app/formal`, and # `shout` is this module's own function, declared below. All three are # written the same way, because to this file they are all just names. shout(polite(name)) + " " + address(name) end end # A module's own functions are in scope for the types declared in it, without # `self.` and without qualification: an iyi module is a compilation unit, and # these are its functions. Not exported, so nothing outside can call it. def shout(text : String) : String text.upcase end puts User.new("iyi").greet # `title` was reachable unqualified from neither import: `app/greeter`'s was # left ambiguous by nothing (only one import was whole), and `app/formal`'s # was excluded by the selective form. Qualification always works. puts App::Greeter.title puts App::Formal.title # --------------------------------------------------------------------------- # What this sample would report as an error # --------------------------------------------------------------------------- # # * Widening the second import to `import app/formal::*` and then writing an # unqualified `title` is an error, and it is reported at the `title`, not at # the import. Widening it alone is not an error at all: two modules # exporting the same name is not by itself a mistake, so nothing is wrong # until a name is written that could mean either (SPEC.md II.3). # # * Adding `def title` here makes even that legal again. A local definition # beats an imported one, silently, which is what keeps a library from # breaking its consumers by adding an export. # # * Moving the `impl` above into a module that is neither `app/greeter` nor # this one is refused by R-3, naming both modules where it would be allowed. # # * `import app/greeter::{greeting}` — `greeting` is declared there without # `pub`, so it is not part of the module's surface. The error names it at the # import, where the author can see which of the names they asked for they # cannot have. Widening to `import app/greeter::*` does not reach it # either; it simply is not there to be found (R-2b). # # * `App::Greeter.greeting` — qualifying it does not get around `pub` either. # It has to be unreachable, not merely absent from an import's names: # `.iyimod` carries a module's exports and nothing else, so if another # module could call an unmarked name, that metadata would not be enough to # compile against (SPEC.md IV.2). # # * `import app/v_1`, or any path segment that is not `[a-z][a-z0-9]*` with # single `_` between groups. A module declared `app/greeter` is reached as # `App::Greeter`, and the two notations are kept in step by making the # mapping reversible: the name splits back into a path at every upper-case # letter. `camelcase` drops an underscore before a digit, so `v_1` and `v1` # would both be `V1` — two paths naming one module (SPEC.md IV.6 #6). # # --------------------------------------------------------------------------- # Compiling this file without `app/greeter.iyi` # --------------------------------------------------------------------------- # # R-1 says a module that imports another reads its `.iyimod` and never opens # its source. That is not a claim about a future release: # # ./bin/crystal build --no-codegen --emit-iyimod /tmp/mods samples/iyi/modules.iyi # ./bin/crystal mod dump --declarations /tmp/mods/app/greeter.iyimod # mv samples/iyi/app /tmp/app-elsewhere # ./bin/crystal build --no-codegen --use-iyimod /tmp/mods samples/iyi/modules.iyi # compiles # mv /tmp/app-elsewhere samples/iyi/app # # The second command prints what the third one compiles against: `app/greeter` # with `pub def polite(name : String) : String`, `pub trait Greet` with its # requirement, and no bodies at all. `greeting` is not in it — it carries no # `pub`, and R-2b's "unreachable rather than absent from an import's names" # is the same statement as "not in the artifact". # # * No object code here: the first build was `--no-codegen`, so its artifacts # carry what another module may call and not how it is done, and the third # build typechecks against them; one that links is refused by name (SPEC.md # IV.1f). `--emit-iyimod` without `--no-codegen` carries the code too.
Press Run.
The pane no longer holds samples/iyi/modules.iyi. Run executes iyi-modules.wasm, the module the compiler built from the recorded file, so your edit is not in this run: there is no iyi compiler in this page. How this works
recorded with iyi 0.18.0 (built on Crystal 1.22.0-dev [2a9653052] (2026-10-18)) · Linux 7.0.0-38-generic x86_64, QEMU Virtual CPU version 2.5+ · 2026-10-19
You can type in the pane: nothing here compiles, so what you type cannot run, and the page says which bytes it is running the moment the text stops being the recorded file. There is no standard input, no filesystem and no threads on this target, and one sample fails on purpose for exactly that reason.
Run executes iyi-modules.wasm, the module the compiler built from this file, in your browser. How this works
Try it on your machine
Copy the program into modules.iyi, change something, and run it. Only a compiler on your own machine can run what you wrote.
$ iyi run modules.iyi