Instruction file imported from LedgerHQ/ledger-device-rust-sdk (
.github/instructions/rust.instructions.md). Copyright stays with the author.
Rust Coding Conventions and Best Practices
Follow idiomatic Rust practices and community standards when writing Rust code.
These instructions are based on The Rust Book, Rust API Guidelines, RFC 430 naming conventions, and the broader Rust community at users.rust-lang.org.
These are general Rust guidelines and may not all apply to the embedded/no_std portions of this codebase.
General Instructions
- Always prioritize readability, safety, and maintainability.
- Use strong typing and leverage Rust's ownership system for memory safety.
- Break down complex functions into smaller, more manageable functions.
- For algorithm-related code, include explanations of the approach used.
- Write code with good maintainability practices, including comments on why certain design decisions were made.
- Handle errors gracefully using
Result<T, E>and provide meaningful error messages. - For external dependencies, mention their usage and purpose in documentation.
- Use consistent naming conventions following RFC 430.
- Write idiomatic, safe, and efficient Rust code that follows the borrow checker's rules.
- Ensure code compiles without warnings.
Patterns to Follow
- Use modules (
mod) and public interfaces (pub) to encapsulate logic. - Handle errors properly using
?,match, orif let. - Implement traits to abstract services or external dependencies.
- Prefer enums over flags and states for type safety.
- Use builders for complex object creation.
- Use iterators instead of index-based loops as they're often faster and safer.
- Use
&strinstead ofStringfor function parameters when you don't need ownership. - Prefer borrowing and zero-copy operations to avoid unnecessary allocations.
Ownership, Borrowing, and Lifetimes
- Prefer borrowing (
&T) over cloning unless ownership transfer is necessary. - Use
&mut Twhen you need to modify borrowed data. - Explicitly annotate lifetimes when the compiler cannot infer them.
- Use
Rc<T>for single-threaded reference counting andArc<T>for thread-safe reference counting. - Use
RefCell<T>for interior mutability in single-threaded contexts.
Patterns to Avoid
- Don't use
unwrap()orexpect()unless failure is truly impossible or represents an unrecoverable state. - Avoid panics in library code—return
Resultinstead. - Don't rely on global mutable state—use dependency injection or thread-safe containers.
- Avoid deeply nested logic—refactor with functions or combinators.
- Don't ignore warnings—treat them as errors during CI.
- Avoid
unsafeunless required and fully documented. - Don't overuse
clone(), use borrowing instead of cloning unless ownership transfer is needed. - Avoid premature
collect(), keep iterators lazy until you actually need the collection. - Avoid unnecessary allocations—prefer borrowing and zero-copy operations.
Code Style and Formatting
- Follow the Rust Style Guide and use
rustfmtfor automatic formatting. - Keep lines under 100 characters when possible.
- Place function and struct documentation immediately before the item using
///. - Use
cargo clippyto catch common mistakes and enforce best practices.
Error Handling
- Use
Result<T, E>for recoverable errors andpanic!only for unrecoverable errors. - Prefer
?operator overunwrap()orexpect()for error propagation. - Use
Option<T>for values that may or may not exist. - Provide meaningful error messages and context.
- Error types should be meaningful and well-behaved (implement standard traits).
- Validate function arguments and return appropriate errors for invalid input.
API Design Guidelines
Common Traits Implementation
Eagerly implement common traits where appropriate:
Copy,Clone,Eq,PartialEq,Ord,PartialOrd,Hash,Debug,Display,Default- Use standard conversion traits:
From,AsRef,AsMut - Collections should implement
FromIteratorandExtend - Note:
SendandSyncare auto-implemented by the compiler when safe; avoid manual implementation unless usingunsafecode
Type Safety and Predictability
- Use newtypes to provide static distinctions
- Arguments should convey meaning through types; prefer specific types over generic
boolparameters - Use
Option<T>appropriately for truly optional values - Functions with a clear receiver should be methods
- Only smart pointers should implement
DerefandDerefMut
Future Proofing
- Use sealed traits to protect against downstream implementations
- Structs should have private fields
- Functions should validate their arguments
- All public types must implement
Debug
Testing and Documentation
Testing (Speculos / Custom Targets)
This repo targets custom ARM Cortex-M devices, not standard Rust host platforms. Tests cannot
use standard cargo test on the host — they must run inside the
Speculos emulator against a device target.
Custom test harness: The testmacro crate provides test_item, a proc-macro replacement
for #[test]. Inside #[cfg(test)] modules, import it as:
#[cfg(test)]
mod tests {
use crate::testing::TestType;
use testmacro::test_item as test;
#[test]
fn test_example() {
// test body — return Ok(()) on success
}
}
The custom runner sdk_test_runner in testing.rs collects TestType items, runs them
via PIC-aware function pointers, and reports results through semihosting output on Speculos.
Running tests:
# Unit tests — requires speculos on PATH and a device target
cargo test --target nanosplus --features speculos,debug --tests
# Run an example on Speculos (uses per-target runner from config.toml)
cargo run --example nbgl_home_and_settings --target stax --release \
--config ledger_device_sdk/examples/config.toml
Key differences from standard Rust testing:
- Do not create a
tests/directory for integration tests — there are no host-side integration tests. Examples inledger_device_sdk/examples/serve as integration-level verification when run on Speculos. - Use
assert_eq_err!(fromtesting.rs) instead ofassert_eq!inside test functions. It returnsErr(())instead of panicking, allowing the harness to report all failures rather than aborting on the first one. - Enable the
speculosanddebugcargo features when running tests so that emulator-specific code paths and semihosting output are active. - Every test binary needs a panic handler; the custom harness provides
test_panicfor this.
Documentation
- Write clear and concise comments for each function, struct, enum, and complex logic.
- Ensure functions have descriptive names and include comprehensive documentation.
- Document all public APIs with rustdoc (
///comments) following the API Guidelines. - Use
#[doc(hidden)]to hide implementation details from public documentation. - Document error conditions, panic scenarios, and safety considerations.
- Examples should use
?operator, notunwrap()or deprecatedtry!macro.
Project Organization
- Use semantic versioning in
Cargo.toml. - Include comprehensive metadata:
description,license,repository,keywords,categories. - Use feature flags for optional functionality.
- Organize code into modules using
mod.rsor named files. - Keep
main.rsorlib.rsminimal - move logic to modules.
Quality Checklist
Before publishing or reviewing Rust code, ensure:
Core Requirements
- Naming: Follows RFC 430 naming conventions
- Traits: Implements
Debug,Clone,PartialEqwhere appropriate - Error Handling: Uses
Result<T, E>and provides meaningful error types - Documentation: All public items have rustdoc comments with examples
- Testing: Tests use
testmacro::test_itemand run on Speculos against a device target
Safety and Quality
- Safety: No unnecessary
unsafecode, proper error handling - Performance: Efficient use of iterators, minimal allocations
- API Design: Functions are predictable, flexible, and type-safe
- Future Proofing: Private fields in structs, sealed traits where appropriate
- Tooling: Code passes
cargo fmtandcargo clippy; tests pass on Speculos