Expand description
§DataStruct.rs
The library provides a derive macro to automatically implement “plain methods” for data structures.
Currently Available:
- Default: Standard
Default, lib-specificDataStruct::data_defaultand constant defaultConstDataStruct::DEFAULT. - Debug: Manual
Debugfilter. - Comparison: Standard
Eq,PartialEq,Ord,PartialOrd. - Operations: Standard
Add(Assign),Sub(Assign),Mul(Assign),Div(Assign).
Unlike standard derive macros, the DataStruct macro accepts user-defined behaviors without
writing implementation code.
§Basic Syntax
To configure the meta options of the generator, add #[dstruct(options)] before your definition.
To configure the fields’ options, add #[dfield(options)] before your field declaration.
use datastruct_derive::DataStruct;
#[derive(DataStruct)]
#[dstruct()]
struct Data {
#[dfield()]
field: u32,
}You can define the meta attributes multiple times, and the last declaration of each attribute will be used to generate the code.
For example,
#[dfield(no_debug, cmp(eq = false))]
#[dfield(cmp(eq = true))]is equivalent to
#[dfield(no_debug, cmp(eq = true))]§Api Document
§Default
§Default Implementation
All default implementations are like:
struct NeedDefault {
field1: usize,
field2: String,
}
// generated code
let default: NeedDefault = {
let field1: usize = 42;
let field2: String = "Something default".to_string();
NeedDefault {
field1,
field2,
}
};That means you can refer to other fields when initializing the default value.
use datastruct::DataStruct;
#[derive(DataStruct)]
#[dstruct(default)]
struct MyDefault {
#[dfield(default = "second + first")]
manual: usize,
#[dfield(default = "40", seq = -1)]
second: usize,
#[dfield(default = "2", seq = -2)]
first: usize,
}
// generated code
impl ::datastruct::DataStruct for MyDefault {
fn data_default() -> Self {
let first: usize = 2;
let second: usize = 40;
let manual: usize = second + first;
Self {
first,
second,
manual,
}
}
}§default
Ask the macro to generate an implementation of datastruct::DataStruct,
which offers a runtime-default value but does not pollute the namespace.
Syntax:
#[dstruct(default)]
Restriction:
- All fields must be provided with default value.
Field Configuration:
-
#[dfield(default = xxx)]The literal expression should be wrapped inside a string, like this:
#[dfield(default = "field + another")] #[dfield(default = "42_usize")]If no default value is provided, the field will be considered uninitialized and
default-related code cannot be generated. -
#[dfield(seq = xxx)]|#[dfield(sequence = xxx)]wherexxxisisizeChange the sequence of the fields. By default, the sequence to initialize the fields is the same as how they are declared. The sequence option can help reorganize the process. Fields that are not tagged with
seq = isizewill inherit from the default sequence. You can also set the index with negative numbers.#[dstruct(default)] struct Data { field: u8, // seq = 0 #[dfield(seq = -1)] before: u8, // default seq = 1, seq = -1 }
§const
Ask the macro to generate an implementation of datastruct::ConstDataStruct,
which offers a compile-time const default value but does not pollute the namespace.
Syntax:
#[dstruct(const)]
Restriction:
- All fields must be provided with const default value.
Field Configuration:
- Inherits from
default.
§std_default
The same as default, but implement std::default::Default instead.
Syntax:
#[dstruct(std_default)]
Restriction:
- All fields must be provided with default value.
Field Configuration:
- Inherits from
default.
Warning:
- This may pollute the namespace, and IDE may not be able to identify the implementation.
§partial
Partially default implementation.
This will produce something like:
use datastruct::DataStruct;
#[derive(DataStruct)]
#[dstruct(partial)]
struct Data {
#[dfield(default = "10")]
value1: u32,
value2: u32,
}
// generated code
impl Data {
pub fn partial_default(value2: u32) -> Self {
Self {
value1: 10,
value2,
}
}
}Syntax:
#[dstruct(partial)]
Field Configuration:
- Inherits from
default.
§Setter and Getter
§set
Generate default setter for fields.
Setter Type:
full|all: Bothsetandwith. (Default.)set:set_field_name(&mut self, value). Set the structure’s value.with:with_field_name(self, value) -> Self. Internally set the structure’s value and return itself.no: Ignore the field.
Syntax:
#[dstruct(set)]: Default setter configuration.#[dstruct(set = "setter_type")]: Set default setter configuration tosetter_type.
Field Configuration:
#[dfield(set)]: Inherit the setter configuration from the structure. Typically, you don’t need to specify this.#[dfield(set = "setter_type")]: Override the default setter configuration.
Example:
use datastruct::DataStruct;
#[derive(DataStruct)]
#[dstruct(set)] // equivalent to `set = "full"`
struct Data {
auto_set: usize,
#[dfield(set = "no")]
no_set: usize,
#[dfield(set = "set")]
only_set: usize,
}
// generated code
impl Data {
pub fn set_auto_set(&mut self, auto_set: usize) {
self.auto_set = auto_set;
}
pub fn set_only_set(&mut self, only_set: usize) {
self.only_set = only_set;
}
pub fn with_auto_set(mut self, auto_set: usize) -> Self {
self.auto_set = auto_set;
self
}
}§get
Generate default getter for fields.
Setter Type:
full|all: Bothmoveandget.get:field_name(&self) -> &value. Get the structure’s field’s reference. (Default.)move:get_field_name(self) -> move. Move the field out of the structure.no: Ignore the field.
Syntax:
#[dstruct(get)]: Default getter configuration.#[dstruct(get = "getter_type")]: Set default getter configuration togetter_type.
Field Configuration:
#[dfield(get)]: Inherit the getter configuration from the structure. Typically, you don’t need to specify this.#[dfield(get = "getter_type")]: Override the default getter configuration.
Example:
use datastruct::DataStruct;
#[derive(DataStruct)]
#[dstruct(get)] // equivalent to `get = "get"`
struct Data {
auto_get: usize,
#[dfield(get = "no")]
no_get: usize,
#[dfield(get = "full")]
can_move: usize,
}
// generated code
impl Data {
pub fn auto_get(&self) -> &usize {
&self.auto_get
}
pub fn can_move(&self) -> &usize {
&self.can_move
}
pub fn get_can_move(self) -> usize {
self.can_move
}
}§map
Map a field’s value and modify the structure. This does not have structure-level configuration.
Field Configuration:
#[dfield(map)]|#[dfield(map = ture): Enable mapping.#[dfield(map = false)]: Disable mapping. Typically, you don’t need to explicitly disable mapping since it’s the default behavior.
Examples:
use datastruct::DataStruct;
#[derive(DataStruct)]
struct MapItem {
#[dfield(map)] // equivalent to `#[dfield(map = true)]
item: usize,
}
// generated code
impl MapItem {
pub fn map_item(mut self, f: impl FnOnce(usize) -> usize) -> Self {
self.item = f(self.item);
self
}
}§do_with
Modify a field’s value. This does not have structure-level configuration.
Field Configuration:
#[dfield(do_with)]|#[dfield(do_with = ture): Enabledo_with.#[dfield(do_with = false)]: Disabledo_with. Typically, you don’t need to explicitly disable mapping since it’s the default behavior.
Examples:
use datastruct::DataStruct;
#[derive(DataStruct)]
struct MapItem {
#[dfield(do_with)] // equivalent to `#[dfield(do_with = true)]
item: usize,
}
// generated code
impl MapItem {
pub fn do_with_item(&mut self, f: impl FnOnce(&mut usize)) {
f(&mut self.item);
}
}§Comparison cmp
Macro-generateable comparison traits are Eq, PartialEq, Ord and PartialOrd.
Syntax:
All cmp configurations must be defined within cmp(xxx) field:
#[dstruct(cmp(<your config>))]
#[dfield(cmp(<your config>))]§Eq and PartialEq
Syntax:
#[dstruct(cmp(eq))]: GenerateEqimplementation for the struct. Note that this won’t implementPartialEq, and you must explicitly enable that.#[dfield(cmp(peq))]|#[dfield(cmp(partial_eq))]: GeneratePartialEqimplementation for the struct.
Field Configuration:
#[dfield(cmp(eq))]: When checking equality, this field is included. (Default if enabled.)#[dfield(cmp(eq = boolean))]: Whether to include this field in equality check.
Examples:
use datastruct::DataStruct;
#[derive(DataStruct)]
#[dstruct(cmp(eq, peq))]
struct CanEq {
// #[dfield(cmp(eq))]
// you don't need to explicitly specify this.
content: usize,
#[dfield(cmp(eq = false))]
do_not_check: usize,
}
// generated code
impl ::std::cmp::PartialEq for CanEq {
fn eq(&self, rhs: &Self) -> bool {
(self.content == rhs.content)
}
}
impl ::std::cmp::Eq for CanEq {}§Ord and PartialOrd
Syntax:
#[dstruct(ord)]: ImplementOrdfor the struct.#[dstruct(pord)]|#[dstruct(partial_ord)]: ImplementPartialOrdfor the struct.
Field Configuration:
Ord: The configuration key iscmporord. (Disabled by default.)-
#[dfield(cmp(ord))]: Include this field in theOrdimplementation. -
#[dfield(cmp(ord = boolean))]: Whether to include this field in theOrdimplementation. -
#[dfield(cmp(ord = "isize"))]|#[dfield(cmp(ord = isize))]: Set the sequence of the field in theOrdimplementation.By default, all included fields’ comparison results are chained with
Ordering::then_with. This configuration can change the index of the field. Negative number is allowed to use.
-
PartialOrd: The configuration key ispcmp,partial_cmp,pordorpartial_ord. (Disabled by default.)-
#[dfield(cmp(pord))]: Include this field in thePartialOrdimplementation. -
#[dfield(cmp(pord = boolean))]: Whether to include this field in thePartialOrdimplementation. -
#[dfield(cmp(pord = "isize"))]|#[dfield(cmp(pord = isize))]: Set the sequence of the field in thePartialOrdimplementation.By default, all included fields’ comparison results are chained with
Option::and_thenandOrdering::then_with. This configuration can change the index of the field. Negative number is allowed to use.
-
Note:
- If no field is configured to be included, then
OrdandPartialOrdwill not be implemented. - If both
OrdandPartialOrdare enabled:- If only
Ordis configured, thenPartialOrdwill be simplySome(Ord). - If both are configured, Clippy may throw a
clippy::non_canonical_partial_ord_impl(non-canonical implementation ofpartial_cmpon anOrdtype) warning about the implementation, see Clippy Lint for more information.
- If only
Examples:
use datastruct::DataStruct;
#[derive(DataStruct)]
#[dstruct(cmp(eq, peq, ord, pord))]
struct MyComparable {
#[dfield(cmp(ord))]
only_ord: usize,
#[dfield(cmp(pord))]
only_partial_ord: usize,
#[dfield(cmp(ord = -1, pord = -1))]
both_ord: usize,
}
// generated code (`Eq` and `PartialEq` is omitted).
impl ::std::cmp::Ord for MyComparable {
fn cmp(&self, other: &Self) -> ::std::cmp::Ordering {
self.both_ord
.cmp(&other.both_ord)
.then_with(|| self.only_ord.cmp(&other.only_ord))
}
}
impl ::std::cmp::PartialOrd for MyComparable {
fn partial_cmp(&self, other: &Self) -> ::core::option::Option<::std::cmp::Ordering> {
self.both_ord
.partial_cmp(&other.both_ord)
// the following identifier `__gen_xxx` is generated by the macro
.and_then(|__gen_dparord| {
self.only_partial_ord
.partial_cmp(&other.only_partial_ord)
.map(|__gen_dparord_self| __gen_dparord.then(__gen_dparord_self))
})
}
}§Operations ops
Macro-generateable operation traits are Add +, Sub -, Mul *, Div /
and their assignable versions AddAssign +=, SubAssign -=, MulAssign *= and DivAssign /=.
Syntax:
All ops configurations must be defined within ops(xxx) field:
#[dstruct(ops(<your config>))]
#[dfield(ops(<your config>))]Operations definitions on structure-level can be declared by
#[dstruct(ops(add = "type"))] (Take Add/AddAssign as an example):
- “both” | “all”: Generate both assignment and plain operation for all fields by default.
- “assign”: Only generate assignment by default.
- “plain” | “default”“: Generate plain operation by default. (Default if enabled.)
Field Configuration:
- Plain operations
+ - * /: (TakeAdd +as an example:)#[dfield(ops(add = "type"))]:- “inherit” | “default”: Inherit the default configuration declared in the
dstructattributes. - “ignore” | “no”: Ignore this field, that is,
A1 + A2 -> A1.
- “inherit” | “default”: Inherit the default configuration declared in the
#[dfield(ops(add = boolean))]: Whether to include this field.#[dfield(ops(add = "expression"))]: Use your own expression to implement theAdd.
- Assignment operations
+= -= *= /=: (TakeAddAssign +=as an example:)#[dfield(ops(add_assign = "type"))]:- “inherit” | “default”: Inherit the default configuration declared in the
dstructattributes. - “ignore” | “no”: Ignore this field, that is,
self.A <- self.A.
- “inherit” | “default”: Inherit the default configuration declared in the
#[dfield(ops(add_assign = boolean))]: Whether to include this field.#[dfield(ops(add_assign = "expression"))]: Use your own expression to implement theAddAssign.
About expression:
You can use your expression to manually implement the operations.
The expression must be wrapped in a literal string.
Use $self to refer to the left-hand self value, and use $rhs to refer to the right-hand other value.
For example,
#[dfield(ops(add = "$self.something + $rhs.otherthing"))]
field: SomeTypewill be translated into
Self {
field: self.something + other.otherthing,
..
}Examples:
use datastruct::DataStruct;
#[derive(Debug, Clone, Copy, DataStruct)]
#[dstruct(ops(add = "both"))]
struct CanOpsAssign {
can_add: i8,
#[dfield(ops(add = "ignore"))]
no_add: i8,
#[dfield(ops(add_assign = "std::cmp::max($self.max, $rhs.max)"))]
max: i8,
#[dfield(ops(add_assign = "std::cmp::min($self.min, $rhs.min)"))]
min: i8,
}
// generated code
impl ::std::ops::Add for CanOpsAssign {
type Output = Self;
fn add(self, rhs: Self) -> Self {
Self {
can_add: self.can_add + rhs.can_add,
no_add: self.no_add,
max: self.max + rhs.max,
min: self.min + rhs.min,
}
}
}
impl ::std::ops::AddAssign for CanOpsAssign {
fn add_assign(&mut self, rhs: Self) {
self.can_add += rhs.can_add;
self.no_add += rhs.no_add;
self.max = std::cmp::max(self.max, rhs.max);
self.min = std::cmp::min(self.min, rhs.min);
}
}Traits§
- Const
Data Struct - The macro’s
constoption will implement this trait for the structure. - Data
Struct - The macro’s
defaultoption will implement this trait for the structure.