Crate optfield

source ·
Expand description

optfield is a macro that, given a struct, generates another struct with the same fields, but wrapped in Option<T>.

Features:

Simple examples

The first argument is the name of the generated struct:

use optfield::optfield;

#[optfield(Opt)]
struct MyStruct {
    text: String
}

Will generate the following second struct (leaving MyStruct as is):

struct Opt {
    text: Option<String>
}

It also works with tuple structs:

#[optfield(Opt)]
struct MyTuple(String, i32);

Will generate:

struct Opt(Option<String>, Option<i32>);

Generics and lifetimes are preserved:

#[optfield(Opt)]
struct MyStruct<'a, T> {
    field: &'a T
}

Will generate:

struct Opt<'a, T> {
    field: Option<&'a T>
}

Visibility

By default, opt structs are private. To use custom visibility simply add it right before the opt struct name:

#[optfield(pub(crate) Opt)]
struct MyStruct {
    text: String
}

Will generate:

pub(crate) struct Opt {
    text: Option<String>
}

Field visibility is preserved.

Rewrapping Option fields

By default, fields that are already wrapped in Option<T> are not wrapped again:

#[optfield(Opt)]
struct MyStruct {
    text: Option<String>,
    number: i32
}

Will generate:

struct Opt {
    text: Option<String>,
    number: Option<i32>
}

To rewrap them pass the rewrap argument:

#[optfield(Opt, rewrap)]
struct MyStruct {
    text: Option<String>,
    number: i32
}

Will generate:

struct Opt {
    text: Option<Option<String>>,
    number: Option<i32>
}

Documentation

To document the opt struct, either duplicate the same documentation as the original using the doc argument by itself:

/// My struct documentation
/// ...
#[optfield(Opt, doc)]
struct MyStruct {
    text: String
}

Will generate:

/// My struct documentation
/// ...
struct Opt {
    text: Option<String>
}

Or write custom documentation by giving doc a value:

#[optfield(
    Opt,
    doc = "
        Custom documentation
        for Opt struct...
    "
)]
struct MyStruct {
    text: String
}

Will generate:

/// Custom documentation
/// for Opt struct...
struct Opt {
    text: Option<String>
}

Attributes

The attrs argument alone makes optfield insert the same attributes as the original:

#[optfield(Opt, attrs)]
#[cfg(test)]
#[derive(Clone)]
struct MyStruct {
    text: String
}

Will generate:

#[cfg(test)]
#[derive(Clone)]
struct Opt {
    text: Option<String>
}

To add more attributes besides the original ones, use attrs = add(...):

#[optfield(
    Opt,
    attrs = add(
        cfg(test),
        derive(Clone)
    )
)]
#[derive(Debug)]
struct MyStruct {
    text: String
}

Will generate:

#[derive(Debug)]
#[cfg(test)]
#[derive(Clone)]
struct Opt {
    text: Option<String>
}

To replace with other attributes, attrs = (...):

#[optfield(
    Opt,
    attrs = (
        cfg(test),
        derive(Clone)
    )
)]
#[derive(Debug)]
struct MyStruct {
    text: String
}

Will generate:

#[cfg(test)]
#[derive(Clone)]
struct Opt {
    text: Option<String>
}

NOTE on attribute order: optfield, like any other proc macro, only sees the attributes defined after it:

#[cfg(test)] // optfield is unaware of this attribute
#[optfield(Opt, attrs)]
#[derive(Debug)]
struct MyStruct;

Will generate:

#[derive(Debug)]
struct Opt;

Field documentation

By default, field documentation is removed:

#[optfield(Opt)]
struct MyStruct {
    /// Field
    /// documentation
    text: String
}

Will generate:

struct Opt {
    text: Option<String>
}

To preserve field documentation use the field_doc argument:

#[optfield(Opt, field_doc)]
struct MyStruct {
    /// Field
    /// documentation
    text: String
}

Will generate:

struct Opt {
    /// Field
    /// documentation
    text: Option<String>
}

Field attributes

Field attributes can be handled using the field_attrs argument which works similarly to attrs, but applies to all fields.

field_attrs can be used independently of attrs.

By default, no field attributes are inserted:

#[optfield(Opt, attrs)]
#[derive(Deserialize)]
struct MyStruct {
    #[serde(rename = "text")]
    my_text: String
}

Will generate:

#[derive(Deserialize)]
struct Opt {
    my_text: Option<String>
}

To keep them:

#[optfield(Opt, attrs, field_attrs)]
#[derive(Deserialize)]
struct MyStruct {
    #[serde(rename = "text")]
    my_text: String
}

Will generate:

#[derive(Deserialize)]
struct Opt {
    #[serde(rename = "text")]
    my_text: Option<String>
}

To add more attributes:

#[optfield(
    Opt,
    attrs,
    field_attrs = add(
        serde(default)
    )
)]
#[derive(Deserialize)]
struct MyStruct {
    #[serde(rename = "text")]
    my_text: String,
    #[serde(rename = "number")]
    my_number: i32
}

Will generate:

#[derive(Deserialize)]
struct Opt {
    #[serde(rename = "text")]
    #[serde(default)]
    my_text: Option<String>,
    #[serde(rename = "number")]
    #[serde(default)]
    my_number: Option<i32>
}

To replace all field attributes:

#[optfield(
    Opt,
    attrs,
    field_attrs = (
        serde(default)
    )
)]
#[derive(Deserialize)]
struct MyStruct {
    #[serde(rename = "text")]
    my_text: String,
    #[serde(rename = "number")]
    my_number: i32
}

Will generate:

#[derive(Deserialize)]
struct Opt {
    #[serde(default)]
    my_text: Option<String>,
    #[serde(default)]
    my_number: Option<i32>
}

Merging

When the merge_fn argument is used optfield will add a method to the original struct that merges an opt struct back into the original.

By default, the method is named merge_opt and has the following signature:

// assuming the opt struct is named Opt;
// takes opt by value;
fn merge_opt(&mut self, opt: Opt)

When merging, all values of the opt struct that are Some(...) are set as values of the original struct fields.

To use it:

#[optfield(Opt, merge_fn)]
struct MyStruct {
    text: String,
    number: i32
}

let mut original = MyStruct {
    text: "awesome".to_string(),
    number: 1
};

let opt = Opt {
    text: Some("amazing".to_string()),
    number: None
};

original.merge_opt(opt);

// text field value is merged
assert_eq!(original.text, "amazing");
// number field stays the same
assert_eq!(original.number, 1);

The merge function can be given:

  • custom name: merge_fn = my_merge_fn
  • custom visibility (default is private): merge_fn = pub(crate)
  • both: merge_fn = pub my_merge_fn

From

When the from argument is used, From<MyStruct> is implemented for Opt.

#[optfield(Opt, from)]
struct MyStruct {
    text: String,
    number: i32,
}

let original = MyStruct {
    text: "super".to_string(),
    number: 2,
};

let from = Opt::from(original);
assert_eq!(from.text.unwrap(), "super");
assert_eq!(from.number.unwrap(), 2);

Attribute Macros