Heuristic:Duckdb Duckdb Memory Management Rules
| Knowledge Sources | |
|---|---|
| Domains | Code_Quality, C_Plus_Plus |
| Last Updated | 2026-02-07 12:00 GMT |
Overview
DuckDB prohibits raw `malloc`/`new`/`delete`; use smart pointers exclusively, strongly preferring `unique_ptr` over `shared_ptr`, and `idx_t` over `size_t`.
Description
DuckDB enforces strict memory management rules to prevent leaks and dangling pointers. Raw memory allocation (`malloc`, `new`, `delete`) is considered a code smell and should never be used in core code. The project strongly prefers `unique_ptr` for ownership semantics, using `shared_ptr` only when absolutely necessary (e.g., shared catalog objects). Additionally, the project uses the `idx_t` type alias instead of `size_t` for all offset/index/count values, and the `D_ASSERT` macro instead of standard `assert`.
Usage
Apply these rules when writing any C++ code for the DuckDB core (`src/` directory). These conventions are enforced during code review and violations will block PR merging.
The Insight (Rule of Thumb)
- Rule 1: Never use `malloc`, `new`, or `delete`. Use smart pointers instead.
- Use `make_uniq<T>(...)` (DuckDB's wrapper) or `std::make_unique<T>(...)`.
- Rule 2: Strongly prefer `unique_ptr` over `shared_ptr`. Only use `shared_ptr` when ownership is truly shared.
- Rule 3: Use `idx_t` instead of `size_t` for all offsets, indices, and counts.
- Rule 4: Use `[u]int(8|16|32|64)_t` instead of `int`, `long`, `uint` etc.
- Rule 5: Use `const` references for non-trivial arguments (e.g., `std::vector`, `std::string`).
- Rule 6: Use `D_ASSERT` instead of `assert`. Asserts should never be triggered by user input.
- Rule 7: Use exceptions only for query-terminating errors. Use return values for expected/recoverable errors.
- Trade-off: Slightly more verbose code in exchange for memory safety and clear ownership semantics.
Reasoning
Smart pointers provide automatic memory management with clear ownership semantics. `unique_ptr` makes ownership transfer explicit (via `std::move`) and has zero runtime overhead compared to raw pointers. `shared_ptr` has atomic reference counting overhead and makes ownership ambiguous, which is why it's discouraged.
The `idx_t` convention avoids sign-comparison warnings and makes the intent of a variable clearer (it's a count/index, not an arbitrary size).
Code evidence from `CONTRIBUTING.md:80-81`:
* Do not use `malloc`, prefer the use of smart pointers. Keywords `new` and
`delete` are a code smell.
* Strongly prefer the use of `unique_ptr` over `shared_ptr`, only use
`shared_ptr` if you **absolutely** have to.
Type conventions from `CONTRIBUTING.md:86`:
* Use `[u]int(8|16|32|64)_t` instead of `int`, `long`, `uint` etc. Use `idx_t`
instead of `size_t` for offsets/indices/counts of any kind.
Error handling from `CONTRIBUTING.md:115-118`:
* Use exceptions **only** when an error is encountered that terminates a query.
* Use `D_ASSERT` to assert. Use **assert** only when failing the assert means
a programmer error. Assert should never be triggered by user input.