Invisible link to canonical for Microformats

ADR-035 moduleName() in slug.std


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.path would 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.module introspection 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.path for paths, slug.std for 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.module module 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.