derived: Automating the boring stuff
Background
There are struct
s. Then there are huge struct
s. And then there are enormous struct
s. Last week,
I was working on simplifying some structures at Skytable when
I was really annoyed with a struct
.
No, let me not leave you there. It looked like this (module shown for a reason):
mod cfgset {
struct ConfigKeys {
tls_cert: String,
tls_key: String,
tls_passin_stream: String,
// ... some large number of other fields ...
}
}
The problem:
- I didn’t want to make the fields public.
This means that instantiation like below was not a choice:
let something = ConfigKeys { ... };
- I was too lazy to write a constructor (which would have ended up like:
pub fn new(tls_cert: String, ...)
)
Do note that my use of the word constructor is strictly in the Rust context. As in
$struct::new()
The solution
The solution to my indolence was a derive macro; and I decided to call it derived. The macro automates the generation of some boring things, for example the constructor generation case illustrated above. With this macro, all you need to do is:
use derived::Ctor;
#[derive(Ctor)]
struct ConfigKeys {
tls_cert: String,
tls_key: String,
tls_passin_stream: String,
// ... some large number of other fields ...
}
And now, I can simply call:
let mykeys = ConfigKeys::new("/path/to/cert".to_owned(), ...);
Behind the scenes, the Ctor
macro will generate a constructor like:
pub fn new(
tls_cert: String,
tls_key: String,
...
) -> ConfigKeys {
Self {
tls_cert,
tls_key,
..
}
}
Another problem appears
Now, since the fields are private – there are instances when I’d need to access the fields,
but again, I did not want to make the fields public! To solve this, we got ourselves another
derive macro which I call Gtor
(dubbed after getter
and ctor
).
This suddenly reminded me of the getter and setter days (Java go brrr)
With the Gtor
macro, this is all I needed to do:
#[derive(Gtor, Ctor)]
pub struct MyStruct {
cert: String,
port: u16,
}
let x = MyStruct::new("cert.pem".to_owned(), 2003);
assert_eq!(x.get_port(), 2003);
assert_eq!(x.get_cert(), "cert.pem");
Woot, the Gtor
macro will generate get_*
for every field in the struct! Behind the scenes,
the macro will generate methods like:
/// Returns the value for the `port` field in struct [`ConfigKeys`]
pub fn get_port(&self) -> u16 {
self.port
}
/// Returns the value for the `cert` field in struct [`ConfigKeys`]
pub fn get_cert(&self) -> &String {
&self.cert
}
/// ...
Wait, what?: But wait, what happened when we ran
get_port()
? Well, theGtor
macro is smart enough to realize thatu16
is aCopy
type and automatically returns a copy instead of a reference. While for theget_cert()
, it will return a reference.
Thinking about setters?
Yup, that’s there too! With the Stor
derive macro. Simply add #[derive(Stor)]
and you’re good
to go! Also, the macro will generate doc-comments for you (read more here).
The work ahead
The derived macro crate doesn’t end with ctors, gtors and stors (there are some amazing crates that already do that :D)! I have plans to add several other macros to reduce some of the redundant work in codebases. If you have an idea, just drop it here and we can get to work!