Skip to content

Repository files navigation

generic-array-struct

An attribute proc macro to convert structs with named fields of the same generic type into a single-array-field tuple struct with array-index-based accessor and mutator methods.

MSRV

rustc 1.83.0 (stabilization of core::mem::replace() in const)

Example Usage

use generic_array_struct::generic_array_struct;#[generic_array_struct]#[derive(Debug,Default,Clone,Copy,PartialEq,Eq,Hash)]#[repr(transparent)]pubstructCartesian<T>{/// x-coordinatepubx:T,/// y-coordinatepuby:T,}

expands to

#[derive(Debug,Default,Clone,Copy,PartialEq,Eq,Hash)]#[repr(transparent)]pubstructCartesian<T>([T;CARTESIAN_LEN]);impl<T>Cartesian<T>{/// x-coordinate#[inline]pubconstfnx(&self) -> &T{&self.0[CARTESIAN_IDX_X]}#[inline]pubconstfnx_mut(&mutself) -> &mutT{&mutself.0[CARTESIAN_IDX_X]}/// Returns the old field value#[inline]pubconstfnset_x(&mutself,val:T) -> T{
core::mem::replace(&mutself.0[CARTESIAN_IDX_X], val)}#[inline]pubfnwith_x(mutself,val:T) -> Self{self.0[CARTESIAN_IDX_X] = val;self}/// y-coordinate#[inline]pubconstfny(&self) -> &T{&self.0[CARTESIAN_IDX_Y]}#[inline]pubconstfny_mut(&mutself) -> &mutT{&mutself.0[CARTESIAN_IDX_Y]}/// Returns the old field value#[inline]pubconstfnset_y(&mutself,val:T) -> T{
core::mem::replace(&mutself.0[CARTESIAN_IDX_Y], val)}#[inline]pubfnwith_y(mutself,val:T) -> Self{self.0[CARTESIAN_IDX_Y] = val;self}}impl<T:Copy>Cartesian<T>{#[inline]pubconstfnconst_with_x(mutself,val:T) -> Self{self.0[CARTESIAN_IDX_X] = val;self}#[inline]pubconstfnconst_with_y(mutself,val:T) -> Self{self.0[CARTESIAN_IDX_Y] = val;self}}impl<T>Cartesian<T>{pubconstLEN:usize = 2;pubconstIDX_X:usize = 0;pubconstIDX_Y:usize = 1;}// consts are also exported with prefix (not just as associated consts)// so that we dont need turbofish e.g. `Cartesian::<f32>::IDX_X`pubconstCARTESIAN_LEN:usize = 2;pubconstCARTESIAN_IDX_X:usize = 0;pubconstCARTESIAN_IDX_Y:usize = 1;

Usage Notes

Declaration Order

Because this attribute modifies the struct definition, it must be placed above any derive attributes or attributes that use the struct definition

WRONG ❌

use generic_array_struct::generic_array_struct;// Fails to compile because #[generic_array_struct] is below #[derive] attribute#[derive(Debug,Default,Clone,Copy,PartialEq,Eq,Hash)]#[generic_array_struct]pubstructCartesian<D>{pubx:D,puby:D,}

RIGHT ✅

use generic_array_struct::generic_array_struct;#[generic_array_struct]#[derive(Debug,Default,Clone,Copy,PartialEq,Eq,Hash)]pubstructCartesian<D>{pubx:D,puby:D,}

Field Visibility

All methods have the same visibility as that of the originally declared field in the struct.

mod private {use generic_array_struct::generic_array_struct;#[generic_array_struct]#[derive(Debug,Default,Clone,Copy,PartialEq,Eq,Hash)]pubstructCartesian<T>{// Note: fields are privatex:T,y:T,}}use private::Cartesian;// fails to compile because [`Cartesian::const_with_x`] is privateconstONE_COMMA_ZERO:Cartesian<f64> = Cartesian([0.0;2]).const_with_x(1.0);

Attribute args

The attribute can be further customized by the following space-separated positional args.

destr Arg

An optional destr prefix arg controls whether to output the original struct definition as a separate struct for destructuring.

use generic_array_struct::generic_array_struct;#[generic_array_struct(destr pub)]#[derive(Debug,Default,Clone,Copy,PartialEq,Eq,Hash)]#[repr(transparent)]pubstructCartesian<Z>{pubx:Z,puby:Z,}

expands to

use generic_array_struct::generic_array_struct;#[generic_array_struct(pub)]#[derive(Debug,Default,Clone,Copy,PartialEq,Eq,Hash)]#[repr(transparent)]pubstructCartesian<Z>{pubx:Z,puby:Z,}#[derive(Debug,Default,Clone,Copy,PartialEq,Eq,Hash)]pubstructCartesianDestr<Z>{pubx:Z,puby:Z,}impl<T>Cartesian<T>{#[inline]pubfnfrom_destr(CartesianDestr{ x, y,}:CartesianDestr<T>) -> Self{Self([x, y,])}#[inline]pubfninto_destr(self) -> CartesianDestr<T>{letSelf([x, y,]) = self;CartesianDestr{ x, y,}}}impl<T:Copy>Cartesian<T>{#[inline]pubconstfnconst_from_destr(CartesianDestr{ x, y,}:CartesianDestr<T>) -> Self{Self([x, y,])}#[inline]pubconstfnconst_into_destr(self) -> CartesianDestr<T>{letSelf([x, y,]) = self;CartesianDestr{ x, y,}}}impl<T>From<CartesianDestr<T>>forCartesian<T>{#[inline]fnfrom(d:CartesianDestr<T>) -> Self{Self::from_destr(d)}}impl<T>From<Cartesian<T>>forCartesianDestr<T>{#[inline]fnfrom(d:Cartesian<T>) -> Self{
d.into_destr()}}

