diff --git a/src/lib.rs b/src/lib.rs index 5e90f1c..f645b3d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,6 +13,11 @@ //! * [Field attributes](#field-attributes) //! * [Merging](#merging) //! * [From](#from) +//! * [Custom wrappers](#custom-wrappers) +//! * [rewrap](#wrapper--rewrap) +//! * [merging](#wrapper--merge_fn) +//! * [from](#wrapper--from) +//! * [nesting](#wrapper-nesting) //! //! # Simple examples //! The first argument is the name of the generated struct: @@ -447,6 +452,241 @@ //! assert_eq!(from.text.unwrap(), "super"); //! assert_eq!(from.number.unwrap(), 2); //! ``` +//! +//! # Custom wrappers +//! Custom wrappers can be used instead of `Option` with the `wrapper` argument: +//! ``` +//! # use optfield::*; +//! enum MyWrapper { +//! Empty, +//! Number(i32), +//! Something(T), +//! } +//! +//! #[optfield(Opt, wrapper = MyWrapper)] +//! struct MyStruct { +//! optional: Option, +//! text: String, +//! } +//! ``` +//! Will generate: +//! ``` +//! # struct MyWrapper(T); +//! struct Opt { +//! optional: MyWrapper>, +//! text: MyWrapper, +//! } +//! ``` +//! Any type with a signature taking exactly one generic (`Wrapper`) can be +//! used as a wrapper: structs, tuples, enums or type aliases. +//! +//! When using `wrapper` some behavior changes: +//! +//! ### `wrapper` + `rewrap` +//! `rewrap` will target the wrapper instead of `Option`: +//! ``` +//! # use optfield::*; +//! # struct MyWrapper(T); +//! #[optfield(Opt, wrapper = MyWrapper)] +//! struct MyStruct { +//! optional: Option, +//! text: String, +//! wrapped: MyWrapper, +//! } +//! ``` +//! Will generate: +//! ``` +//! # struct MyWrapper(T); +//! struct Opt { +//! optional: MyWrapper>, +//! text: MyWrapper, +//! wrapped: MyWrapper, +//! } +//! ``` +//! Whereas +//! ``` +//! # use optfield::*; +//! # struct MyWrapper(T); +//! #[optfield(Opt, wrapper = MyWrapper, rewrap)] +//! struct MyStruct { +//! optional: Option, +//! text: String, +//! wrapped: MyWrapper, +//! } +//! ``` +//! Will generate: +//! ``` +//! # struct MyWrapper(T); +//! struct Opt { +//! optional: MyWrapper>, +//! text: MyWrapper, +//! wrapped: MyWrapper>, +//! } +//! ``` +//! ### `wrapper` + `merge_fn` +//! Unlike with `Option` which is part of Rust core, optfield +//! can't make any assumptions about the custom types used as wrappers, so +//! when using `merge_fn` a few extra things are required. +//! +//! A `From> for Option` implementation must exist, either +//! generically or specifically for each type used for the original struct's +//! fields. For each field the value will be merged when +//! `Option::::from(opt.field)` is `Some(...)` and ignored when it is `None`, +//! `T` being the field's type in the original struct and `opt` an instance of +//! the struct generated by optfield. Example: +//! ``` +//! # use optfield::*; +//! enum MyWrapper { +//! Nothing, +//! NothingElse, +//! Something(T), +//! } +//! +//! impl From> for Option { +//! fn from(value: MyWrapper) -> Option { +//! match value { +//! MyWrapper::Nothing => None, +//! MyWrapper::NothingElse => None, +//! MyWrapper::Something(t) => Some(t), +//! } +//! } +//! } +//! +//! #[optfield(Opt, wrapper = MyWrapper, merge_fn)] +//! struct MyStruct { +//! number: i32, +//! another_number: u32, +//! text: String, +//! } +//! +//! let mut original = MyStruct { +//! number: 0, +//! another_number: 0, +//! text: "nice".to_string(), +//! }; +//! +//! let opt = Opt { +//! number: MyWrapper::Nothing, +//! another_number: MyWrapper::NothingElse, +//! text: MyWrapper::Something("great".to_string()), +//! }; +//! +//! original.merge_opt(opt); +//! +//! assert_eq!(original.number, 0); +//! assert_eq!(original.another_number, 0); +//! assert_eq!(original.text, "great".to_string()); +//! ``` +//! Furthermore, if any of the original fields are already wrapped in the custom +//! wrapper and you are **not** using `rewrap`, a +//! `From<&MyWrapper> for Option<()>` is required. This is needed in order to +//! figure out whether to merge the value without turning it into an `Option` +//! and losing its type information. If `Option::<()>::from(opt.field)` is +//! `Some(())` then the value in the original struct instance will be replaced +//! with the one from the wrapped struct and otherwise ignored. Example: +//! ``` +//! # use optfield::*; +//! #[derive(Debug, PartialEq)] +//! enum MyWrapper { +//! Nothing, +//! Something(T), +//! } +//! +//! impl From> for Option { +//! fn from(value: MyWrapper) -> Option { +//! match value { +//! MyWrapper::Nothing => None, +//! MyWrapper::Something(t) => Some(t), +//! } +//! } +//! } +//! +//! impl From<&MyWrapper> for Option<()> { +//! fn from(value: &MyWrapper) -> Self { +//! match value { +//! MyWrapper::Nothing => None, +//! MyWrapper::Something(_) => Some(()), +//! } +//! } +//! } +//! +//! #[optfield(Opt, wrapper = MyWrapper, merge_fn)] +//! struct MyStruct { +//! number: i32, +//! wrapped: MyWrapper, +//! } +//! +//! let mut original = MyStruct { +//! number: 0, +//! wrapped: MyWrapper::Something(0), +//! }; +//! +//! let opt = Opt { +//! number: MyWrapper::Something(42), +//! wrapped: MyWrapper::Something(1337), +//! }; +//! +//! original.merge_opt(opt); +//! +//! assert_eq!(original.number, 42); +//! assert_eq!(original.wrapped, MyWrapper::Something(1337)); +//! ``` +//! ### `wrapper` + `from` +//! When using `from` a `From for MyWrapper` implementation must exist, +//! either generic or specific for each of the target struct field types: +//! ``` +//! # use optfield::*; +//! #[derive(Debug, PartialEq)] +//! enum MyWrapper { +//! Nothing, +//! Something(T), +//! } +//! +//! impl From for MyWrapper { +//! fn from(value: T) -> Self { +//! MyWrapper::Something(value) +//! } +//! } +//! +//! #[optfield(Opt, wrapper = MyWrapper, from)] +//! struct MyStruct { +//! field: i32, +//! } +//! +//! let original = MyStruct { field: 88 }; +//! +//! let opt = Opt::from(original); +//! +//! assert_eq!(opt.field, MyWrapper::Something(88)); +//! ``` +//! ### Wrapper nesting +//! With its support for attributes optfield can be nested. This may be useful +//! to combine various wrappers. +//! ``` +//! # use optfield::*; +//! struct MyWrapper(T); +//! +//! #[optfield(OptMyWrapper, wrapper = MyWrapper, attrs = ( +//! optfield(OptOption) +//! ))] +//! struct MyStruct { +//! number: i32, +//! text: String, +//! } +//! ``` +//! Will generate: +//! ``` +//! # struct MyWrapper(T); +//! struct OptMyWrapper { +//! number: MyWrapper, +//! text: MyWrapper, +//! } +//! +//! struct OptOption { +//! number: Option>, +//! text: Option>, +//! } +//! ``` extern crate proc_macro; use crate::proc_macro::TokenStream;