Skip to content

Commit 721762e

Browse files
committed
Improve developer guide code style conventions for Rust
1 parent bcbbcc0 commit 721762e

File tree

1 file changed

+170
-30
lines changed

1 file changed

+170
-30
lines changed

docs/developer_guide/rust.md

Lines changed: 170 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ All Rust files must include the standardized copyright header:
3131

3232
### Code Formatting
3333

34-
Import formatting is automatically handled by rustfmt when running `make format`. The tool organizes imports into groups (standard library, external crates, local imports) and sorts them alphabetically within each group.
34+
Import formatting is automatically handled by rustfmt when running `make format`.
35+
The tool organizes imports into groups (standard library, external crates, local imports) and sorts them alphabetically within each group.
3536

3637
### Error Handling
3738

@@ -59,11 +60,128 @@ Use structured error handling patterns consistently:
5960

6061
3. **Error Propagation**: Use the `?` operator for clean error propagation.
6162

63+
### Attribute Patterns
64+
65+
Consistent attribute usage and ordering:
66+
67+
```rust
68+
#[repr(C)]
69+
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
70+
#[cfg_attr(
71+
feature = "python",
72+
pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.model")
73+
)]
74+
pub struct Symbol(Ustr);
75+
```
76+
77+
For enums with extensive derive attributes:
78+
79+
```rust
80+
#[repr(C)]
81+
#[derive(
82+
Copy,
83+
Clone,
84+
Debug,
85+
Display,
86+
Hash,
87+
PartialEq,
88+
Eq,
89+
PartialOrd,
90+
Ord,
91+
AsRefStr,
92+
FromRepr,
93+
EnumIter,
94+
EnumString,
95+
)]
96+
#[strum(ascii_case_insensitive)]
97+
#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
98+
#[cfg_attr(
99+
feature = "python",
100+
pyo3::pyclass(eq, eq_int, module = "nautilus_trader.core.nautilus_pyo3.model.enums")
101+
)]
102+
pub enum AccountType {
103+
/// An account with unleveraged cash assets only.
104+
Cash = 1,
105+
/// An account which facilitates trading on margin, using account assets as collateral.
106+
Margin = 2,
107+
}
108+
```
109+
110+
### Constructor Patterns
111+
112+
Use the `new()` vs `new_checked()` convention consistently:
113+
114+
```rust
115+
/// Creates a new [`Symbol`] instance with correctness checking.
116+
///
117+
/// # Errors
118+
///
119+
/// Returns an error if `value` is not a valid string.
120+
///
121+
/// # Notes
122+
///
123+
/// PyO3 requires a `Result` type for proper error handling and stacktrace printing in Python.
124+
pub fn new_checked<T: AsRef<str>>(value: T) -> anyhow::Result<Self> {
125+
// Implementation
126+
}
127+
128+
/// Creates a new [`Symbol`] instance.
129+
///
130+
/// # Panics
131+
///
132+
/// Panics if `value` is not a valid string.
133+
pub fn new<T: AsRef<str>>(value: T) -> Self {
134+
Self::new_checked(value).expect(FAILED)
135+
}
136+
```
137+
138+
Always use the `FAILED` constant for `.expect()` messages related to correctness checks:
139+
140+
```rust
141+
use nautilus_core::correctness::FAILED;
142+
```
143+
144+
### Constants and Naming Conventions
145+
146+
Use SCREAMING_SNAKE_CASE for constants with descriptive names:
147+
148+
```rust
149+
/// Number of nanoseconds in one second.
150+
pub const NANOSECONDS_IN_SECOND: u64 = 1_000_000_000;
151+
152+
/// Bar specification for 1-minute last price bars.
153+
pub const BAR_SPEC_1_MINUTE_LAST: BarSpecification = BarSpecification {
154+
step: NonZero::new(1).unwrap(),
155+
aggregation: BarAggregation::Minute,
156+
price_type: PriceType::Last,
157+
};
158+
```
159+
160+
### Re-export Patterns
161+
162+
Organize re-exports alphabetically and place at the end of lib.rs files:
163+
164+
```rust
165+
// Re-exports
166+
pub use crate::{
167+
nanos::UnixNanos,
168+
time::AtomicTime,
169+
uuid::UUID4,
170+
};
171+
172+
// Module-level re-exports
173+
pub use crate::identifiers::{
174+
account_id::AccountId,
175+
actor_id::ActorId,
176+
client_id::ClientId,
177+
};
178+
```
179+
62180
### Documentation Standards
63181

64182
#### Module-Level Documentation
65183

