iyi

Learn · step 3 of 8

One keyword, and your own namespace

import loads a module and gives you its qualified name. The list or the glob written after the path, ::{...} or ::*, is what shortens that, and you write it, so no library can change what a name in your file means by adding an export of its own.

What the rule says

R-2b One keyword loads a module and names what it brings into scope, written by the consumer. import X loads X and leaves its names qualified (X::f); import X::{a, b} also brings a and b into unqualified scope; import X::* brings every exported name. pub import X re-exports the module, never names.R-2b SPEC.md, line 38

Run it

This step is grounded in samples/iyi/modules.iyi, 124 lines in the repository, read out of the tree when this page is built.

Run it in your browser from the module the compiler built for it.

Two steps in, the library from step one has a shape: three definitions, two of them exported, all of their types written down. Bringing that into a file of your own takes one line, and the line says how much of it you want.

samples/iyi/modules.iyilines 8 to 16
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}

import names a module and loads it at most once, so the import graph is a DAG rather than textual inclusion. Written bare, import app/greeter gives you the module under its qualified name and nothing more: polite is App::Greeter.polite and nothing shorter.

What you write after the path is what shortens it. ::* brings every exported name, ::{...} brings the ones listed, and the library does not get a say. That is the whole of R-2b, and it is a smaller idea than it sounds: a module's names reach your file only through a line in your file, and the glob is spelled out, so "every name" is visible on the line where it happens.

The list on the second line is the interesting one. app/greeter and app/formal both export title, so taking both whole would make every bare title in the file ambiguous. It takes exactly address from the second module and leaves that module's title behind. That list is a repair rather than mere tidiness.

Where the ambiguity gets reported#

Widen that second import to import app/formal::* and nothing happens. Two modules exporting one name is not a mistake. The error arrives later, at the first bare title you write, because that is the first thing that could mean either.

Then add your own def title to the file and it compiles again. A local definition beats an imported one, silently. That silence is deliberate: it is what stops a library breaking your build by adding an export.

The wall a qualified name does not climb#

Qualifying a name is not a way around the import. Call App::Greeter.polite from a file that never imported app/greeter, because some other file in the program did, and the compiler does not let the rest of the program's imports leak into yours:

Error: `App::Greeter` is not imported here — it is in the program only because
some other file imported it. Add an import in this file, or have a module this
file imports re-export it with `pub import` (SPEC.md R-1)
Quoted verbatim from README.md, lines 951 to 953.

It names the rule and both ways out. The rules are new, so the compiler teaches them at the moment they are broken, which is the only place a person is actually paying attention.

Now forget the names#

Do it the other way around. Import the module bare, and call one of its exported functions the way every other language would let you.


Break it

an unqualified name no import brought in

records/break/r2b_name_not_imported.iyi10 lines
# Breaks R-2b: a bare `import` brings a module into scope under its qualified
# name and nothing more. Unqualified names arrive only through the list or the
# glob on an import line, `::{...}` or `::*`, which the consumer writes, so
# importing a module never quietly changes what a name in this file means.
# `polite` is exported and still not in scope here.
module diagnostics/r2b_name_not_imported

import dep/greeter

puts polite("iyi")
records/break/dep/greeter.iyi19 lines
# A module the break programs import. Its surface is deliberately narrow: one
# exported function, one exported trait, and one function left unexported so a
# consumer has something it is not allowed to reach.
module dep/greeter

pub trait Greet
  abstract def greet : String
end

pub def polite(name : String) : String
  "#{greeting}, #{name}."
end

# No `pub`, so this is the module's own business. It is not in the module's
# declarations, which is what makes reaching for it from another module an
# error rather than a style question (SPEC.md R-1, IV.2).
def greeting : String
  "Hello"
end
$ IYI_PATH=<iyi>/src iyi build --no-codegen --no-color records/break/r2b_name_not_imported.iyi
Showing last frame. Use --error-trace for full trace.

In records/break/r2b_name_not_imported.iyi:10:6

 10 | puts polite("iyi")
           ^-----
Error: undefined method 'polite' for Diagnostics::R2bNameNotImported:Module

`polite` is exported by `dep/greeter`, and this file has not brought it into scope. Import it by name, `import dep/greeter::{polite}`, or call it as `Dep::Greeter.polite` (SPEC.md R-2b)
R-2bOne keyword loads a module and names what it brings into scope, written by the consumer. import X loads X and leaves its names qualified (X::f); import X::{a, b} also brings a and b into unqualified scope; import X::* brings every exported name. pub import X re-exports the module, never names.SPEC.md, line 38. Recorded run, exit code 1, iyi 0.17.0 (built on Crystal 1.22.0-dev [fe3db8354] (2026-10-14)), commit fe3db8354f56. Regenerate with npm run record:diagnostics.

What the rule bought

A bare import never changes what your unqualified names mean, so a library cannot capture a name in your file by adding one to itself, and a local definition beats an imported one silently. Both are what make it safe to depend on a module you do not control, which is the position every consumer of an artifact is in.

Standing on

samples/iyi/app/greeter.iyi
the module both imports name, exporting a title
samples/iyi/app/formal.iyi
the second module, exporting a title of its own
samples/iyi/modules.iyi
the glob, the list, and where an ambiguous name is reported
README.md
the diagnostic for a module this file never imported, quoted with its lines on the block