Crate fromsuper

Source
Expand description

fromsuper provides a procedural macro that helps with converting (large) super structs to (smaller) sub structs, by discarding unneeded data. It can also automatically unpack Options during this conversion. It implements TryFrom if Options need to be unpacked, and From otherwise.

It can be useful e.g., when working with large parser outputs of which only a subset is actually needed. Reducing such structs to the data actually needed improves maintainability. If the original struct contains lots of Options, unpacking them validates that the needed data is present and greatly improves ergonomics of further handling.

§Basic Usage

Include fromsuper in your project by adding the following to your Cargo.toml:

fromsuper = "0.2"

You may also want to use the derive macro:

use fromsuper::FromSuper;

Options for the derive macro are specified by using the fromsuper attribute. The only option that is necessary is from_type, defining the super struct to convert from:

struct Bar {
    a: u32,
    b: String,
    c: HashSet<u64>,
    d: ComplexData,
}

#[derive(FromSuper)]
#[fromsuper(from_type = "Bar")]
struct Foo {
    a: u32,
    c: HashSet<u64>,
}

let bar = Bar::new();
let foo: Foo = bar.into(); // using Foo's derived implementation of From<Bar>

If a sub struct’s field is not named the same as the original one, the field attribute rename_from can be used to specify the mapping:

#[derive(FromSuper)]
#[fromsuper(from_type = "Bar")]
struct Foo {
    a: u32,
    #[fromsuper(rename_from = "c")]
    new_name: HashSet<u64>,
}

§Unpacking Options

The automatic unpacking of Options from the original struct can be enabled by adding the unpack argument. Single fields can opt-out of the unpacking (unpack = false), e.g., when not all original fields are Options, or when you can tolerate None values. Also, for field types that implement Default, None values can be replaced by the default value by specifying unpack_or_default = true. When unpacking is enabled, TryFrom is implemented instead of From, in order to fail when required values are None:

struct Bar {
    a: Option<u32>,
    b: String,
    c: Option<HashSet<u64>>,
    d: Option<ComplexData>,
    e: Option<u64>
}

#[derive(FromSuper)]
#[fromsuper(from_type = "Bar", unpack = true)]
struct Foo {
    #[fromsuper(unpack = false)]
    b: String,
    #[fromsuper(unpack_or_default = true)]
    c: HashSet<u64>,
    d: ComplexData,
    #[fromsuper(unpack = false)]
    e: Option<u64>
}

let bar = Bar::new();
let foo: Foo = bar.try_into()?; // using Foo's derived implementation of TryFrom<Bar>

§Generics

derive(FromSuper) can handle many situations in which generics are involved. Both, the super and the sub struct, can have type parameters. When specifying the super struct, however, it is impossible to decide whether e.g. the T in Bar<T> is a type parameter or a concrete type. In order to differentiate the two meanings, generic type parameters have to be prefixed with a # sign:

struct Bar<T, U> {
    x: Vec<T>,
    y: Vec<U>,
    z: U,
}

#[derive(FromSuper)]
#[fromsuper(from_type = "Bar<#T,u32>")]
struct Foo<T> {
    x: Vec<T>,
    z: u32,
}

This way, it is possible to reduce the number of type parameters for the sub struct, if its fields do not require them.

Lifetime parameters for both, the super and the sub struct, should automatically be handled properly.

§Referencing instead of consuming the super struct

If the super struct can or should not be consumed, the derived sub struct can be made to contain only references to the original values instead of consuming them. This behavior can be activated by using the make_refs argument. Note that this can only be activated for the whole struct, not on a per-field basis.

struct Bar {
    a: Option<String>,
    b: String,
}

#[derive(FromSuper)]
#[fromsuper(from_type = "&'a Bar", unpack = true, make_refs = true)]
struct Foo<'a> {
    a: &'a String,
    #[fromsuper(unpack = false)]
    b: &'a String,
}

Derive Macros§

FromSuper
The procedural macro this crate is all about.