Building and running code

  • Prefer to run build/gen.py --debug unless you need a release build.
  • NEVER run ninja without an explicit list of targets to build
    • Build gn_unittests if you want to run unittests
      • You can manually run them with a filter in the hot-path of the development loop (eg. ./out/gn_unittests --gtest_filter=Functions.*)
    • Build gn if you want to run gn itself (but if you want to run it on chromium, prefer building GN in a separate output directory with --release, as the debug build is very slow on a project of chromium's size).
  • Before uploading code, make sure to:
    • Run the full test suite via building the target run_tests
    • Run tools/run_formatter.sh to format the code
    • Run tools/update_reference.sh to ensure documentation is up to date.
    • If you changed rust code, also run tools/run_linter.sh
  • NEVER delete the build output directory without permission to fix build errors. If you feel the need to do so, there is likely a far more fundamental issue.

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.
  • If you ever believe that #[allow] is required to bypass the linter:
    • You must get permission from the user.
    • You must write a comment above the allow saying why we need it.
    • You must put it in as specific a place as is feasible. Avoid, for example, allowing a lint for a whole file.

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.

Building and testing

  • NEVER use cargo to build and test rust changes
    • cargo commands are run for you as a part of ninja.
  • To build, use ninja -C out gn/gn_unittests/rust_unittests respectively, depending on what you want to build.
  • To test a single test, run ninja -C out gn_unittests && out/gn_unittests --gtest_filter=prefix*.
  • To run all unit tests, run ninja -C out run_gn_unittests run_rust_unittests
  • To perform tests that would be run on CQ, run ninja -C out
    • Note: This also runs linter and formatter tests

C++ Style guidelines and best practices

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