Modules and Imports
A Sec module is the shared source namespace formed by one source directory. All applicable Sec source files directly in that directory contribute declarations to the same module.
domain/orders/
order.sec
status.sec
validation.sec
Each ordinary source file in that directory declares:
module orders
External modules are accessed through explicit logical imports:
import "net/http"
fn Handle() void {
let cookie := http.Cookie(...)
}
One Directory Forms One Module
One source directory corresponds to one source module.
services/database/
connection.sec
query.sec
transaction.sec
All participating files declare:
module database
The files do not become separate modules merely because they are separate files.
Conversely, subdirectories form their own source modules rather than being folded automatically into the parent module.
Module Declaration
Every ordinary Sec source file declares the module to which it belongs:
module orders
The declaration appears as one module identifier.
It is not a filesystem path:
module domain.orders // invalid
module internal.storage // invalid
module platform.linux.amd64 // invalid
Directory hierarchy belongs to import resolution. It is not encoded as a dotted source-level module name.
Module Name
For an ordinary directory module, the expected module name is the final source-directory component.
| Source directory | Module declaration |
|---|---|
domain/orders/ |
module orders |
services/database/ |
module database |
internal/storage/ |
module storage |
services/internal/transport/ |
module transport |
The project root follows the root-module rules described in Projects and sec.module.
Module Name and Module Identity Are Different
The short source module name is not globally unique.
These may both be valid:
sales/order/
module order
customer/order/
module order
The modules remain distinct because their canonical import locations and import roots differ.
Source code normally sees the short module qualifier. The compiler tracks a stronger canonical module identity so that declarations from unrelated modules with the same short name never collapse into one semantic module.
Target selection may create different compilation instances of the same logical module without changing the logical module's identity.
Multi-File Modules
A module may be split across as many directly participating source files as is useful.
order.sec:
module orders
type CustomerOrder struct {
Id: uint64
}
create.sec:
module orders
fn CreateCustomerOrder() CustomerOrder {
return CustomerOrder {
Id: 0
}
}
No import is needed between these files.
They are two source files contributing to one module namespace.
Source-File Order Has No Semantic Meaning
Files in one module do not have a semantic compilation order.
A declaration in one file may refer to another declaration in the same module regardless of:
- filename sorting;
- filesystem enumeration order;
- editor-open order;
- parallel compiler worker order.
The compiler assembles the complete relevant declaration surface before ordinary body name resolution depends on source-file order.
Visibility Across Module Files
Sec's name prefixes retain their ordinary visibility meaning:
| Spelling | Visibility |
|---|---|
Name |
Public |
_name |
Module-internal |
__name |
Source-file-private |
A module-internal declaration may be used from sibling source files in the same module.
A source-file-private declaration remains private to its declaring file even though sibling files belong to the same module.
Module-path access restrictions and declaration visibility are separate checks.
Imports
Imports bind an external module to a qualifier in the current source file.
module server
import "net/http"
fn Serve() void {
http.Listen(...)
}
Importing a module does not merge its declarations into the current module.
Imported declarations remain owned by their original module.
Import Paths Are Logical Paths
Sec imports use canonical logical import paths:
import "domain/orders"
import "net/http"
Canonical import paths:
- use
/as the separator on every host platform; - are case-sensitive;
- are not host filesystem paths;
- do not contain
.or..traversal components; - do not use host-specific path separators;
- are resolved through Sec's import roots.
These are invalid:
import "../orders"
import "./orders"
import "/srv/project/orders"
The same source import has the same logical spelling on Linux, Windows, macOS and other supported hosts.
Standard Library Imports
Standard-library modules use their canonical logical path directly.
import "fmt"
import "io"
import "net/http"
import "net/dns"
There is no std/ prefix.
The compiler resolves canonical standard-library paths through the standard-library namespace before ordinary project/dependency fallback.
A project-local module must not silently replace a canonical standard-library module that owns the same logical path.
Platform Imports
platform is a compiler-reserved import root.
import "platform/linux/amd64"
Platform source selection is governed by the active compilation plan and target model.
If a reserved-root import fails, the compiler does not retry the same spelling as a project-local import.
This prevents accidental shadowing of compiler-owned platform identities.
Project-Local Imports
Ordinary non-reserved imports resolve within the active project source root.
import "domain/orders"
import "services/database"
The Project chapter defines how the source root and build inputs are selected.
Module semantics remain based on logical import identity rather than arbitrary paths relative to the importing file.
The Default Qualifier Comes from the Imported Module
Consider:
import "customer/order"
If the imported module declares:
module order
the default source qualifier is:
order.CreateCustomerOrder()
not:
customer.order.CreateCustomerOrder() // invalid
customer/order.CreateCustomerOrder() // invalid
The import path identifies the module. The module declaration provides its normal short source qualifier.
Import Bindings Are Source-File-Local
Imports do not become module-wide bindings.
create.sec:
module orders
import "database"
fn Create() void {
database.Insert()
}
A sibling file in module orders does not automatically gain the
database qualifier.
If that sibling file also needs the module, it declares its own import:
module orders
import "database"
fn Remove() void {
database.Delete()
}
Import Aliases
An import can provide a local qualifier:
import customerOrder "customer/order"
Use the alias in that source file:
customerOrder.CreateCustomerOrder()
The alias changes only the source-file-local binding.
It does not change:
- the imported module's identity;
- the imported module's own module name;
- declaration ownership;
- cross-module semantic identity;
- linking identity.
Qualifier Collisions
Two imported modules may have the same short module name. The current source file still needs unique local qualifiers.
import (
"sales/order"
customerOrder "customer/order"
)
Usage:
order.Ascending()
customerOrder.CreateCustomerOrder()
Without the alias, both imports would attempt to bind the local qualifier
order.
Aliases and imported module bindings participate in the ordinary unified source-scope naming rules.
Imported Declarations Use Qualified Access
After:
import "storage"
use:
storage.Open()
Imported module declarations are not injected into the current source scope as unqualified names.
This keeps module ownership visible in source and avoids ambiguity as projects grow.
No Implicit Name Injection
This is not valid solely because storage was imported:
Open()
Use the module qualifier:
storage.Open()
A local declaration named Open remains a separate local/module name.
Imports Are Not Re-Exports
Suppose module service imports database:
module service
import "database"
A third module that imports service does not automatically obtain a
database binding.
Ordinary imports are dependencies of the importing file/module, not public forwarding declarations.
Sec 0.1 does not define explicit re-export syntax.
No Selective or Wildcard Imports
Sec 0.1 does not define forms such as:
import { Open, Close } from "storage" // invalid
import * from "storage" // invalid
from "storage" import Open // invalid
Import the module and use its qualifier:
import "storage"
storage.Open()
storage.Close()
internal Modules
The path component internal is an import-access marker.
services/internal/transport/
The module inside still declares:
module transport
internal remains part of the canonical import path and module identity,
but it is not part of the module declaration or default source qualifier.
internal Access Scope
A module below an internal directory may be imported only by modules
below the directory immediately containing that internal directory.
For:
services/
internal/
transport/
modules within the services subtree may use that internal module.
Modules outside that enclosing subtree may not.
A root-level internal/ directory is private to the complete project.
This access check occurs before declaration visibility. A public declaration inside an inaccessible internal module does not make the module importable.
Modules Cannot Import Themselves
A module must not create an import edge to its own canonical module identity.
Same-module sibling files need no import at all.
If code in orders/create.sec needs a declaration from
orders/status.sec, it simply uses that declaration according to normal
same-module visibility.
Duplicate Imports
One source file must not import the same canonical module identity more than once.
Different aliases do not make duplicate imports distinct modules:
import first "customer/order"
import second "customer/order" // invalid duplicate module import
Different source files may independently import the same dependency because each file has its own import-binding scope.
Import Cycles Are Invalid
The Sec 0.1 module import graph is acyclic.
This is invalid:
orders
imports billing
billing
imports inventory
inventory
imports orders
The cycle is rejected regardless of:
- import aliases;
- source-file order;
- whether declarations happen to resolve under a partial compilation order.
Cycle detection uses canonical module identities, not local source aliases.
The Module Dependency Graph
Every source-file import contributes an edge to the containing module's dependency set.
Conceptually:
source-file imports
↓
module dependency set
↓
resolved ModuleGraph
Once self-imports, duplicate imports and cycles are rejected, the module dependency graph is a directed acyclic graph.
The compiler can then process module dependencies deterministically without making filesystem enumeration order semantic.
The Import Graph Is Not the Initialization Order
Imports describe semantic module dependencies.
They do not by themselves define:
- static initialization order;
- runtime startup order;
- startup failure behavior;
- deinitialization order.
Program initialization consumes module dependency facts but has its own lifecycle rules.
Public Module Surface
A module's external semantic surface consists of the public declarations and semantic information another module needs in order to use them.
This includes, where applicable:
- public types;
- public functions;
- public properties;
- public interfaces and conformance relationships;
- public generic signatures and constraints;
- other public declaration identities required for type checking.
Imported module bindings are not automatically part of the module's public surface.
The compiler may represent this surface internally in whatever form preserves the required semantics.
Public Signatures Cannot Expose Inaccessible Types
A public declaration used from another module must not require that importer to name or understand an inaccessible declaration.
For example, a public function cannot expose a module-internal type in its signature:
type _InternalState struct {
...
}
fn CurrentState() _InternalState { // invalid public surface
...
}
The same principle applies, where relevant, to:
- parameter types;
- return types;
- public fields;
- public properties;
- interface relationships;
- generic constraints and arguments.
Test Files and Module Participation
Production module instances exclude *_test.sec files.
During sec test, a test compilation may combine:
- the ordinary production files of the module;
- same-directory
*_test.secfiles.
Same-directory test files participate in the same test-view module and therefore have ordinary same-module access.
They still do not gain access to declarations that are source-file-private in another file.
Production code must never depend on test-only declarations.
Project-root tests/ modules are test-only external modules and use
ordinary imports and external visibility rules.
Entry Modules Are a Project Role
A project target may designate an entry module.
Entry status does not change:
- the module namespace;
- visibility rules;
- import semantics;
- module identity;
- ordinary declaration ownership.
Entry selection belongs to the project/build model rather than the source module declaration itself.
Compilation Plans Select Module Instances
One logical module may be analyzed under more than one concrete compilation plan when target-dependent source selection differs.
A Linux AMD64 build and a bare-metal Cortex-M build therefore do not become one polymorphic module graph.
Each concrete compilation plan receives the module instances applicable to that plan.
The logical identity of a target-independent module does not change merely because the module is compiled for another target.
Sec 0.1 does not define conditional-import source syntax.
Separate and Incremental Compilation
Modules are designed so importers can resolve public declarations without requiring every imported implementation body to be reanalyzed from scratch.
Conceptually, the compiler can derive a deterministic semantic module surface:
ModuleIdentity
PublicDeclarations
SemanticSignatures
RequiredTypeInformation
SurfaceFingerprint
An implementation-only edit that preserves the public semantic surface need not invalidate ordinary importer name/type resolution.
A public-signature change must invalidate dependent semantic state where necessary.
These are compiler/tooling properties; they do not add source syntax.
Editor and LSP Behavior
The LSP uses the same module resolver and active compilation-plan model as command-line compilation.
For an open source file, tooling can therefore reason about:
- its module identity;
- its declared module name;
- same-module sibling declarations;
- file-local import aliases;
- module dependency identities;
internalaccessibility;- the public surface of imported modules.
Editing one file updates that file's imports immediately and invalidates wider module state only as required by the changed dependency or public surface.
A qualifier rename normally changes the local import alias rather than renaming the imported module itself.
Common Invalid Forms
Dotted module declaration
module domain.orders
Relative filesystem import
import "../orders"
Absolute filesystem import
import "/srv/project/orders"
Implicit unqualified imported member
import "storage"
Open() // invalid solely because storage was imported
Duplicate canonical import
import first "customer/order"
import second "customer/order"
Import cycle
A imports B
B imports A
Wildcard import
import * from "storage"
Diagnostics
Important module diagnostics include:
- missing module declaration;
- module declaration inconsistent with the source directory;
- sibling files declaring different module names;
- unresolved import;
- malformed or non-canonical import path;
- illegal
internalimport; - duplicate import of the same canonical module identity;
- self-import;
- qualifier or alias collision;
- import cycle;
- duplicate module-scope declaration;
- public surface exposing an inaccessible declaration or type.
Directory/module mismatch
module declaration does not match source directory
file:
domain/orders/status.sec
declared:
module status
expected:
module orders
Qualifier collision
both imports bind the qualifier `order`
import "sales/order"
import "customer/order"
help: give one import an explicit local alias
Import cycle
module import cycle detected
orders -> billing -> inventory -> orders
Cycle diagnostics should point to the import sites forming a short deterministic representative cycle.
Best Practices
- Use one cohesive source directory per module.
- Keep the module declaration equal to the directory's module name.
- Split large modules into files freely; sibling files already share the module namespace.
- Declare imports only in files that actually use the external module.
- Use direct standard-library paths such as
"fmt"and"net/http". - Use import aliases when two dependencies have the same short module name.
- Keep imported names qualified rather than trying to simulate wildcard imports.
- Use
internaldirectories for subtree-level implementation modules that should not be imported externally. - Avoid cyclic module design; extract shared contracts into a lower dependency when necessary.
- Do not rely on filename or filesystem enumeration order for declaration visibility.
- Keep public signatures free of inaccessible implementation-only types.
- Remember that import dependencies and program initialization order are different concerns.
Summary
- One ordinary source directory forms one Sec module.
- All applicable
.secfiles directly in that directory contribute to one shared module namespace. - Every ordinary source file declares its module with
module Name. - The normal module name is the final source-directory component.
- A module declaration contains one identifier and is not a dotted filesystem path.
- The compiler tracks canonical module identity separately from the short module name.
- Different modules may legally share the same short module name.
- No import is required between files belonging to the same module.
- Duplicate-declaration checks operate over the complete module namespace.
- Source-file and filesystem enumeration order have no semantic meaning.
- Public names are externally visible subject to module access rules.
_nameis module-internal.__nameis source-file-private.- Module access and declaration visibility are separate checks.
- Imports use quoted canonical logical paths such as
import "net/http". - Import paths use
/on every host platform. - Import paths are case-sensitive and do not use
.,.., absolute host paths or host-specific separators. - Standard-library modules use direct logical paths without a
std/prefix. - Canonical standard-library paths cannot be silently shadowed by project-local modules.
platformis a compiler-reserved import root.- Failed reserved-root imports do not silently fall back to project-local resolution.
- Ordinary non-reserved imports resolve within the active project source root.
- The default imported qualifier comes from the imported module's
moduledeclaration. - The canonical import path does not become a dotted source qualifier.
- Import bindings are source-file-local.
- Sibling files do not inherit one another's imports.
- Import aliases create local qualifiers only and do not change module identity.
- One source file must have unique import qualifiers.
- The same canonical module identity cannot be imported twice by one source file.
- Imports do not inject declarations as unqualified names.
- Ordinary imports are not re-exports.
- Sec 0.1 has no explicit re-export syntax.
- Sec 0.1 has no selective-import or wildcard-import syntax.
- The path component
internalis an access-control marker, not part of the source module name. - An internal module is importable only from the subtree allowed by its enclosing
internalboundary. - A root-level
internaldirectory is private to the project. - A public declaration cannot override an inaccessible internal-module boundary.
- A module cannot import itself.
- The Sec 0.1 module import graph must be acyclic.
- Cycle detection uses canonical module identities rather than aliases.
- Module dependency edges are the union of imports from the module's participating source files.
- The module import graph is not automatically the program initialization or deinitialization graph.
- A public module surface contains the public semantic information required by importers.
- Public signatures cannot expose inaccessible declarations that external users need to name or understand.
- Production module instances exclude
*_test.sec. - A test compilation may combine production files with same-directory
*_test.secfiles. - Same-directory test files gain ordinary same-module visibility but not another file's source-private names.
- Production declarations must not depend on test-only declarations.
- Entry-module status is a project/build role and does not change module semantics.
- Each concrete compilation plan receives the module instances selected for that plan.
- Target-specific compilation does not automatically create a different logical module identity.
- Sec 0.1 has no conditional-import source syntax.
- Module surfaces support deterministic separate and incremental compilation.
- Implementation-only changes need not invalidate importers when the public semantic surface remains unchanged.
- The LSP uses the same module-resolution and compilation-plan model as command-line compilation.