typesift-macros 0.1.1

Implementation detail of the typesift crate: the #[derive(TypeSift)] macro
Documentation

TypeSift

A Rust derive macro that walks structs, enums, collections and maps to collect every value of a given type.

Usage

use typesift::TypeSift;

#[derive(Debug, PartialEq, TypeSift)]
struct UserId(u64);

#[derive(TypeSift)]
struct Group {
    name: String,
    admin: UserId,
    members: Vec<UserId>,
}

let group = Group {
    name: "Core".to_string(),
    admin: UserId(7),
    members: vec![UserId(12), UserId(9)],
};

// Get references to all values of type `UserId` inside of `group`
let user_ids = group.sift::<UserId>();
assert_eq!(user_ids, [&UserId(7), &UserId(12), &UserId(9)]);

Motivation

Big API responses tend to be graphs of DTOs that point at other resources by id. Before responding you want those resources loaded. Collecting the ids normally means a function that walks every field by hand, and that function may quietly go stale when a new field is added.

Typesift reduces the amount of code that needs to be written and eliminates this concern.

Integration with third-party crates

If you don't own a type, the orphan rule stops it from implementing the trait.

Feature flags

Support for types from some popular crates can be enabled with the corresponding crate feature: bytes, chrono, enum-map, http, indexmap, rust_decimal, serde_json, smallvec, time, url, uuid.

Example:

typesift = { version = "0.1", features = ["uuid"] }

Typesift attributes

If the crate you're interested in is not supported, typesift offers you the following macro attributes to overcome the issue:

  • #[typesift(leaf)] finds the field but doesn't look inside it. This is what you want for ids, timestamps and other opaque values.
  • #[typesift(skip)] leaves it out of the search entirely. Also the escape hatch for RefCell, Mutex and friends, which can't lend out their contents for as long as the search needs them.
  • #[typesift(with = path)] hands the field to a function of yours, which decides what the search sees and in what order.

Limitations

Everything involved has to be 'static, so the derive rejects types with lifetime parameters, though &'static T fields are fine. Interior mutability isn't supported; skip those fields.

Benchmarks

Run benchmarks with just bench.

variant time vs baseline
manual_collector (baseline) 3.82 µs -
typesift 5.48 µs 1.43x slower
manual_iterator 31.78 µs 8.3x slower
  • manual_collector walks the fields by hand and pushes into a Vec, which is about as fast as this gets.
  • TypeSift costs roughly 1.4x of that
  • manual_iterator is the same walk written as a chain of impl Iterators, which is what most people reach for.

License

MIT © Serhii Potapov