macro_rules! mac_trait {
($i:item) => {
trait T { $i }
}
}
mac_trait! {
fn foo() {}
}
Is this making it easier to implement a trait? interface? macro_rules! mac_trait {
($i:item) => {
trait T { $i }
}
}
mac_trait! {
fn foo() {}
}
Is this making it easier to implement a trait? interface?But as others said, the point is boilerplate reduction. You can write a macro that'll automate that process away. A lot of examples are about automatically implementing certain behaviors for structs, such as equality, but you'll also see extensive macro use in ORM libraries that can generate a lot of runtime code off of a smaller table definition macro.
Anything with an explanation point is a macro invocation, by the way.
Can you explain what this means, exactly?
foo(bar)
This is a macro invocation: foo!(bar)
It's done this way because macros can have much more complex things inside the ()s than functions can. This helps both humans and computers parse such things. println!("hello, world");
or todo!();
are macro invocations. println!("something")
that's a macro#[Foo] struct Bar {}
baz!()
cat!{}
blah![]
I didn’t bother to clarify, since 5 people jumped in to explain, and I felt it was redundant.
`trait T {}` and `fn foo() {}` are used in the final code, which would be:
trait T { fn foo(){} }The macros are only about convenience on usage and reduction of boilerplate. A good example is the vec![1,2] macro call that expands to
{
let mut x = Vec::new();
x.push(1);
x.push(2);
x
}
The reason the blog post is using that example is because it's showing a new type of macro argument which accepts items (functions, structs, enums, traits and impls) and expands them correctly without any extra needs by the macro writer. fn set_document_owner(document_id: i64, user_id: i64)
where I could easily provide the arguments in the wrong order, I want fn set_document_owner(document_id: DocumentID, user_id: UserID)
where this cannot happen. Each ID type is just a one-element tuple containing an i64, which would just be pub struct DocumentID(pub i64);
pub struct UserID(pub i64);
//... and so on ...
and so on. But in order for them to behave as expected (e.g. with the == operator or when reading/writing IDs in the database), I need to write a bunch of boilerplate. This is the macro that I have now: use rusqlite::types::{FromSql, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
macro_rules! make_id_type {
( $name:ident ) => {
///A type-safe primary key.
#[derive(Default, Clone, Copy, Hash, PartialEq, Eq, Deserialize, Serialize)]
pub struct $name(pub i64);
impl fmt::Display for $name {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
}
}
impl ToSql for $name {
fn to_sql(&self) -> rusqlite::Result<ToSqlOutput> {
self.0.to_sql()
}
}
impl FromSql for $name {
fn column_result(value: ValueRef) -> FromSqlResult<Self> {
Ok($name(i64::column_result(value)?))
}
}
};
}
make_id_type!(DocumentID);
make_id_type!(UserID);
//... and so on ...
This is pretty much the most basic usecase for macros. You can do some more interesting stuff than just take a name and insert it into some template, because macros can actually pattern-match the phrases given to them in the macro invocation. A good example, also from the RDBMS domain, would be https://docs.diesel.rs/diesel/macro.table.html (link goes to the documentation of the macro). #define MAC_TRAIT(i) trait T { i }
MAC_TRAIT( fn foo() {} )
Except that in Rust macro arguments are typed and can dictate their own microsyntax. Macro output is assembled in the syntax tree, not as text find'n'replace, which also avoids many surprises.You don't need them to write rust applications, but they could, for example, help writing tests on 3 different backends without having to write 3 times the same testing function.
In this situation, it's different from having a type parameter, because macros won't make you change the function definition. They will just "copy and paste" the code block for you.
A good mental model is to think about this kind of macro is as a compiler callback that defines a syntax tree fragment in its body, to be evaluated when the macro is called.
The goal of macros is to deduplicate code; either inside a function or to implement traits (eg. the Debug trait, that allows printing a structure/enumeration/... recursively; this would be very boring to implement yourself every time)
Of course lisp macros are on a different level but the languages are massively different so it's not necessarily a super fair comparison.