Status
Accepted
Context
Slug is introducing symbols as a first-class value to represent names in code:
- map keys for structured internal data
- struct fields
- tags for pattern matching and metadata
Slug already supports slice syntax using : inside index expressions (e.g. list[i:j], list[i:]). Introducing symbols must not introduce grammar ambiguity or hidden behavior.
Earlier exploration considered #symbol literals, but prior art (notably Julia) shows that a clean and conventional design is possible using :symbol, provided slice grammar is made explicit.
Slug also distinguishes between:
- internal structured data (best represented with symbol keys)
- external/dynamic data (JSON, HTTP headers, env vars; string keys)
This ADR formalizes symbols, map key semantics, slice grammar, and the associated stdlib APIs.
Decision
1. Symbols use : prefix syntax
Slug symbol literals use a leading colon:
:foo
:User
:darkMode
- The portion following
:must be a valid Slug identifier - Symbols are interned by the runtime
- Equality and hashing are identity-based (interned id)
2. Slice grammar requires an explicit start expression
To avoid ambiguity between slice syntax and symbol literals, Slug adopts the Julia rule:
Slice expressions must always have an explicit start.
Allowed slice forms
list[0:n]
list[i:j]
list[i:]
Disallowed
list[:n]
This removes the only ambiguous case and aligns with Slug’s preference for explicitness over implicit defaults.
3. Map literal keys default to symbols
In map literals:
{ foo: 1, bar: 2 }
- Bare identifiers are parsed as symbol keys
- This is equivalent to
{ :foo: 1, :bar: 2 }
Explicit alternatives:
{ :foo: 1 } // explicit symbol key
{ "foo": 1 } // string key
4. Dot access uses symbol lookup only
Dot-walk is defined as symbol-based access:
m.foo // desugars to m[:foo]
- Dot access never looks up string keys
- Bracket access (
m[k]) always uses the exact key value provided
This keeps dot-walk predictable and aligned with future struct field access.
5. Maps support any hashable key type
Slug maps are general-purpose dictionaries:
- Any hashable value may be used as a key (string, symbol, number, bool, bytes, etc.)
- Map literal sugar only affects parsing, not map capabilities
6. Canonical symbol printing and textual form
Symbols have a canonical textual representation:
-
If the symbol text is a valid identifier:
:foo -
Otherwise (symbols created dynamically):
:"foo bar"
This form is used for printing, inspection, and debugging.
7. Dynamic symbol creation via sym()
A standard-library function is provided:
sym(string) -> symbol
- Interns the provided string as a symbol
- Accepts any string (no identifier restriction)
- If passed a symbol, returns it unchanged
This supports dynamic use cases and boundary normalization without complicating the language grammar.
8. Extracting symbol text via label()
A standard-library function is provided:
label(symbol) -> string
- Returns the raw symbol text (without
:or quotes) - Intended for reflection, debugging, and interop
9. Placement of sym and label
:symbolliterals are core language syntaxsym()andlabel()are provided by the standard library as foreign functions, not VM builtins
Rationale:
- keeps the core language small
- allows evolution without VM changes
- symbol literals and implicit symbol creation do not depend on stdlib presence
10. JSON decoding and encoding
JSON object keys are strings by definition.
- Decoding JSON produces maps with string keys
- Encoding JSON requires maps with string keys
- No implicit conversion between string keys and symbol keys occurs
11. Style guidance (non-normative)
- Use symbol keys for program-internal structured data
-
Use string keys for external/dynamic data:
- JSON
- HTTP headers
- environment variables
- Normalize at boundaries, once, explicitly
Consequences
Positive
- Removes all grammar ambiguity between symbols and slices
- Aligns Slug with well-established language designs (Julia-style)
- Preserves expressive slice syntax
- Makes internal data more explicit, readable, and intention-revealing
- Keeps the VM and core language minimal
- Provides clear, explicit data-boundary semantics
Negative
list[:n]must be rewritten aslist[0:n]- Requires users to learn the distinction between symbol and string keys
Neutral / Future Work
- Escaped symbol literal syntax beyond
:"..."may be expanded later - Tooling (REPL, formatter, linter) should visually distinguish symbol vs string keys
- Debug-mode diagnostics may suggest near-miss key types (
:foovs"foo")
Summary
This ADR establishes symbols as a foundational concept in Slug, clarifies map semantics, resolves slice ambiguity cleanly, and defines explicit boundaries between internal and external data — all while keeping the core language small, predictable, and aligned with Slug’s design philosophy.