Rust macro accept call to member function - rust

I'm trying to replace a bunch of snippets like
if let Some(thing) = some_t {
thing.doSomething();
}
with a macro, resulting in something like
if_some(some_t, doSomething());
so basically replacing
if_some(/* option */, /* member function call(s) */);
with
if let Some(thing) = /* option */ {
thing./* member function call(s) */
}
The attempt below does however not seem to work (the compiler reports "error: unexpected token: doSomething()", which does not help me in understanding what is wrong here. Anyone has an idea on how to handle this?
#[macro_export]
macro_rules! if_some {
( $option:ident, $function:expr ) => {{
if let Some(thing) = $option {
thing.$function
}
}};
}
struct Thing {}
impl Thing {
fn doSomething(&self) {}
}
fn main() {
let some_t = Some(Thing {});
if let Some(thing) = some_t {
thing.doSomething();
}
if_some!(some_t, doSomething());
}

Once doSomething() is captured as expr, it will always stay so. The compiler does not see thing.doSomething(), it sees thing.<expr> and this is invalid Rust. You cannot write an expression after the dot, it needs to be an identifier.
The easiest way to fix that is to instead capture it as list of tts. tts can be inspected after they are captured:
#[macro_export]
macro_rules! if_some {
( $option:ident, $($function:tt)* ) => {{
if let Some(thing) = $option {
thing.$($function)*
}
}};
}

Related

Inverse `?` operator in Rust (aka return if Some/Ok)

I am building a macro parser with syn and need to check if a ParseStream is one of several keywords. The code currently looks similar to this:
mod kw {
syn::custom_keyword!(a);
syn::custom_keyword!(b);
syn::custom_keyword!(c);
}
enum MyKeywordEnum {
A(kw::a),
B(kw::b),
C(kw::c),
}
impl syn::parse::Parse for MyKeywordEnum {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
Ok(if let Ok(ret) = input.parse::<kw::a>() {
Self::A(ret)
} else if let Ok(ret) = input.parse::<kw::b>() {
Self::B(ret)
} else if let Ok(ret) = input.parse::<kw::c>() {
Self::C(ret)
} else {
abort!(input.span(), "Couldn't parse primitive type"); // Via #[proc_macro_error]
})
}
}
Is there a built-in operator or macro to return immediately if an expression is Option::Some or Result::Ok?
Is there a better way to organize these checks?
Because ParseStream::parse statically compiles to the specific impl Parse type, I can't use match, right?
As far as I know, there is not. You could create a macro to do this, or you could use the Result combinator functions to make things a little prettier:
impl syn::parse::Parse for MyKeywordEnum {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
input.parse::<kw::a>().map(Self::A)
.or_else(|_| input.parse::<kw::b>().map(Self::B))
.or_else(|_| input.parse::<kw::c>().map(Self::C))
.or_else(|_| abort!(input.span(), "Couldn't parse primitive type"))
}
}

Is there a macro I can use to expect a variant of an enum and extract its data?

Given an enum like
struct Earth { water: usize }
struct Mars { redness: usize }
enum World {
Mars(Mars),
Earth(Earth),
}
A common pattern I write is
fn something_expecting_mars(planet: World) {
let mars = match planet {
World::Mars(data) => data,
_ => panic!("Shouldn't be here now"),
}
}
Is there a macro I can use to expect a variant of an enum and subsequently extract its data?
// rewriting to this
let mars = expect_v!(planet, World::Mars);
The standard library provides a macro for testing a match, but not one for extracting a value. However, it's fairly easy to write one:
macro_rules! expect_v {
($e:expr, $p:path) => {
match $e {
$p(value) => value,
_ => panic!("expected {}", stringify!($p)),
}
};
}
Playground
As suggested in answers to the related question brought up in the comments, you might want to decouple value extraction from the panic. In that case, return an Option instead and let the callers panic if they wish by calling unwrap():
macro_rules! extract {
($e:expr, $p:path) => {
match $e {
$p(value) => Some(value),
_ => None,
}
};
}
// ...
fn something_expecting_mars(planet: World) {
let mars = extract!(planet, World::Mars).unwrap();
}
Anything wrong with just using if let instead of match?
mars = if let World::Mars(data) = planet { data } else { panic!("Woot woot")}

How to to pattern match an Option<&Path>?

My code looks something like this:
// my_path is of type "PathBuf"
match my_path.parent() {
Some(Path::new(".")) => {
// Do something.
},
_ => {
// Do something else.
}
}
But I'm getting the following compiler error:
expected tuple struct or tuple variant, found associated function `Path::new`
for more information, visit https://doc.rust-lang.org/book/ch18-00-patterns.html
I read chapter 18 from the Rust book but I couldn't figure out how to fix my specific scenario with the Path and PathBuf types.
How can I pattern match an Option<&Path> (according to the docs this is what the parent methods returns) by checking for specific values like Path::new("1")?
If you want to use a match, then you can use a match guard. In short, the reason you can't use Some(Path::new(".")) is because Path::new(".") is not a pattern.
match my_path.parent() {
Some(p) if p == Path::new(".") => {
// Do something.
}
_ => {
// Do something else.
}
}
However, in that particular case you could also just use an if expression like this:
if my_path.parent() == Some(Path::new(".")) {
// Do something.
} else {
// Do something else.
}
Two options, convert the path into a str or use a match guard. Both examples below:
use std::path::{Path, PathBuf};
fn map_to_str(my_path: PathBuf) {
match my_path.parent().map(|p| p.to_str().unwrap()) {
Some(".") => {
// Do something
},
_ => {
// Do something else
}
}
}
fn match_guard(my_path: PathBuf) {
match my_path.parent() {
Some(path) if path == Path::new(".") => {
// Do something
},
_ => {
// Do something else
}
}
}
playground

