6 useful Rust macros that you might not have seen beforeBen McDonaldBlockedUnblockFollowFollowingApr 21Below are 6 Rust macros that are worth taking a look at to improve your project.
Rust macros are a great feature that can reduce code boilerplate and be a time saver for programmers.
They also offer flexibility for developers to use metaprogramming to add new features to the language and package them in a way that is easy to integrate into code.
They are one of the more powerful features of the language and this led me to search github and cargo to see what was out there.
Below are some interest macros that are not as well known1) Log-derive — Result logginghttps://crates.
io/crates/log-derive#[logfn(ok = "TRACE", Err = "Error", fmt = "Failed fetching json: {:?}")]fn fetch(url: &str) -> reqwest::Result<reqwest::Response> { reqwest::get(url)}Here is a macro to log errors from a function.
Add this attribute to a function returning a Result and with a single line you will enable logging of errors.
Customise the log level and the message logged attached to both the Err or the Ok enum variants.
The code generated makes use of the log crate https://crates.
io/crates/log.
2) Recap — regex parsinghttps://crates.
io/crates/recap#[recap(regex = r#"(?x) (?P<foo>d+) s+ (?P<bar>true|false) s+ (?P<baz>S+) "#)]struct LogEntry { foo: usize, bar: bool, baz: String,}let entry: LogEntry = "1 true hello".
parse()?;Recap is an easy way to build data from regex strings.
Each attribute of the struct matches a group in a regex string that is defined in the attribute tag.
The macro then derives a FromStr trait that will populate the struct with data once the parse method is called on a string.
3) Shrinkwraprs — generate distinct typeshttps://crates.
io/crates/shrinkwraprs#[derive(Shrinkwrap)]struct Email(String);let email = Email("chiya+snacks@natsumeya.
jp".
into());let is_discriminated_email = email.
contains("+"); // Woohoo, we can use the email like a string!Shrinkwraprs redefines a datatype as a new distinct type.
You can add the Shrinkwrap attribute to inherit all the behaviour of the embedded datatype.
Useful if you want to use the power of the type checker to ensure the correctness of your code.
Use this crate to create variants of a library type that have the same behaviour and data but are distinct types.
4) Meteredhttps://crates.
io/crates/meteredThis macro will automatically generate the following stats on a methodHitCount: number of times a piece of code was calledErrorCount: number of errors returned – (works on any expression returning a Result)InFlight: number of requests activeResponseTime: statistics on the duration of an expressionThroughput: how many times an expression is called per second.
#[derive(Default, Debug, serde::Serialize)]pub struct Biz { metrics: BizMetrics,}#[metered(registry = BizMetrics)]impl Biz { #[measure([HitCount, Throughput])] pub fn biz(&self) { let delay = std::time::Duration::from_millis(rand::random::<u64>() % 200); std::thread::sleep(delay); } }In the struct you will add a metrics attribute and a new metrics type will be generated by the macro.
For example:metrics: BizMetrics,Then on the impl the name of the attribute must match the metrics type#[metered(registry = BizMetrics)]Retrieve the metrics as serialised yaml:serde_yaml::to_string(&*biz).
unwrap();metrics: biz: hit_count: 1000 throughput: – samples: 20 min: 35 max: 58 mean: 49.
75 stdev: 5.
146600819958742 90%ile: 55 95%ile: 55 99%ile: 58 99.
9%ile: 58 99.
99%ile: 58 – ~5) Derive-new — generate constructorshttps://github.
com/nrc/derive-new#[derive(new)]struct Foo { x: bool, #[new(value = "42")] y: i32, #[new(default)] z: Vec<String>,}let _ = Foo::new(true);Derive-new is a simple way to add a constructor to a struct.
This method will add an impl fn new(.
) -> Self method generated from the structs attributes that will initialise the struct.
6) Snafu — error managementhttps://crates.
io/crates/snafuThis crate has many helper functions for dealing with errors in Rust.
Dealing with error can be verbose in Rust and having a library to reduce code and increase readable is something you may be seeking after starting a project.
It has functions for generating Fromtraits for Errors to be used with the try operator (?), easy methods for embedding data related to the errors into the error struct, and helpers for generating display error messages.
This is a well-designed error crate including much of the functionality a developer will need for error management in Rust.
#[derive(Debug, Snafu)]enum Error { #[snafu(display("Could not open config from {}: {}", filename.
display(), source))] OpenConfig { filename: PathBuf, source: std::io::Error }, #[snafu(display("Could not save config to {}: {}", filename.
display(), source))] SaveConfig { filename: PathBuf, source: std::io::Error }, #[snafu(display("The user id {} is invalid", user_id))] UserIdInvalid { user_id: i32, backtrace: Backtrace },}type Result<T, E = Error> = std::result::Result<T, E>;fn log_in_user<P>(config_root: P, user_id: i32) -> Result<bool>where P: AsRef<Path>,{ let config_root = config_root.
as_ref(); let filename = &config_root.
join("config.
toml"); let config = fs::read(filename).
context(OpenConfig { filename })?; // Perform updates to config fs::write(filename, config).
context(SaveConfig { filename })?; ensure!(user_id == 42, UserIdInvalid { user_id }); Ok(true)}The content method is used to embed relevant information into the error.
If the enum has a source field, its context selector will have an implementation of From for a Context.
This allows a developer to use the try operator (?) and the type will be converted into a Result enum.
fs::write(filename, config).
context(SaveConfig { filename })?Otherwise, the context selector will have a method fail to create a Result enum.
LoadNextPage { page_number }.
fail() About meI’m a freelance developer from New Zealand.
I spend my time teaching people about bitcoin or coding Rust.
Find me on twitter https://twitter.
com/BenMcDonald___.