Reputation: 679
I want to implement a struct using macro_rules!
because the generics require a lot of boilerplate and trait hunting.
The struct in question has a hash table inside but the key and the value types are to be provided by the user. The code is as follows:
macro_rules! new_ytz {
($T: ty) => {
// define the struct
pub struct Ytz {
table: hashbrown::hash_map::HashMap<$T, $T>,
}
impl Ytz {
pub fn new() -> Self {
Ytz {
table: hashbrown::hash_map::HashMap::<$T, $T>::new(),
}
}
pub fn add(&mut self, item: &$T) {
if self.table.contains_key(item) {
*self.table.get_mut(item).unwrap() += *item;
} else {
self.table.insert(*item, *item);
}
}
pub fn largest(&self) -> $T {
let mut result = 0;
for v in self.table.values() {
if result < *v {
result = *v;
}
}
result
}
}
// construct an instance of the struct and return it
Ytz::new()
};
}
// driver
fn main() {
let mut y = new_ytz!(u64); // should construct the object and return Ytz::new()
y.add(&71);
y.add(&25);
y.add(&25);
y.add(&25);
y.add(&34);
println!("{}", y.largest());
}
This won't compile since it tries to paste the struct within the main function:
error: expected expression, found keyword `pub`
--> src/main.rs:4:9
|
4 | pub struct Ytz {
| ^^^ expected expression
...
40 | let mut y = new_ytz!(u64); // should construct the object and return Ytz::new()
| ------------- in this macro invocation
How can I work around it? How can I paste the struct outside the main function publicly, along with the impl
block?
Upvotes: 1
Views: 554
Reputation: 1742
generics require a lot of boilerplate
use std::collections::HashMap;
use core::hash::Hash;
use std::ops::AddAssign;
struct YtzU64<T: Eq + Ord + Hash + Copy + AddAssign> {
table: HashMap<T, T>
}
impl<T: Eq + Ord + Hash + Copy + AddAssign> YtzU64<T> {
pub fn new() -> Self {
Self {
table: HashMap::new()
}
}
pub fn add(&mut self, item: &T) {
if let Some(item) = self.table.get_mut(item) {
*item += *item;
} else {
self.table.insert(*item, *item);
}
}
pub fn largest(&self) -> Option<T> {
let mut values = self.table.values();
let mut largest:Option<T> = values.next().map(|t| *t);
for v in values {
if largest < Some(*v) {
largest = Some(*v);
}
}
largest
}
}
fn main() {
let mut y = YtzU64::new();
y.add(&71);
y.add(&25);
y.add(&25);
y.add(&25);
y.add(&34);
println!("{}", y.largest().unwrap());
}
My translation of your macro requires less boilerplate than your macro. It has two fewer indents, 4 fewer lines (macro_rules!, pattern matching at the top, two close braces at the end). Note that I changed the api slightly, as largest
now returns an Option
, to match std::iter::Iterator::max()
. Also note that your api design is limited to T:Copy
. You would have to redesign it a little if you want to support T: ?Copy + Clone
or T: ?Copy + ?Clone
.
trait hunting
The compiler is your friend. Watch what happens when I remove one of the trait bounds
error[E0277]: the trait bound `T: std::hash::Hash` is not satisfied
...
Using a macro is an interesting exercise, but re-implementing generics using macros is not useful.
Upvotes: 1