66-
All modules should have comprehensive module-level documentation:
184+
All modules must have module-level documentation starting with a brief description:
67185

68186
```rust
69187
//! Functions for correctness checks similar to the *design by contract* philosophy.
@@ -74,6 +192,38 @@ All modules should have comprehensive module-level documentation:
74192
//! some section of code - for correct behavior as per the design specification.
75193
```
76194

195+
For modules with feature flags, document them clearly:
196+
197+
```rust
198+
//! # Feature flags
199+
//!
200+
//! This crate provides feature flags to control source code inclusion during compilation,
201+
//! depending on the intended use case:
202+
//!
203+
//! - `ffi`: Enables the C foreign function interface (FFI) from [cbindgen](https://github.com/mozilla/cbindgen).
204+
//! - `python`: Enables Python bindings from [PyO3](https://pyo3.rs).
205+
//! - `stubs`: Enables type stubs for use in testing scenarios.
206+
```
207+
208+
#### Field Documentation
209+
210+
All struct and enum fields must have documentation with terminating periods:
211+
212+
```rust
213+
pub struct Currency {
214+
/// The currency code as an alpha-3 string (e.g., "USD", "EUR").
215+
pub code: Ustr,
216+
/// The currency decimal precision.
217+
pub precision: u8,
218+
/// The ISO 4217 currency code.
219+
pub iso4217: u16,
220+
/// The full name of the currency.
221+
pub name: Ustr,
222+
/// The currency type, indicating its category (e.g. Fiat, Crypto).
223+
pub currency_type: CurrencyType,
224+
}
225+
```
226+
77227
#### Function Documentation
78228

79229
Document all public functions with:
@@ -171,14 +321,23 @@ impl Send for MessageBus {
171321

172322
#### Test Organization
173323

324+
Use consistent test module structure with section separators:
325+
174326
```rust
327+
////////////////////////////////////////////////////////////////////////////////
328+
// Tests
329+
////////////////////////////////////////////////////////////////////////////////
175330
#[cfg(test)]
176331
mod tests {
177332
use rstest::rstest;
178333
use super::*;
179-
use crate::stubs::*;
334+
use crate::identifiers::{Symbol, stubs::*};
180335

181-
// Tests here
336+
#[rstest]
337+
fn test_string_reprs(symbol_eth_perp: Symbol) {
338+
assert_eq!(symbol_eth_perp.as_str(), "ETH-PERP");
339+
assert_eq!(format!("{symbol_eth_perp}"), "ETH-PERP");
340+
}
182341
}
183342
```
184343

@@ -188,11 +347,12 @@ Use the `rstest` attribute consistently, and for parameterized tests:
188347

189348
```rust
190349
#[rstest]
191-
#[case(1)]
192-
#[case(3)]
193-
#[case(5)]
194-
fn test_with_different_periods(#[case] period: usize) {
195-
// Test implementation
350+
#[case("AUDUSD", false)]
351+
#[case("AUD/USD", false)]
352+
#[case("CL.FUT", true)]
353+
fn test_symbol_is_composite(#[case] input: &str, #[case] expected: bool) {
354+
let symbol = Symbol::new(input);
355+
assert_eq!(symbol.is_composite(), expected);
196356
}
197357
```
198358

@@ -203,6 +363,7 @@ Use descriptive test names that explain the scenario:
203363
```rust
204364
fn test_sma_with_no_inputs()
205365
fn test_sma_with_single_input()
366+
fn test_symbol_is_composite()
206367
```
207368

208369
## Unsafe Rust
@@ -232,27 +393,6 @@ and covering the invariants which the function expects the callers to uphold, an
232393
unsafe impl Send for MessageBus {}
233394
```
234395

235-
### File Headers
236-
237-
All Rust files should include the standard copyright header:
238-
239-
```rust
240-
// -------------------------------------------------------------------------------------------------
241-
// Copyright (C) 2015-2025 Nautech Systems Pty Ltd. All rights reserved.
242-
// https://nautechsystems.io
243-
//
244-
// Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
245-
// You may not use this file except in compliance with the License.
246-
// You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
247-
//
248-
// Unless required by applicable law or agreed to in writing, software
249-
// distributed under the License is distributed on an "AS IS" BASIS,
250-
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
251-
// See the License for the specific language governing permissions and
252-
// limitations under the License.
253-
// -------------------------------------------------------------------------------------------------`
254-
```
255-
256396
## Tooling Configuration
257397

258398
The project uses several tools for code quality:

0 commit comments

Comments
 (0)