Files
greptimedb/docs/style-guide.md
T
2026-09-17 13:14:56 +00:00

137 lines
5.7 KiB
Markdown

# GreptimeDB Style Guide
This style guide is intended to help contributors to GreptimeDB write code that is consistent with the rest of the codebase. It is a living document and will be updated as the codebase evolves.
It's mainly an complement to the [Rust Style Guide](https://pingcap.github.io/style-guide/rust/).
## Table of Contents
- Formatting
- Naming
- Imports
- Modules
- Structs and functions
- Comments
- Cargo.toml
- Unsafe
## Formatting
- Place all `mod` declaration before any `use`.
- Use `unimplemented!()` instead of `todo!()` for things that aren't likely to be implemented.
- Add an empty line before and after declaration blocks.
- Place comment before attributes (`#[]`) and derive (`#[derive]`).
## Naming
- Before adding a struct, function, or module, inspect the closest implementation
in the same crate. Reuse its domain terminology, suffixes, placement, and
construction pattern rather than inventing a new convention.
- Treat uppercase acronyms as one word in project-defined names: `Grpc`,
`Http`, `Otlp`, `Sst`, `Wal`, and `Sql`, not `GRPC`, `HTTP`, or one letter per
word. Keep externally defined names and product branding unchanged.
- Use `-` for Cargo package names and standalone crate directories (for example,
`src/meta-client`). For nested crates, follow the nearby workspace layout
(for example, `src/common/meta`), and use `_` for crate-internal module and
directory names.
- Name a type after its responsibility. Established suffixes include `Config`
or `Options` for configuration, `Builder` for staged construction, `Context`
for an explicit operation context, `Request`/`Response` for protocol payloads,
and `Ref` for shared-reference aliases. Use `Impl` only for a concrete
implementation of an existing trait.
- Use verbs for operations: `create_*`, `build_*`, `parse_*`, `list_*`,
`is_*`, and `has_*`. Reserve `*_inner` for an implementation behind a
corresponding higher-level API.
## Imports
- Prefer crate-rooted imports (`use crate::...`) over `self::` or `super::` in
production code. Tests may use `use super::*`; re-exports may use relative
paths such as `pub use self::foo::bar`.
- Prefer importing a function or constant's module and qualifying the use at
the call site. Do not import enum variants except for narrowly established
cases such as `Some` and `Err`.
- Avoid wildcard imports except for a prelude or `use super::*` in tests.
## Modules
- Use the file with same name instead of `mod.rs` to define a module. E.g.:
```
.
├── cache
│ ├── cache_size.rs
│ └── write_cache.rs
└── cache.rs
```
- Keep items private by default. Do not introduce `pub(super)` or `pub(in ...)`.
When an item must be used outside its defining module, use `pub(crate)` or
restructure the module so it can remain private.
- Name test modules `tests`. Put public structs, enums, and functions before
private items, and order related items top-down from their public entry point
to implementation details.
## Structs and functions
- Give each struct one cohesive responsibility. Split independent state,
I/O, transformation, and orchestration into focused types and compose them.
Do not create catch-all `Helper`, `Utils`, `Manager`, or `Context` types to
avoid defining ownership boundaries.
- Follow the closest same-crate constructor pattern. Use `new` for a small set
of required inputs. For optional, chainable configuration, use the established
fluent API in that module (`with_*` or field-named builder methods). Use a
`Builder` when the construction has many optional inputs or requires staged
validation. Use `Default` when the type has a meaningful default state.
- Keep a function focused on one operation. Move reusable transformations or
stateful sub-operations into their own type instead of coupling them to an
unrelated orchestrator.
- Before adding a feature, search the workspace for an existing module, type,
or utility that already owns the same responsibility. Reuse it when it fits;
if a small cohesive refactor makes it fit, prefer that refactor over a
parallel implementation. Do not duplicate a shared abstraction merely to
avoid touching its current boundary.
## Comments
- Add comments for public functions and structs.
- Prefer document comment (`///`) over normal comment (`//`) for structs, fields, functions etc.
- Add link (`[]`) to struct, method, or any other reference. And make sure that link works.
- Write descriptive comments, not imperative comments: `Opens the file`, not
`Open the file`.
- Write pending work as `TODO(name): explanation` (or `TODO(name)` when no
explanation is needed). Write bug workarounds as `FIXME(#1234): explanation`.
## Cargo.toml
- Keep dependency entries alphabetically ordered. Add shared dependencies to
the root `[workspace.dependencies]` and reuse them from member crates.
- Do not weaken an existing exact version or compatibility pin merely to follow
a general version-format preference.
## Error handling
- Define a custom error type for the module if needed.
- Use `context()` for cheap context selectors. Its argument is evaluated even when the
operation succeeds.
- Use `with_context()` when constructing the context requires work such as `format!`,
allocation, or cloning, so that work only happens on the error path. For example:
```rust
value.with_context(|| InvalidValueSnafu {
reason: format!("invalid value: {value}"),
})?;
```
- Use `error!()` or `warn!()` macros in the `common_telemetry` crate to log errors. E.g.:
```rust
error!(e; "Failed to do something");
```
## Unsafe
- Avoid manual `unsafe impl Send` or `unsafe impl Sync`. Prefer expressing the
required bounds in trait declarations or redesigning ownership so Rust can
derive the auto traits safely.