Naming Conventions¶
Accepted
Accepted for V1 naming guidance, semantic naming requirements, and lint/style classification.
Catalyst naming should make source roles visible without adding ceremony. Names should follow ordinary readable code conventions before abbreviations or symbolic cleverness.
Semantic Requirements vs Lints¶
Most naming rules on this page are style or lint policy, not hard type-checking requirements. V1 hard semantic naming rules are:
- reserved words cannot be used as ordinary identifiers
_is the ignore binding pattern and does not introduce a bindingSelfis a special type-like comptime constant in type, contract, and implementation bodies- enum case identity is scoped by the enum owner
- error case identity is scoped by the error-set owner
Other casing and vocabulary rules on this page are lintable conventions unless a feature page explicitly says otherwise. Lints may be configured as warnings or errors by project policy, but they remain lint findings rather than core semantic failures.
General Rules¶
Catalyst source files use the .ct extension and should normally use lower_snake_case.ct filenames. Directory segments that participate in Catalyst source/module layout should also use lower_snake_case.
Use UpperCamelCase for type-like names:
const Frame: Type = [128]f32
const Sequence = ...
const AudioBuffer = struct {
}
Use lower_snake_case for functions, methods, variables, fields, parameters, comptime functions, and local constants:
fn process_block(samples: []f32) void
const frame_len = 128
var write_index: usize = 0
satisfies(.{ ... })
AudioBuffer.implements(Sequence(f32))
mutable_fields(.{ ... })
Use lower_snake_case for namespace and module path segments:
Root namespaces and module names follow the same rule as values, not type-like declarations. Do not use UpperCamelCase for module names.
Names beginning with is_ should be boolean-testable classifiers:
T.is_concrete()
T.is_sized()
slice.is_empty()
Use is_* only for operations whose primary result can be tested as a boolean. Most is_* operations return bool. Type reflection classifiers may return Type.Predicate when a true result grants sema a reusable fact, because Type.Predicate is still boolean-testable. Fact-bearing relation checks such as T.implements(...) or T.satisfies(...) should not use is_* names.
Compiler-provided singleton service values that are part of canonical language surface use UpperCamelCase:
Compiler.err(.{ .message = "message" })
Compiler.warn(.{ .message = "message" })
Compiler.note(.{ .message = "message" })
These are values, not module path segments. Their operations still use lowercase method names.
Type-Like Names¶
Type-like names are capitalized even when they are ordinary const declarations:
const Sample: Type = f32
const ReadError = error {
EndOfFile,
}
const Sequence = ...
This includes:
- concrete type aliases
- structural constraint aliases
- semantic contract factories or contract values
- error-set type aliases
- structs, enums, and future aggregate type declarations
Prelude Constructors and Comptime Functions¶
Comptime functions use lowercase names. This includes prelude-defined constructors such as satisfies(...) and mutable_fields(...):
satisfies(.{ ... })
Buffer.implements(Sequence(f32))
mutable_fields(.{ ... })
This keeps capitalized identifiers available for type-like values while making prelude-defined constructors read like ordinary function calls.
Enum Names¶
Enum type names are type-like:
const Ordering = enum {
less,
equal,
greater,
}
Enum cases use lower_snake_case.
Resource Cleanup Names¶
Use dispose for the Disposable contract operation:
const Disposable = contract {
fn dispose(self: *Self) void
}
dispose means “end this value's owned payload/resource lifetime.” It does not imply freeing the storage backing self.
Use destroy for APIs that dispose a heap-allocated object and free the storage backing self. destroy is convention/lint vocabulary, not a language cleanup hook.
Contract Names¶
Semantic contract names are type-like and capitalized:
Copyable
Cloneable
Disposable
Indexable(T)
MutableIndexable(T)
Sequence(T)
MutableSequence(T)
Iterator(Item)
Iterable(Item)
MutableIterable(Item)
PartialEq(Other)
Eq(T)
PartialOrd(Other)
Ord(Other)
Capability-style contracts should use adjective names when the contract primarily says what values of a type can do or participate in, such as Copyable, Cloneable, Indexable, and Iterable. The operation names inside those contracts stay ordinary verbs or verb phrases, such as clone, at, iter, or dispose.
Use established domain terms when an adjective would be awkward or less precise. Lifecycle and relation contracts may use conventional names such as Disposable, PartialEq, Eq, PartialOrd, and Ord.
PartialEq(Other) and Eq(T) are the accepted names for the V1 equality contract pair. PartialEq backs equality operators and may be non-reflexive; Eq is the stricter same-type equivalence-relation marker.
PartialOrd(Other) and Ord(Other) are the accepted names for the V1 comparison contract pair. PartialOrd backs comparison operators and may represent unordered operands; Ord is the stricter total-order contract.
Contract implementation queries and reflection functions are functions and therefore lowercase:
Buffer.implements(Sequence(f32))
Error Names¶
Error-set type names are type-like:
const ParseError = error {
UnexpectedToken,
}
Error cases use UpperCamelCase.
An error case may have the same name as its owning error set or as an imported type-like name such as Error, but those forms should lint because they are easy to misread:
const Timeout = error {
Timeout,
}
Acronyms¶
Prefer readable mixed-case acronyms in type-like names and lowercase acronyms in function/value names:
const HttpClient = struct {
}
fn parse_http_header(...) {
}
All-caps acronyms should be reserved for external names or established constants where that spelling is the domain convention.