I am new to Rust + procedural macro's — apologies because I don't know what I am doing
Background:
I am using the rustdds
crate to implement DDS. I have created extensive wrapper code to customize these domain participants to talk on custom topics (CustomTopic
), using enums. Each arm of the enum is a topic that has an associated DataReader
+ DataWriter
, both of a custom serializable datatype. To do this, I use a HashMap: HashMap<CustomTopic, DataReader<dyn Any>>
(one for DataReader
, one for DataWriter
)
The problem with using dyn Any
or any custom trait that implements serializability, is the compiler does not like this because size of the datatype is not known during compilation time.
Work-around: I used enums in the following way:
pub enum CustomDataReader = {
String(DataReader(String)),
MyCustomDatatype(DataReader(MyCustomDatatype)), // MyCustomDatatype is serializable
}
// and then
data_readers_hashmap = HashMap<CustomTopic, CustomDataReader>
and this works perfectly. I can now access my data readers / writers using the hash map, AND I can access them using a match:
match datareader {
CustomDataReader::String => {
// datareader is of type DataReader<String>
},
CustomDataReader::MyCustomDatatype => {
// datareader is of type DataReader<MyCustomDatatype>
},
}
Now this fits my application PERFECTLY. The downside, there is a lot of boilerplate (a lot which I have not shown) so I started looking into procedural macros.
Example:
Ideally, I would just create a struct, and slap #[derive(CustomDatatype)]
above a struct as follows:
#[derive(CustomDatatype)]
struct Car {
make: String,
model: String,
num_wheels: u8,
}
#[derive(CustomDatatype)]
struct Building {
name: String,
location: (f32, f32),
}
struct Duck {
color: String,
}
pub enum CustomDataReader {}
pub enum CustomDataWriter {}
And the macro would add these elements as arms to those two enums:
...
pub enum CustomDataReader {
Car(DataReader(Car)),
Building(DataReader(Building)),
}
pub enum CustomDataWriter {
Car(DataWriter(Car)),
Building(DataWriter(Building)),
}
Now I can access these data readers / writers using CustomDataReader::Car
and CustomDataReader::Building
Failed attempt:
I've followed all the steps in making a procedural macro, and I am starting to wrap my head around it. My only issue is, I am unable to actually "append" to an existing enum. I can go ahead and make new enums using quote!
just fine
#[proc_macro_derive(CustomDatatype)]
pub fn custom_datatype_derive(input: TokenStream) -> TokenStream {
let input: DeriveInput = parse_macro_input!(input);
let struct_name = &input.ident;
let output = quote! {
#input
pub enum CustomDataReader {
#struct_name(rustdds::no_key::DataReader<#struct_name>),
}
pub enum CustomDataWriter {
#struct_name(rustdds::no_key::DataWriter<#struct_name>),
}
}
output.into()
}
But the problem with this, is it generates code likes this:
// first
pub enum CustomDataReader {
Car(DataReader(Car)),
}
pub enum CustomDataWriter {
Car(DataWriter(Car)),
}
// then
pub enum CustomDataReader {
Building(DataReader(Building)),
}
pub enum CustomDataWriter {
Building(DataWriter(Building)),
}
Which is simply not the functionality I want. And I have been stuck on this for the past couple of weeks and have looked into other macro types. Not sure where to go from here. Any help or general advice is appreciated
EDIT: Response to comment recommended using generics: I have a trait that implements the serde::Serialize
and serde::Deseralize
and used it to create the exact struct you're talking about:
pub trait DDS_Msg: Serialize + for<'a> Deserialize<'a> + fmt::Display + Clone {}
struct CustomDatatype {};
impl DDS_Msg for CustomDatatype {}
pub struct Messenger {
pub reader: Box<DataReader<dyn DDS_Msg>>,
pub writer: Box<DataWriter<dyn DDS_Msg>>,
}
But then I get the following stack trace:
error[E0277]: the size for values of type `(dyn ddstypes::DDS_Msg + 'static)` cannot be known at compilation time
--> src/utils/ddstypes.rs:123:17
|
123 | pub reader: Box<DataReader<dyn DDS_Msg>>,
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time
|
= help: the trait `Sized` is not implemented for `(dyn ddstypes::DDS_Msg + 'static)`
note: required by a bound in `rustdds::no_key::DataReader`
--> /home/user/.cargo/registry/src/github.com/rustdds-0.7.11/src/dds/no_key/datareader.rs:49:23
|
49 | pub struct DataReader<D: DeserializeOwned, DA: DeserializerAdapter<D> = CDRDeserializerAdapter<D>> {
| ^ required by this bound in `rustdds::no_key::DataReader`
So since I need to predefine my datatypes during compilation time, a statically typed enum does the trick