Rust Style Guidelines & Best Practices

Please adhere to the following conventions and best practices when writing or modifying Rust code in this repository:

Code Structure & Formatting

  • Code Ordering: Be consistent about the layout within a file.
    • The structure should generally be:
      1. mod statements
      2. use statements
      3. Core code (structs, enums, impls, functions)
      4. Test module at the bottom: #[cfg(test)]
    • Structs should always come immediately before their impl and impl traits.
    • Full types should be defined before reference types (e.g. if we were writing the standard library, String before str)
  • Inlining: If you only use a variable once, prefer inlining it rather than assigning it to a separate binding.
  • Collect: Prefer using .collect() over manually iterating and adding/pushing to data structures when possible.
  • Unused Variables: Variables starting with _ (e.g. _var) should only be used if they are truly never used. If you begin using a variable that was prefixed with an underscore, rename it to remove the underscore prefix.
  • Unsafe Code: All unsafe blocks must be prefixed with a // Safety: <reason> comment explaining why the usage is safe.
  • Warnings: The code (including the tests) should compile with no warnings and no clippy lint errors.

Imports & Use Statements

  • Test-only Imports: Imports that are only used inside tests should be moved into a module block guarded by #[cfg(test)].

Documentation

  • Anything marked as pub should have a docstring (starts with ///).

Coding guidelines

  • Data members should not be public without good reason
  • FFI Boundary: No FFI function calls (such as extern "C" blocks or raw FFI calls) are allowed outside of the ffi submodule. The only exception is intern_string.
  • No Statics: Do not use global static variables or thread_local! structures. They hinder concurrency, unit testing isolation, and multi-session safety. Always prefer passing state and context explicitly via arguments or trait objects.
  • Dynamic dispatch: Never use dynamic dispatch (dyn Trait) in the hot path (any code that could be called an arbitrary number of times by a starlark rule).
    • Avoid using dynamic dispatch (dyn Trait) in general if you can, but you may use it to increase testability of code if it is not in the hot path.
      • Eg. Using dyn Trait to support a “real” version in production code and a fake one in test code.
  • collect_repr and collect_str
    • collect_repr should be defined on starlark types iff Debug is available on a type. It should just write!(collector, "{:?}").unwrap()
    • collect_str should be defined on starlark types iff Display is available on a type. It should just write!(collector, "{}").unwrap()
  • Error Handling: Prefer using custom error enums with thiserror for crate-level errors, rather than generic strings or custom hand-written Display implementations. Let exceptions and failures bubble up naturally.
  • Crate Encapsulation: Keep modules, structs, and fields private or pub(crate) by default to maintain clean boundaries. Only use pub for APIs that are intended to be consumed by other crates.

C++ Style guidelines and best practices

  • Never use #pragma once - use header guards instead.