builder Arg

An optional builder prefix arg controls whether to generate a builder struct that, at compile-time, ensures that every field is set exactly once before creating the struct.

use generic_array_struct::generic_array_struct;#[generic_array_struct(builder pub)]pubstructCartesian<Z>{pubx:Z,puby:Z,}

expands to

use generic_array_struct::generic_array_struct;#[generic_array_struct(pub)]pubstructCartesian<Z>{pubx:Z,puby:Z,}// The const generic booleans track which fields have been set#[repr(transparent)]pubstructCartesianBuilder<Z,constS0:bool,constS1:bool>([core::mem::MaybeUninit<Z>;CARTESIAN_LEN]);pubtypeNewCartesianBuilder<Z> = CartesianBuilder<Z,false,false>;impl<T>NewCartesianBuilder<T>{// impl notes:// need to specify as associated const instead of fn local const, otherwise errors with// 'can't use generic parameters from outer item'const _UNINIT: core::mem::MaybeUninit<T> = core::mem::MaybeUninit::uninit();#[inline]pubconstfnstart() -> Self{Self([Self::_UNINIT;CARTESIAN_LEN])}}// impl notes:// - cannot use transmute() due to const generic, cannot move out of struct due to Drop.// Hopefully rustc is able to optimize away all the // transmute_copy() + core::mem::forget()s and use the same memory.// I cannot wait for array transmutes to be stabilized.impl<Z,constS1:bool>CartesianBuilder<Z,false,S1>{#[inline]pubconstfnwith_x(mutself,val:Z,) -> CartesianBuilder<Z,true,S1>{self.0[CARTESIAN_IDX_X] = core::mem::MaybeUninit::new(val);unsafe{
core::mem::transmute_copy::<_,_>(&core::mem::ManuallyDrop::new(self))}}}impl<Z,constS0:bool>CartesianBuilder<Z,S0,false>{#[inline]pubconstfnwith_y(mutself,val:Z,) -> CartesianBuilder<Z,S0,true>{self.0[CARTESIAN_IDX_Y] = core::mem::MaybeUninit::new(val);unsafe{
core::mem::transmute_copy::<_,_>(&core::mem::ManuallyDrop::new(self))}}}impl<Z>CartesianBuilder<Z,true,true>{#[inline]pubconstfnbuild(self) -> Cartesian<Z>{// if not `repr(transparent)`, must use `self.0` instead of `self`,// but we always enforce repr(transparent)unsafe{Cartesian(
core::mem::transmute_copy::<_,_>(&core::mem::ManuallyDrop::new(self)))}}}/// This gets called if the Builder struct was dropped before `self.build()` was calledimpl<Z,constS0:bool,constS1:bool>DropforCartesianBuilder<Z,S0,S1>{fndrop(&mutself){ifS0{unsafe{self.0[CARTESIAN_IDX_X].assume_init_drop();}}ifS1{unsafe{self.0[CARTESIAN_IDX_Y].assume_init_drop();}}}}impl<Z,constS0:bool,constS1:bool>CloneforCartesianBuilder<Z,S0,S1>whereZ:Copy{#[inline]fnclone(&self) -> Self{Self(self.0)}}
Example Builder Usages
Attempting to build before setting all fields
use generic_array_struct::generic_array_struct;#[generic_array_struct(builder)]pubstructCartesian<T>{pubx:T,puby:T,}// y has not been set, this fails to compile with// "method not found in `CartesianBuilder<{integer}, true, false>`"let pt:Cartesian<u8> = NewCartesianBuilder::start().with_x(1).build();
Attempting to set a field twice
use generic_array_struct::generic_array_struct;#[generic_array_struct(builder pub)]pubstructCartesian<T>{pubx:T,puby:T,}// attempted to set x twice, this fails to compile with// "no method named `with_x` found for struct `CartesianBuilder<{integer}, true, true>` in the current scope"let pt:Cartesian<u8> = NewCartesianBuilder::start().with_x(1).with_y(0).with_x(2).build();
Proper initialization
use generic_array_struct::generic_array_struct;#[generic_array_struct(builder pub(crate))]pubstructCartesian<T>{pubx:T,puby:T,}// proper initialization after setting all fields exactly oncelet pt:Cartesian<u8> = NewCartesianBuilder::start().with_x(1).with_y(0).build();

trymap Arg

