Incan Code Style Guide¶
This page is the canonical style guide for human-written .incn source files.
It describes how Incan code is laid out, what spacing is expected, and which readability gaps are allowed.
Use incan fmt to apply these formatting rules to source files.
For formatter command usage and limits, see Formatting with incan fmt.
Historical note
RFC 053 is the design record behind the current vertical-spacing model.
Scope¶
This guide is about source layout and readability:
- indentation
- blank lines
- comments and docstrings
- horizontal spacing
- quoting and commas
- naming conventions
- line breaking for common constructs
Principles¶
Incan formatting optimizes for a few stable outcomes:
- code reads as intentional prose, not as minimally legal syntax
- the same construct looks the same across projects
- authors may use a small amount of vertical whitespace for readability
- formatting rules stay simple enough that humans can follow them without memorizing formatter internals
The Zen of Incan
┌──────────────────────────────────────────────────────────────────────┐
│ The Zen of Incan │
│ by Danny Meijer (inspired by Tim Peters' "The Zen of Python") │
└──────────────────────────────────────────────────────────────────────┘
› Readability counts ─ clarity over cleverness
› Safety over silence ─ errors surface as Result, not hide
› Explicit over implicit ─ magic is opt-in and marked
› Fast is better than slow ─ performance costs must be visible
› Namespaces are great ─ keep modules and traits explicit
› One obvious way ─ conventions beat novelty,
with escape hatches documented
This guide turns the Zen's style philosophy into concrete formatting and naming rules.
You can print it with incan run -c "import this".
Required Baseline¶
Indentation¶
- Use
4spaces per indentation level. - Do not use tabs.
- Indentation is semantic in Incan, so accidental indentation drift is a real syntax problem, not just a style issue.
def calculate(x: int) -> int:
if x > 0:
return x * 2
return 0
Line length¶
- Treat
120characters as the target line length. - This is a best-effort target, not an absolute hard cap.
- When a constructor call or ordinary call overflows, rewrite it vertically with one argument per line.
- Some signatures, strings, and complex nested expressions may still require manual judgment.
result = build_report(
source_uri,
output_uri,
include_lineage=true,
include_statistics=true,
)
Naming¶
Use naming that matches the kind of thing being declared:
- functions, methods, local variables, parameters, imported module aliases, and
staticbindings uselower_snake_case constbindings useSCREAMING_SNAKE_CASE- classes, models, traits, enums, type aliases, newtypes, rusttypes, and enum variants use
UpperCamelCase - module file names use
lower_snake_case
const DEFAULT_PORT: int = 8080
static active_clients: int = 0
type UserId = str
model UserProfile:
display_name: str
def is_internal_user(self) -> bool:
return self.display_name.ends_with("_internal")
enum JobStatus:
Pending
Running
Finished
Vertical Layout¶
Vertical spacing is the most opinionated part of the current contract. The rule is deliberately simple:
2blank lines are reserved for specific top-level declaration boundaries1blank line is allowed for readability inside ordinary code- more than
2blank lines are never allowed
Top-level declarations¶
Use exactly 2 blank lines around top-level body-bearing type-like and callable declarations:
defclassmodeltraitenumtypenewtyperusttype
Top-level aliases are declaration syntax, but they are not body-bearing declarations. Keep them grouped tightly with nearby imports, constants, statics, or related declarations unless they border one of the body-bearing declarations above.
def parse_user(raw: str) -> User:
...
def store_user(user: User) -> None:
...
This double-spacing is a root-level rule. It is not a general license to scatter double blank lines throughout the file.
Type bodies¶
Inside model, class, trait, enum, and similar type bodies:
- keep adjacent fields, variants, and abstract signatures tight
- insert exactly
1blank line before a following body-bearing member
model User:
id: UserId
email: str
def is_internal(self) -> bool:
return self.email.ends_with("@example.com")
Ordinary statement blocks¶
Inside function bodies, loops, if blocks, match arms, and other indented suites:
1authored blank line is allowed and is preserved2or more consecutive blank lines are not allowed
This is where "code prose" matters. A single readability gap between logic groups is valid Incan style and survives formatting.
def register_source(session: Session, source: Source) -> Result[None, SessionError]:
validate_source(source)?
logical_name = source.logical_name()
physical_uri = source.uri
return session.register(logical_name, source)
Imports, constants, and statics¶
Keep import runs and grouped const / static declarations tight unless they border a top-level declaration that requires the two-blank-line rule.
from std.io import File
from std.path import Path
const DEFAULT_PORT: int = 8080
static active_clients: int = 0
def main() -> None:
...
Comments And Docstrings¶
Stand-alone comments¶
Stand-alone comments are attached by scope and structure. They do not create extra blank-line entitlement on their own.
- a comment with no blank line before the next construct is treated as a leading comment for that construct
- a comment separated from the next construct by a blank line stays with the previous construct instead
type UserId = str
# Validate before any network call.
def load_user(id: UserId) -> User:
...
Docstrings¶
Docstrings are part of the readable source contract, but their interior spacing is normalized.
- single-line docstrings stay on one line when they fit
- multi-line docstrings use opening and closing quotes on their own lines
- repeated empty-line runs inside the docstring payload collapse to at most
1blank line
"""
Load the project manifest.
This docstring may contain one intentional blank line.
"""
Literal text such as \n inside strings is still just text.
Normalization applies to actual blank lines, not slash characters.
Horizontal Spacing¶
Use ordinary spaces to make expressions readable.
- put spaces around binary operators:
a + b - put a space after commas:
foo(a, b) - put a space after the colon in type annotations:
x: int - do not put a space between a callable name and
( - do not put spaces around
=in named arguments
value = left + right
user = User(name="Alice", age=30)
Avoid these forms:
value=left+right
user = User (name = "Alice", age = 30)
Strings, Quotes, And Commas¶
- Prefer double quotes for strings.
- Existing single quotes may be preserved, but double quotes are the house style.
- Use trailing commas in multi-line constructs.
payload = {
"kind": "event",
"source": "cli",
}
Match Arms And Short Forms¶
Short single-statement match arms are allowed on one line when they stay readable.
match node.kind:
PrismNodeKind.ReadNamedTable => return str("ReadNamedTable")
PrismNodeKind.Filter => return str("Filter")
When an arm needs more space or more than one statement, use a block body.
match result:
Ok(value) =>
log_success(value)
return value
Err(err) =>
return report_error(err)
Do not insert extra blank lines immediately after => or a suite header unless you genuinely intend one readability gap inside that block.
What The Formatter Should Preserve¶
incan fmt preserves legitimate authored structure rather than flattening everything into one style-less block.
Today that specifically includes:
- one blank line between logic groups inside indented code
- one blank line between sibling statements after nested suites
- short inline
matcharms when they are still readable - actual string contents, including literal
\ntext
What The Formatter Should Normalize¶
incan fmt normalizes mechanical drift:
- tabs or inconsistent indentation
- repeated blank-line runs beyond the allowed buckets
- trailing blank lines at end-of-file
- inconsistent wrapping of overflowing calls and constructors
- comment placement that would otherwise detach comments from the same-scope construct they describe
Formatted files must end with exactly one trailing newline.
Tooling¶
Use Formatting with incan fmt for command-line usage, CI integration, and formatter limitations.