Imports & Modules¶
Split a program into .dream files as it grows. An import makes another file's public declarations available. You can also import a built-in library package or an installed external package.
At a glance
- Each
.dreamfile is a module;importbrings in another module's public declarations. - Built-in library packages are imported the same way.
Import and module forms¶
| Form | Example | Effect |
|---|---|---|
import system; / import system.<package>; |
import system.io; |
Loads a built-in library package and its dependencies |
import <path>; |
import utils.math_lib; |
Loads utils/math_lib.dream relative to this file, falling back to installed dream_packages/ dependencies |
import <module>.<item> as <alias>; |
import utils.math.add as add_int; |
Makes one public or internal item of a declared module available under a new name |
module <path>; |
module utils.math; |
Puts this file's declarations in a named module; must come before every import and declaration |
Standard library packages¶
Opt-in stdlib APIs live under the reserved system.* package tree. A plain import loads that package (and its dependencies) into the program; public names are then usable unqualified:
import system; // System, DateTime, Stopwatch, Random, …
import system.collections; // List, Map, Set, Queue, Stack
import system.text; // string methods, StringBuilder, Regex
import system.json; // Json, JsonValue
import system.io; // File, FileHandle, Path, IoError
import system.encoding; // Encoding (UTF-8 / hex / Base64)
import system.logging; // Logger, LogLevel, handlers
import system.crypto; // Sha256, HmacSha256, SecureRandom
import system.task; // Task, TaskPool (real threads)
Always available without an import (bootstrap): Option, Result, Error, ParseError, Array, Bytes, Span, RawPtr, Arena, Promise, Math, js, comparison/Collection interfaces, and primitive extend methods (int.parse, bool.parse, …). String helpers require import system.text; (or import system;, which depends on text).
There is no import system.*; wildcard — import each package you need (the editor offers an auto-import quick fix when you type an unresolved stdlib name).
Reserved system / system.* paths always resolve to the embedded stdlib, not a local system/… file.
Importing a file¶
- The path is a dotted module path ending in a semicolon.
- Each
.maps to a directory separator, and.dreamis added automatically:import utils.math_lib;resolves toutils/math_lib.dream, relative to the importing file. - Imported declarations are usable directly — there is no namespace prefix.
- If no matching file exists relative to the importing file, resolution falls back to a
dream_packages/dependency directory installed by thedreamerpackage manager — soimport json_tools;can resolve to a project dependency oncedreamer installhas run, with no different syntax required. - Names that match a stdlib package (
system,system.io, …) never fall through to the filesystem.
// main.dream
import math_lib;
import system;
fun main() {
System.println(add_numbers(10, 20)); // 30
}
Imports resolve recursively (an imported file may import others), and each file is processed only once even if imported from several places.
Modules¶
A file can opt into a named module by declaring it at the very top, before any import:
- At most one
moduledeclaration per file, and it must be the first thing in the file (beforeimportand before any other declaration). - A module path is purely a name —
module utils.math;does not need to live in autils/mathdirectory. File resolution for plainimports is still directory-based, exactly as before;moduleonly tags a file's declarations with a logical namespace, independent of where the file sits on disk. - Files that don't declare a
modulestay in the implicit, unnamed root module — today's flat, unqualified behavior is unchanged for any file that never writesmodule. - Two files in different modules may declare the same name (e.g. two
public fun add(...)) without colliding. Two declarations in the same module (or both in the unnamed root module) with the same name still collide exactly as before — a module is still one namespace. - Same-module overloads and cross-module duplicate names compose: within one module you may overload
addby signature; across modules each side keeps its own overload set. Call sites andimport … asaliases still use the source names.
Aliased imports (resolving duplicate names)¶
Because two modules can legally define the same name, import has a second form — with a trailing as clause — that pulls in one specific item from a module (not a file) under a chosen local alias:
// vendor/math_lib.dream
module vendor.math;
public fun add(a: float, b: float): float {
return a + b;
}
// main.dream
import system;
import utils.math_lib; // file import, unchanged: loads utils/math_lib.dream
import vendor.math_lib; // loads vendor/math_lib.dream
import utils.math.add as add_int; // aliases the `add` declared in module `utils.math`
import vendor.math.add as add_float; // aliases the `add` declared in module `vendor.math`
fun main() {
System.println(add_int(1, 2)); // 3
System.println(add_float(1.5, 2.5)); // 4.0
}
The two import forms are told apart by the trailing as clause, not by a different keyword:
import <dotted-file-path>;(noas) — resolves against the filesystem, exactly as in Importing a file. The declaring file must still be pulled into the program this way (directly or transitively) before an aliased import from its module can see it.import <module-path>.<item> as <alias>;(withas) — resolves<item>against the declared module<module-path>, not the filesystem, and binds it into the current file's top-level scope as<alias>.
The aliased item must be public or internal; importing a private declaration this way is always an error, regardless of which module the importing file belongs to. If <alias> collides with another name already in scope in the importing file, that's a normal "already defined" diagnostic — pick a different alias.
There is no wildcard import pkg.*; in this version — only single-item aliased imports. This affects convenience, not reachability: it just means one import ... as line per name you want to consume unqualified from another module.
Visibility¶
Dream has three visibility levels — private (the default, no keyword), internal, and public — applied consistently across two independent axes.
| Keyword | Top-level declaration is visible in | Class member is reachable from |
|---|---|---|
| (none) | Its own file only | The type's own methods only |
internal |
Every file in the same module | Anywhere in the declaring type's module |
public |
Every file that can reach it | Anywhere the type is reachable |
File / module visibility¶
A top-level declaration (function, class, interface, enum, or global) is private by default — usable anywhere in its own file but invisible to any other file, even one that imports it.
internal— visible from any file that shares the same declaringmodule(or, for undeclared files, the shared unnamed root module), but not from a file in a different module.public— visible everywhere the file is reachable from (and, for functions, visible to the host that runs the program).
// lib.dream
module utils.math;
public fun public_add(a: int, b: int): int {
return a + b;
}
internal fun shared_helper(): int {
return 99;
} // visible anywhere in module utils.math
fun private_helper(): int {
return 1;
} // file-private
// other_file_same_module.dream
module utils.math; // same module as lib.dream
fun uses_helper(): int {
return shared_helper(); // ok: same module
}
// main.dream (unnamed root module)
import lib;
fun main() {
System.println(public_add(2, 3)); // ok: public
System.println(shared_helper()); // error: 'shared_helper' is internal to module utils.math
System.println(private_helper()); // error: 'private_helper' is not 'public'
}
Class member visibility¶
A class member (field, method, static method, accessor, or constructor) is class-private by default — reachable only from that class's own methods, regardless of file. static never implies visibility; a static member must still be internal / public to be called from outside the class.
Constructors follow the same rule: mark them public (or internal) to allow Type(...) from outside the type. An implicit zero-arg default (no constructor declared) is public. Destructors (del) are always private.
internal— reachable from anywhere in the declaring class's module (not just the class's own methods), but not outside the module.public— reachable from anywhere the type itself is reachable.
module utils.math;
public class Vector {
internal x: int; // visible anywhere in module utils.math
public fun length_sq(): int {
return helper_square(this.x);
}
}
How they compose¶
To use a member from another file you need both: the type must be reachable from the caller (its axis-1 visibility, evaluated against the caller's module), and the member must be reachable too (its axis-2 visibility, evaluated the same way). Effective accessibility from a given caller is the minimum of the two — an internal class only exposes its public members within the declaring module; even a public member is invisible outside it, because the type itself isn't reachable there.
public class Point {
public x: int;
public y: int;
}
public fun origin(): Point {
return Point(0, 0);
}
There is no protected modifier. Dream has no class inheritance — only interface implementation — so there is no subclass hierarchy for protected to grant access to.
Importing from JavaScript¶
Pulling in functions from the host (rather than another .dream file) uses extern fun. See JS Interop.