Status
Accepted
Context
Slug modules often need a stable, human-readable source identifier for diagnostics and logging.
A common pattern is creating a module-local logger:
val log = import("slug.log").logger("playground")
Hardcoding the module name is repetitive and fragile:
- copy-pasting a module requires manual edits
- renames can leave stale logger source strings behind
- the logger source is already logically available from the current module context
We considered whether this capability should live in slug.path, since related functions such as cwd(), projectRoot(), and moduleDir() are path- or location-adjacent.
However, moduleName() is not a filesystem path operation. It returns the logical module identity used by the import system (for example "slug.io.http"), not a directory or file path.
We also considered whether slug.log.logger() should default its source automatically, but that would evaluate in the callee module (slug.log) rather than the caller module, which is incorrect for this use case.
Slug needs a small, explicit way for a module to obtain its own logical name at the call site.
Decision
Introduce moduleName() in slug.std.
1. Signature
moduleName() -> @str
It returns the fully-qualified logical name of the current module.
Example:
moduleName() == "slug.io.http"
2. Semantics
moduleName() evaluates in the module where it is called.
This means:
val log = import("slug.log").logger(import("slug.std").moduleName())
uses the caller’s module name, not the module name of slug.log.
3. Returned value
The returned value is the module’s fully-qualified import name, not:
- the leaf file name
- the current directory
- the library root
- a filesystem path
Examples:
import("slug.std").moduleName() // called inside slug.db.query => "slug.db.query"
import("slug.std").moduleName() // called inside app.main => "app.main"
4. Placement
moduleName() lives in slug.std, not slug.path.
Rationale:
- it is a core runtime-context helper
- it returns logical module identity, not a path
- placing it in
slug.pathwould blur the boundary between filesystem operations and module introspection
5. Primary use case
The intended ergonomic pattern is module-local logger construction:
val log = import("slug.log").logger(import("slug.std").moduleName())
This is the canonical copy-paste-safe form:
- explicit
- portable
- free of hardcoded module strings
- correct at the caller boundary
6. Non-goals
This ADR does not introduce:
- implicit caller inspection in libraries
- automatic logger source injection
- a broader
slug.moduleintrospection API - filesystem metadata such as module file path or library root
Those may be explored later if needed.
Consequences
Positive
- Eliminates fragile hardcoded module name strings in logging and diagnostics.
- Produces a clean, portable copy-paste pattern for per-module logger setup.
- Keeps caller identity explicit and predictable.
- Avoids stack inspection, hidden context injection, and other runtime magic.
- Preserves a clean conceptual boundary:
slug.pathfor paths,slug.stdfor core runtime helpers.
Negative
- Logger setup remains slightly verbose unless callers destructure imports locally.
- Introduces another stdlib helper that is specific to module/runtime context rather than general data manipulation.
- Does not solve broader module introspection needs beyond the current module name.
Neutral
- This decision leaves room for a future
slug.modulemodule if richer module metadata is later required. moduleName()is a logical identity primitive and does not imply anything about on-disk file layout.- Existing logging APIs remain unchanged; this ADR only improves how callers provide the source string.