Arbitrum Stylus logo

Stylus by Example

Inheritance (Trait-Based Composition)

Stylus doesn’t use classical inheritance. Instead, you compose behavior with Rust traits and expose them through the router using #[implements(...)]. This yields Solidity-compatible ABIs with strong type safety.

For Solidity developers

Think of traits like interfaces, and #[implements(...)] as wiring those interfaces into your contract’s externally callable surface (ABI). There’s no virtual/override; you model reuse via composition and default methods.

Overview

  • Define traits (interfaces) per capability (e.g., ERC-20, Ownable).
  • Define storage components per capability.
  • Build an entrypoint contract that contains those components.
  • Use one #[public] inherent impl with #[implements(...)] to export the traits.
  • Implement each trait for the entrypoint type with #[public] impl Trait for Contract.

ABI export considerations (selector precision)

Rust method names are snake_case; Solidity selectors are camelCase and may overlap across traits. When two methods would collide in the ABI, add:

1#[selector(name = "ActualSolidityName")]
1#[selector(name = "ActualSolidityName")]

In the example below, ERC-20’s name() remains standard, while an extra branding method is exported as displayName() to avoid colliding with name().

Method search order

When a call hits your contract, the router checks:

  1. Methods defined directly on the entrypoint (the #[public] impl Contract { ... } block)
  2. Methods from traits listed in #[implements(...)], in order
  3. If not found, the call reverts

Full Example

This single example shows:

  • Trait-based composition (ERC-20 + Ownable)
  • A second trait (IBranding) exposing a “name-like” method with a custom selector (displayName) to avoid ABI collisions with ERC-20’s name()
  • A single #[public] inherent block wired with #[implements(...)] (no router conflicts)

src/lib.rs

src/lib.rs

1Loading...
1Loading...

Cargo.toml

1Loading...
1Loading...

src/main.rs

1Loading...
1Loading...

Key takeaways

  • Use traits to model “inheritance”; wire them into the ABI with #[implements(...)].
  • Keep one #[public] inherent block per entrypoint to avoid router conflicts.
  • Prevent ABI collisions by assigning explicit selectors with #[selector(name = "...")] when another trait exposes a similar concept (e.g., displayName vs ERC-20 name).