#generics #pattern #macro-derive #wrap

no-std amplify

Amplifying Rust language capabilities: multiple generic trait implementations, type wrappers, derive macros

105 releases (64 stable)

5.0.0-beta.1 Aug 5, 2024
4.8.0 Nov 3, 2024
4.7.0 Aug 5, 2024
4.6.0 Feb 15, 2024
0.1.8 Jul 4, 2020

#50 in Rust patterns

Download history 3762/week @ 2024-07-19 5572/week @ 2024-07-26 4964/week @ 2024-08-02 6462/week @ 2024-08-09 6392/week @ 2024-08-16 5382/week @ 2024-08-23 8037/week @ 2024-08-30 5917/week @ 2024-09-06 4975/week @ 2024-09-13 5892/week @ 2024-09-20 7039/week @ 2024-09-27 5793/week @ 2024-10-04 7276/week @ 2024-10-11 5604/week @ 2024-10-18 5921/week @ 2024-10-25 5819/week @ 2024-11-01

25,553 downloads per month
Used in 194 crates (126 directly)

MIT license

180KB
4K SLoC

Rust Amplify Library

Build Tests Lints codecov

crates.io Docs unsafe forbidden MIT licensed

Amplifying Rust language capabilities: multiple generic trait implementations, type wrappers, derive macros. Tiny library with zero non-optional dependencies. Able to work as no_std.

Minimum supported rust compiler version (MSRV): 1.75.0; rust edition 2021.

Main features

Generics

Library proposes generic implementation strategies, which allow multiple generic trait implementations.

Implementing trait for a generic type ("blanket implementation") more than once (applies both for local and foreign traits) - or implement foreign trait for a concrete type where there is some blanket implementation in the upstream. The solution is to use special pattern by @Kixunil. I use it widely and have a special helper type in src/strategy.rssrc/strategy.rs module.

With that helper type you can write the following code, which will provide you with efficiently multiple blanket implementations of some trait SampleTrait:

pub trait SampleTrait {
    fn sample_trait_method(&self);
}

// Define strategies, one per specific implementation that you need,
// either blanket or concrete
pub struct StrategyA;

pub struct StrategyB;

pub struct StrategyC;

// Define a single marker type
pub trait Strategy {
    type Strategy;
}

// Do a single blanket implementation using Holder and Strategy marker trait
impl<T> SampleTrait for T
    where
        T: Strategy + Clone,
        amplify::Holder<T, <T as Strategy>::Strategy>: SampleTrait,
{
    // Do this for each of sample trait methods:
    fn sample_trait_method(&self) {
        amplify::Holder::new(self.clone()).sample_trait_method()
    }
}

// Do this type of implementation for each of the strategies
impl<T> SampleTrait for amplify::Holder<T, StrategyA>
    where
        T: Strategy,
{
    fn sample_trait_method(&self) {
        /* ... write your implementation-specific code here */
    }
}

# pub struct ConcreteTypeA;

// Finally, apply specific implementation strategy to a concrete type
// (or do it in a blanket generic way) as a marker:
impl Strategy for ConcreteTypeA {
    type Strategy = StrategyA;
}

Derive macros

  • Display
  • From
  • Error
  • Getters
  • AsAny
  • Wrapper

A sample of what can be done with the macros:

#[derive(From, Error, Display, Debug)]
#[display(doc_comments)]
pub enum Error {
    // You can specify multiple conversions with separate attributes
    #[from(::std::io::Error)]
    #[from(IoError)]
    /// Generic I/O error
    Io,

    #[from]
    // This produces error description referencing debug representation
    // of the internal error type
    /// Formatting error: {_0:}
    Format(::std::fmt::Error),

    #[from]
    /// Some complex error, here are details: {details}
    WithFields { details: ::std::str::Utf8Error },

    #[display(LowerHex)]
    MultipleFields {
        // ...and you can also covert error type
        #[from(IoErrorUnit)]
        // rest of parameters must implement `Default`
        io: IoError,

        #[display(ToHex::to_hex)]
        details: String,
    },
}

More information is given in amplify_derive crate README.

Macros

  • none! as an alias for Default::default() on collection types and types for which semantics makes it sensible to emphasize that the operation initializes empty structure.
  • s! for fast &str -> String conversions
  • Collection-generating macros:
    • map! & bmap! for a rappid HashMap and BTreeMap creation
    • set! & bset! for a rappid HashSet and BTreeSet creation
    • list! for LinkedList

Wapper type

Wrapper trait helps in creating wrapped rust newtypes, Wrapped types are used for allowing implemeting foreign traits to foreign types: https://doc.rust-lang.org/stable/rust-by-example/generics/new_types.html

Trait defines convenient methods for accessing inner data, construct and deconstruct newtype. It also serves as a marker trait for newtypes.

The trait works well with #[derive(Wrapper)] from amplify_derive crate

Build

cargo build --all
cargo test

As a reminder, minimum supported rust compiler version (MSRV) is 1.36.0, so it can be build with either nightly, dev, stable or 1.36+ version of the rust compiler. Use rustup for getting the proper version, or add +toolchain parameter to both cargo build and cargo test commands.

Benchmark

RUSTFLAGS="--cfg bench" cargo bench

Dependencies

~0.3–1MB
~17K SLoC