Deriving the implementation of trait for structs

#1

I am working on a network simulator and I would like to have detailed control about how many memory it is presently consuming, and also to forecast maximum usage. With that aim I defined the following trait.

pub trait Quantifiable
{
	/// Get the total memory currently being employed by the implementing type. Both stack and heap.
	fn total_memory(&self) -> usize;
	/// Prints by stdout how much memory is used per component.
	fn print_memory_breakdown(&self);
	/// Get an estimation on how much memory the type could reach during the simulation.
	fn forecast_total_memory(&self) -> usize;
}

Implementing this trait is straighforward in most structs.

struct Example
{
	a: Alpha,
	b: Beta,
}
impl Quantifiable for Example
{
	fn total_memory(&self) -> usize
	{
		self.a.total_memory() + self.b.total_memory()
	}
	fn print_memory_breakdown(&self)
	{
		println!("a total : {}",quantify::human_bytes(self.a.total_memory()));
		println!("b total : {}",quantify::human_bytes(self.b.total_memory()));
	}
	fn forecast_total_memory(&self) -> usize;
	{
		self.a.forecast_memory() + self.b.forecast_memory()
	}
}

I need to make this kind of implementation for a lot of structs, so it feels natural to to try to employ a derive annotation. However I am very lost in respect to implementing the macro. I know that I have to use syn::DeriveInput to get the fields of the struct and somehow build a TokenStream. There is also a derive_more crate that does similar things, but I am not understanding exactly its implementation.

So I ask for some advice on how to build this #[derive(Quantifiable)] macro.

#2

Maybe you could copy and tweak the heapsize crate?

1 Like
#3

That crate is very similar to what I have already done. It defines a macro known_heap_size to set constant values to selected types. But I do not see there any implementation of derive macros.

#4

Looks like there is a derive implementation, it just isn’t mentioned in the docs.

https://crates.io/crates/heapsize_derive

2 Likes
#5

Thank you, that is exactly what I was looking for. I should be able to adapt it. Also, reading the documentation of synstructure::each_field, it seems that it should work for complex enums in addition to structs, which is nice.

#6

This feels a little stupid, but I am having cargo-level problems. I have this error when compiling.

error: the `#[proc_macro_derive]` attribute is only usable with crates of the `proc-macro` crate type

The heapsize_derive crate has in its toml

[lib]
path = "lib.rs"
proc-macro = true

But I am building a binary, not a library. I tried to

[[bin]]
name = "simulator"
path = "src/main.rs"
proc-macro = true

but it gives the same error as above. Is it possible to use the proc_macro_derive in a binary? Otherwise, I guess I should move that part into its own crate.

#7

Proc macros have to be compiled before the rest of the code is compiled. To accomplish this, proc macros need to live in their own crate. So you should structure your app something like this:

+-Cargo.toml
|  [dependencies]
|  quantifiable-derive = { path = "./quantifiable-derive" }
|
+-src
| \-main.rs
|   use quantifiable_derive::Quantifiable;
|   #[derive(Debug, Quantifiable)]
|   struct Example {a: Alpha, b: Beta }
|
\-quantifiable-derive
  +-Cargo.toml
  | [lib]
  | proc-macro = true
  |
  \-src
    \-lib.rs
      #[proc_macro_derive]
      ...

(Untested code. Caveat lector.)

1 Like
#8

There’s also a section in the book addressing specifically this issue, called How to Write a Custom Derive Macro. You should give it a read.

#9

It is working now. Thanks to all of you.

1 Like