With a trivial way in which any custom piping can be implemented for any given type for:
let shared = state
.to(Mutex::new)
.to(Arc::new)
.to(Some);
not having at least somewhat of a similar solution for macros, which necessarily require forced prefix syntax, feels rather out of place - to say the least.
The RFC on the matter has been stuck in a limbo for over 6 years now. The topic has been brought up time after time again. A separate library has been made to cover up for the lack of any significant attention in this regard.
From the discussions I can see so far, there doesn't seem to be any major downside / drawback / issue to the idea itself, either. It's the exact implementation / syntax specifics that different people have different positions on.
Can we attempt to settle them once and for all, instead of sporadically commenting on the topic every once in a while, continuously postponing the underlying specifics to some other time?
Option #1
Do not introduce any additional syntax to the existing macro declarations. Merely allow the macros with one argument or more to be used in a postfix notation - as long as a provided AST node matches the expected argument of the macro itself.
macro_rules! debug {
($e:expr) => { dbg!($e); }
}
fn use() {
let example = 2021.debug!();
}
- Pros: no change in the existing syntax.
- Cons: auto-suggestion pollution in the code-analyzer/LSP recommendations.
Option #2
Same as #1, yet with a prior #[macro_suffix]
/ #[macro_postfix]
annotation, required to use any given macro in its postfix form. Macros not explicitly marked as such will not be processed by the compiler and throw a compile time syntax error, as they do today.
#[macro_suffix|macro_suffix(only)]
macro_rules! debug {
($e:expr) => { dbg!($e); }
}
fn use() {
let example = 2021.debug!();
}
- Pros: with a manual attribute opt-in LSP pollution would be minimized; the attribute itself could provide documentation, outlining the details of the underlying implementation; the
#[macro_suffix(only)
could enforce the use of macro only/exclusively in postfix position. - Cons: with no reference to the
$self
, the#[macro_suffix]
alone might be confusing.
Option #2.5
Combines options #2 and #3 in one.
#[macro_suffix]
macro_rules! debug {
($self:expr) => { dbg!($self); }
}
fn use() {
let example = 2021.debug!();
}
- Pros: with a manual attribute opt-in LSP pollution would be minimized; the attribute itself could provide documentation, outlining the details of the underlying implementation; the
#[macro_suffix(only)
could enforce the use of macro only/exclusively in postfix position; the$self
syntax closely mirrors the standard associated methods, with the support for both the postfix callexpr().macrofy!()
and the regularmacrofy!(expr())
call. - Cons: verbosity.
Option #3
Require an explicit $self
declaration as a first argument of a given macro, alongside the type of node the macro will be processed for, in its postfix format. As with #2, any macro that doesn't declare an explicit ($self:?)
as a first argument will not be processed in a postfix position.
macro_rules! debug {
($self:expr) => { dbg!($self); }
}
fn use() {
let example = 2021.debug!();
}
- Pros: the
$self
syntax closely mirrors the standard associated methods, with the built-in support for both the postfix callexpr.macrofy!()
and the regularmacrofy!(expr)
. - Cons: potential confusion as to whether the piece of AST passed will be evaluated once or multiple times - at each placement, whether it is bound by value/ref, as lvalue/rvalue, etc.
Option #4
Similar to #3, yet without an explicit node type for the $self
, with an optional :self
allowing for a custom name in place of the $self
itself.
macro_rules! debug {
($self) => { dbg!($self); }
// same as
($e:self) => { dbg!($e); }
}
fn use() {
let example = 2021.debug!();
}
- Pros: clean, idiomatic, consistent with the associated methods.
- Cons: unclear as to whether the code is to be processed is
expr
,ident
or anything else.
Option #5, @idanarye + @DragonDev1906
In addition to the macros-as-suffix, explicitly binding the passed in expression either by value (self
) or reference (&(mut) self
) an additional let
/ match
in postfix position would allow to match against deeper nester patterns. The end result may look something like:
macro_rules! a(&self) {
.() => {
.match {
this => {
println!("{:?}", this.a);
println!("{:?}", this.b);
}
}
};
}
- Pros: clear explicit binding by value/ref; inline pattern matching.
- Cons: requires additional syntax implementation, alongside suffix macros themselves.
In the spirit of (pre) RFCs, feel free to either or the functionality itself (1) and your own preferred implementation of it (2), alongside your line of reasoning on the matter.
If possible, make an example of a project you have personally worked on, wherein having such a feature right would greatly help/streamline/spare you from unnecessary time/effort/cognitive load.
I'll try my best to organize the incoming pros/cons in at least somewhat comprehensive of a manner, in the meantime.
Quick poll, with up to 3 selectable options - in case you'd be fine with more than one impl.
- Option #1
- Option #2
- Option #2.5
- Option #3
- Option #4
- Option #5