Simplifying a `match` using a Rust macro

There are many question functions (hundreds), and each may have a different type. For each question I want to run a run_question function, which shows how long that function took, and print it's output.
I'm trying to shorten the following match expression with a Rust macro (writing run_question 100s of times does make the code rather long):
fn run_question<T: std::fmt::Display>(question_func: fn() -> T) {
let begin = Instant::now();
let output: T = question_func();
let elapsed_secs = begin.elapsed().as_micros() as f32 / 1e6;
println!("{}", output);
println!("{:.6}s taken", elapsed_secs);
}
fn q1() -> u8 { /* ... */ }
fn q2() -> u32 { /* ... */ }
fn q3() -> u64 { /* ... */ }
fn q4() -> String { /* ... */ }
fn main() {
// ...
match question_num {
1 => run_question(q1), 2 => run_question(q2), 3 => run_question(q3), 4 => run_question(q4),
_ => {
println!("Question doesn't exist.");
},
}
}
I have no experience in writing macros, and attempted the following which doesn't exactly work. It gives the error:
error: variable 'question_num' is still repeating at this depth
I'm rather stumped too how I can print the Question doesn't exist. as a default case.
#[macro_export]
macro_rules! run_questions {
( $chosen_question: expr, $( $question_num: expr, $question_mod: expr ), * ) => {
{
if $chosen_question == $question_num {
run_question($question_mod::solve);
}
}
};
}
The way I'd like to use it, is (or anything just as short is fine as well):
run_questions!(question_num, 1, q1, 2, q2, 3, q3, 4, q4);
I read a bit of the Rust book, but there aren't exactly that many examples of macros.
How would I go about doing this?
Rather than many if statements, I just reproduced the match statement
with a repetition $( ... )* for all the available branches.
It seems to behave like the extensive match expression.
macro_rules! run_questions {
( $chosen_question: expr, $( $question_num: expr, $question_mod: expr ), * ) => {
match $chosen_question {
$($question_num => run_question($question_mod),)*
_ => {
println!("Question doesn't exist.");
}
}
};
}
The error message explained:
macro_rules! run_questions {
($chosen_question: expr, $($question_num: expr, $question_mod: expr),*) => {{
In the above pattern you have a repetition with the * operator that involves variables $question_num and $question_mod
if $chosen_question == $question_num {
run_question($question_mod::solve);
}
In the corresponding code, you can't use $question_num and $question_mod directly: since they are repeated they potentially have more than one value and which one should the compiler use here? Instead, you need to tell the compiler to repeat the block of code that uses these variables. This is done by surrounding the repeated code block with $() and adding the * operator:
$(if $chosen_question == $question_num {
run_question($question_mod::solve);
})*
Although as pointed out by #prog-fh's answer, better to use a match in the macro, same as in the straight code:
match $chosen_question {
$($question_num => run_question ($question_mod::solve),)*
_ => println!("Question doesn't exist.")
};

Is it possible to conditionally compile a code block inside a function?

I'm wondering if something like this is possible
fn main() {
#[cfg(foo)] {
println!("using foo config");
}
}
The context is some code that cannot adequately be tested with just unit tests. I'll often have to run a "demo" cfg which displays information. I'm looking for alternatives to manually commenting in/out some portions of code.
As of at least Rust 1.21.1, it's possible to do this as exactly as you said:
fn main() {
#[cfg(foo)]
{
println!("using foo config");
}
}
Before this, it isn't possible to do this completely conditionally (i.e. avoiding the block being type checked entirely), doing that is covered by RFC #16. However, you can use the cfg macro which evaluates to either true or false based on the --cfg flags:
fn main() {
if cfg!(foo) { // either `if true { ... }` or `if false { ... }`
println!("using foo config");
}
}
The body of the if always has name-resolution and type checking run, so may not always work.
You may interested in the crate cfg-if, or a simple macro will do:
macro_rules! conditional_compile {
($(#[$ATTR:meta])* { $CODE: tt }) => {
{
match 0 {
$(#[$ATTR])*
0 => $CODE,
// suppress clippy warnning `single_match`.
1 => (),
_ => (),
}
}
}
}
fn main() {
conditional_compile{
#[cfg(foo)]
{{
println!("using foo config");
// Or something only exists in cfg foo, like a featured mod.
}}
}
}

Resources