Source files and modules
A dusk source file uses the .dusk extension. It has two kinds of top-of-file syntax. Directives start with @ and configure the file. Declarations define types, functions, and values, and can carry modifier keywords like export and mut.
Comments
Section titled “Comments”Dusk has two comment forms, and a third that is an ordinary comment the tools read as documentation.
A // line comment runs from the slashes to the end of the line. It is the original comment form, in the language from the first release.
A /* */ block comment spans from /* to the matching */, and it nests: every /* inside an open comment opens another level, so you can comment out a region that already contains block comments and the outer */ still closes the right one. Block comments arrived in 1.6.0. Inside one only /* and */ are significant, so a quote or a // sits inert until the comment closes, and symmetrically a /* inside a string, char, or rune literal is plain text. An unterminated block comment is a lexical error reported at its opening /*.
A block comment carries the same statement-breaking weight a newline does when it contains one, and none when it does not. A comment that opens and closes on a single line never glues the statements on either side of it together, and a comment that spans a newline ends the statement exactly the way the newline it swallowed would have, so wrapping a comment around code never changes what the code around it means. Both directions are pinned by the compiler’s own goldens, since this is the one edge where a comment could quietly change a program.
Both forms evaporate at the lexer and produce no token, so a program compiles to byte-identical IR with its comments present or stripped. The directive prescan, the leading sweep that collects @paradigm, @import, @link, and @csource before the file is lexed, tracks block-comment nesting too, so a directive commented out with /* */ is dead in every consumer and a stray /* in a directive argument’s quoted string cannot swallow the lines below it.
Doc comments
Section titled “Doc comments”A block comment that opens with /**, where the byte after the two stars is neither * nor /, is a doc comment. It binds to the declaration whose first token comes next, modifiers like export included, and blank lines and plain comments in between do not break the binding. /**/ is an empty plain comment and a /*** ... */ banner stays plain, so only the /** opener with real content ahead of it starts a doc block. In every other respect a doc comment is an ordinary block comment: it nests, terminates, and evaporates by the same rules, so check, build, and ir output is byte-identical whether the docs are present or stripped. A doc comment before the directive prologue documents the module; in a file with no directives a leading doc binds to the first item.
The prose is the only thing a doc comment carries. Three tags structure it, each recognized at the start of a line after the * gutter is stripped: @param name description documents one parameter, @return description documents the result, and @example opens a verbatim block. The first paragraph is the summary and the paragraphs after it are the body. Every fact about the declaration, its name, its types, its whole signature, comes from the declaration itself rather than the comment, so the documentation cannot drift from the code, and a claim that does contradict the signature, an @param naming no such parameter or a @return on a void function, is an error rather than a lie the tool would repeat.
Doc comments and their tags arrived in 1.6.1. Bindable targets are functions, structs, enums, interfaces, interface method signatures, impl methods, and the module itself. The dusk doc command renders a module’s doc comments as markdown, or as a stable JSON model with --json; see the CLI for both. A doc comment that binds to nothing is an error the doc command reports with a caret on the block.
Directives
Section titled “Directives”Directives appear at the top of the file, before declarations. The compiler scans the leading lines of a file and stops at the first line that is neither blank, a line comment, nor a directive; blank lines and // comments may sit between directives. An unknown @ directive is a compile error, as is an unknown paradigm name or an empty import path.
@paradigm functional@paradigm procedural
@import std.io@import std.functional.maybeThere are four directives.
@paradigm <name>declares a paradigm the file uses. It can be repeated to stack paradigms. A file with no@paradigmdirective defaults to procedural. See Paradigm system.@import <path>brings a module or a symbol into the file.@link <value>names a library or object for the linker. A value with a path separator or ending in.a,.o, or.sois passed toclangverbatim as a file argument; anything else becomes a-lflag, so@link "m"links-lm. Added in 1.4.0.@csource "<path>"compiles a C file in beside the runtime, resolved against the declaring file’s own directory. Added in 1.4.0.
The last two feed the link line a build’s clang invocation runs, alongside a foreign block’s bound symbols. They are collected by the prescan the same way @import is. See foreign functions for the boundary they serve and full detail.
Imports are independent of paradigm directives. Importing a module does not grant any paradigm, and a paradigm directive does not change what can be imported. The two systems do not interact.
Imports
Section titled “Imports”Imports are based on directories and files. A dotted path walks the project tree.
/ myLib/ myFile.dusk main.duskIn main.dusk:
@import myLib.myFile.someFunc // import a leaf symbolA dotted path resolves to one of two things.
- A module: a file, reached through its directory path. You then call through the qualified name.
- A leaf symbol: a function, type, or value inside a file. The leaf name is then in scope unqualified.
@import std.io // module: call std.io.print_line(...)@import std.io.print_line // symbol: call print_line(...)A bare directory is not a module: @import std does not resolve, because there is no std.dusk file. The path must reach a file (std.io) or a symbol inside one (std.io.print_line).
Resolution walks directories, then files, then symbols, so the compiler can tell where the file path ends and the symbol name begins. A dotted path is looked up against the importing file’s directory first, then the standard library root. For each root the compiler first tries the whole path as a module file; if that fails, it tries the path minus its last segment as the module file and treats the trailing segment as a leaf symbol, which the module must export.
Stdlib and local imports
Section titled “Stdlib and local imports”Standard library modules live under std and local modules resolve relative to the importing file. Both use the same dotted form. The following program imports std.io as a module and str_len as a leaf symbol, and it compiles today:
@import std.io@import std.string.str_len
func main() -> int32 { std.io.print_line("hello") std.io.print_int(str_len("hello")) return 0}A qualified call like std.io.print_line(...) reaches an imported module’s function through its module path. A leaf import like str_len puts the bare name in scope. See Standard library overview for the module list.
Git imports
Section titled “Git imports”An external package uses its git path in quotes.
@import "github.com/user/repo/module"The first three segments, host/user/repo, name the repository. The rest names a file inside it. The import string carries no version yet. The dawn tool fetches the repository into a local cache ($DAWN_CACHE, or ~/.dawn/cache when unset), and the compiler resolves the module from there. github.com/user/repo/module resolves to <cache>/github.com/user/repo/module.dusk, or falls back to repo.dusk with module as a leaf symbol, the same way a dotted path resolves. This fragment is valid syntax but is not shown as a compiled program here because it requires a populated cache. There is currently no version pinning, lock file, or integrity check; those are planned. See Dawn for the commands and cache layout.
Exports
Section titled “Exports”By default every declaration is private to its file. The export keyword makes a declaration visible to other files.
export struct Point { x: float64, y: float64 }
export func area(s: Shape) -> float64 { ... }Only exported names can be imported elsewhere. There is no paradigm restriction on exports: an exported function or type is usable from any file regardless of either file’s paradigm directives. This keeps the cross-file story simple and matches the rule that user-defined names are paradigm agnostic: a file without @paradigm functional cannot call map directly, but it can call an imported function that uses map internally.
Privacy
Section titled “Privacy”A private name never crosses a file boundary: neither as a qualified call, nor as a bare call, nor as a leaf import. Two imported modules may each keep a private helper of the same name without colliding; before merging modules, the loader isolates each file’s private top-level names so they cannot be reached or clash across files.
The following two files compile together. square is private to geometry.dusk; Point and dist2 are exported and imported as leaf symbols.
// square is private: it never leaves this file.func square(x: float64) -> float64 { return x * x}
export struct Point { x: float64, y: float64,}
export func dist2(a: Point, b: Point) -> float64 { return square(b.x - a.x) + square(b.y - a.y)}@import geometry.Point@import geometry.dist2
func main() -> int32 { a := Point { x: 0.0, y: 0.0 } b := Point { x: 3.0, y: 4.0 } println(dist2(a, b)) return 0}Every route to a private name is rejected. Each fragment below fails to compile with the diagnostic shown.
A bare call cannot reach an imported module’s private name:
@import geometry
func main() -> int32 { println(square(2.0)) // error: undefined name 'square' return 0}A qualified call to a private name is rejected rather than silently folded:
@import geometry
func main() -> int32 { println(geometry.square(2.0)) // error: 'square' is private to module 'geometry' return 0}A leaf import of a private name is rejected at the import itself:
@import geometry.square// error: import 'geometry.square' names 'square',// but 'geometry' exports no such symbolThe println used above is a language builtin, always in scope without any import; it is distinct from the functions exported by std.io. See Builtins.