Crate Development - System Module II
Product topology
In the previous post, I defined the topology in Continuous system, Lattice system, and Network system for the basic structure of the System module, and wanted to implement the topology trait for product topology.
The simplest way is to define the trait of tuple using generic type.
impl<P1 : Point, P2 : Point> Point for (P1, P2){}
impl<P1, T1, P2, T2> Topology<(P1, P2)> for (T1, T2)
where P1 : Point,
T1 : Topology<P1>,
P2 : Point,
T2 : Topology<P2>{
fn check_move(&self, pos : &(P1, P2), movement : &(P1, P2)) -> bool{
return self.0.check_move(&pos.0, &movement.0) || self.1.check_move(&pos.1, &movement.1);
}
}
However, there is a problem with this approach. Tuple structs are not covered by tuple.
struct Lane(Cartessian1D<i32>, Cartessian1D<f64>);
struct LaneTopology(LatticeTopology, ContinuousTopology);
// (define topology variable and positions);
sys.check_move(&point, &movement); // Cannot compile because check_move is not developed
In the end, we need to write new trait implementation code every time we define a new tuple struct, which is inconvenient. Since tuple structs are essentially the same as tuples, it is better to simplify this using macros.
Derive macros
The thing that can be used in this case is derive macro. If you are using rust code, you have probably written the following code when you define a new struct.
#[derive(Copy, Clone, Debug, PartialOrd, Hash, PartialEq)]
struct NewStruct{
...
}
Here, #[derive(...)] is the derive macro. It automatically implements traits such as copy and clone for the struct. This has already been used in another crate.
Let’s use it to implement fancy trait implementation.
extern crate proc_macro;
use proc_macro::TokenStream;
use proc_quote::{quote, quote_spanned};
use syn::spanned::Spanned;
use syn::{parse_macro_input, DeriveInput, Data, Fields, Index};
/// Derive macros autocomplete the trait Point.
///
/// In MoldyBrody, there is a Point trait, which is a macro that automatically configures it.
#[proc_macro_derive(Point)]
pub fn derive_point(item: TokenStream) -> TokenStream {
let x = syn::parse_macro_input!(item as DeriveInput);
let name = x.ident;
let tokens = proc_quote::quote!{
impl Point for #name {}
};
tokens.into()
}
/// Derive macros autocomplete the trait Topology.
///
/// This macro autocompletes the Topology trait.
/// The Topology trait requires a corresponding Point generic type,
/// so it receives a PointName attribute.
#[proc_macro_derive(Topology, attributes(PointName))]
pub fn derive_topology(item: TokenStream) -> TokenStream {
let x = parse_macro_input!(item as DeriveInput);
let name = x.ident;
let point_name : syn::Ident = x.attrs[0].parse_args().unwrap();
let tokens = match x.data{
Data::Struct(ref data) => {
match data.fields{
Fields::Unnamed(ref fields) =>{
let recurse = fields.unnamed.iter().enumerate().map(|(i, f)| {
let index = Index::from(i);
quote_spanned! {f.span()=>
self.#index.check_move(&pos.#index, &movement.#index)
}
});
quote! {
impl Topology<#point_name> for #name{
fn check_move(&self, pos : &#point_name, movement : &#point_name) -> bool{
false #(|| #recurse)*
}
}
}
},
_ => unimplemented!(),
}
},
Data::Enum(_) |
Data::Union(_) => unimplemented!(),
};
// panic!("{}", tokens.to_string());
tokens.into()
}
By creating a procedural macro like this, we can write the following code.
#[derive(Point)]
pub struct Lane(Cartessian1D<i32>, Cartessian1D<f64>);
#[derive(Topology)]
#[PointName(Lane)]
pub struct LaneTopology(LatticeTopology, ContinuousTopology);
One thing that is a bit inconvenient is that derive macro cannot give attributes inside it, so we need to give the PointName attribute separately, and it is not perfect in terms of usability.