An optional trymap prefix arg controls whether to generate 2 util methods, try_map_opt and try_map_res for the struct.

use generic_array_struct::generic_array_struct;#[generic_array_struct(trymap)]pubstructCartesian<Z>{pubx:Z,puby:Z,}

expands to

use generic_array_struct::generic_array_struct;#[generic_array_struct]pubstructCartesian<Z>{pubx:Z,puby:Z,}// impl notes:// - cannot use transmute() due to const generic, cannot move out of struct due to Drop.// Hopefully rustc is able to optimize away all the // transmute_copy() + core::mem::forget()s and use the same memory.// I cannot wait for array transmutes to be stabilized.// - generate 2 separate methods instead of using `Try` trait so that its compatible// with stable rustimpl<T>Cartesian<T>{#[inline]pubfntry_map_opt<B,F>(self,mutf:F,) -> Option<Cartesian<B>>whereF:FnMut(T) -> Option<B>{letmut res:Cartesian<core::mem::MaybeUninit<B>>
= Cartesian(core::array::from_fn(|_| core::mem::MaybeUninit::uninit()));let written = self.0.into_iter().zip(res.0.iter_mut()).try_fold(0usize,
|written,(val, rmut)| {
rmut.write(f(val).ok_or(written)?);Ok(written + 1)});match written {Ok(_) => Some(Cartesian(unsafe{
core::mem::transmute_copy::<_,_>(&core::mem::ManuallyDrop::new(res.0))})),Err(written) => {
res.0.iter_mut().take(written).for_each(
|mu| unsafe{ mu.assume_init_drop()});None}}}#[inline]pubfntry_map_res<B,E,F>(self,mutf:F,) -> Result<Cartesian<B>,E>whereF:FnMut(T) -> Result<B,E>{letmut res:Cartesian<core::mem::MaybeUninit<B>>
= Cartesian(core::array::from_fn(|_| core::mem::MaybeUninit::uninit()));let written = self.0.into_iter().zip(res.0.iter_mut()).try_fold(0usize,
|written,(val, rmut)| {
rmut.write(f(val).map_err(|e| (e, written))?);Ok(written + 1)});match written {Ok(_) => Ok(Cartesian(unsafe{
core::mem::transmute_copy::<_,_>(&core::mem::ManuallyDrop::new(res.0))})),Err((e, written)) => {
res.0.iter_mut().take(written).for_each(
|mu| unsafe{ mu.assume_init_drop()});Err(e)}}}}

zip Arg

An optional zip prefix arg controls whether to generate the un/zip util methods.

use generic_array_struct::generic_array_struct;#[generic_array_struct(zip)]pubstructCartesian<Z>{pubx:Z,puby:Z,}

expands to

use generic_array_struct::generic_array_struct;#[generic_array_struct]pubstructCartesian<Z>{pubx:Z,puby:Z,}impl<T>Cartesian<T>{#[inline]pubfnzip<U>(self,Cartesian([u0, u1]):Cartesian<U>) -> Cartesian<(T,U)>{letSelf([t0, t1]) = self;Cartesian([(t0, u0),(t1, u1)])}}impl<T:Copy>Cartesian<T>{#[inline]pubconstfnconst_zip<U:Copy>(self,Cartesian([u0, u1]):Cartesian<U>) -> Cartesian<(T,U)>{letSelf([t0, t1]) = self;Cartesian([(t0, u0),(t1, u1)])}}impl<T,U>Cartesian<(T,U)>{#[inline]pubfnunzip(self) -> (Cartesian<T>,Cartesian<U>){letSelf([(t0, u0),(t1, u1)]) = self;(Cartesian([t0, t1]),Cartesian([u0, u1]))}}impl<T:Copy,U:Copy>Cartesian<(T,U)>{#[inline]pubconstfnconst_unzip(self) -> (Cartesian<T>,Cartesian<U>){letSelf([(t0, u0),(t1, u1)]) = self;(Cartesian([t0, t1]),Cartesian([u0, u1]))}}

all Arg

Instead of specifying each individual optional prefix arg, a single all arg can be specified to enable all of the above.

use generic_array_struct::generic_array_struct;#[generic_array_struct(all)]pubstructCartesian<Z>{pubx:Z,puby:Z,}

is equivalent to

use generic_array_struct::generic_array_struct;#[generic_array_struct(builder destr trymap zip)]pubstructCartesian<Z>{pubx:Z,puby:Z,}

.0 Visibility Attribute Arg

The attribute's final position arg is a syn::Visibility that controls the visibility of the resulting .0 array field.

use generic_array_struct::generic_array_struct;#[generic_array_struct]pubstructCartesian<T>{pubx:T,puby:T,}

generates

pubstructCartesian<T>([T;2]);

while

use generic_array_struct::generic_array_struct;#[generic_array_struct(pub(crate))]pubstructCartesian<T>{pubx:T,puby:T,}

generates

pubstructCartesian<T>(pub(crate)[T;2]);

About

An attribute proc macro to convert structs with named fields of the same generic type into a single-array-field tuple struct with array-index-based accessor and mutator methods

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages