5 ms·
For a non-rust person's point of view, I assume a macro is something like a super-pre-processor that helps keep the code concise. In the example below, what por
by harikb 7y ago
For a non-rust person's point of view, I assume a macro is something like a super-pre-processor that helps keep the code concise. In the example below, what portion is written ahead of time (by the person making the macro) and what portion is the macro usage?
macro_rules! mac_trait {
($i:item) => {
trait T { $i }
}
}
mac_trait! {
fn foo() {}
}
Is this making it easier to implement a trait? interface?
- progval 7y agoThe first block defines a macro; the second block is the macro usage. 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)
- zamalek 7y agoThe first block (macro_rules!) the declaration is written by the macro author, the second block (mac_trait!) is the usage written by the user. Note that macro_rules is itself a macro. `trait T {}` and `fn foo() {}` are used in the final code, which would be: trait T { fn foo(){} }
- harikb 7y agoThanks. I am trying to understand where the savings for the user is (as in less code to type). I guess it is somehow related to T being a generic and if they didn't use this macro, they would have had to repeat this all over?
- aliceryhl 7y agoOne common usage is to use macros to repeat some definition for every integer type. In this case T is not a generic, but the name of the trait.
- estebank 7y agoIn the example given the macro is almost useless. The benefit comes if you want the expanded code to have more things, like methods defined that otherwise you would have your consumer write or sister methods and types. 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.
- cesarb 7y agoJust nitpicking, but a better explanation of the expansion of `vec![1,2]` would use `Vec::with_capacity(2)` instead of `Vec::new()`, that is, it already allocates the Vec with the correct capacity (the real expansion uses in-place construction of a boxed slice, but the effect is similar).
- CDSlice 7y agoIn this specific case the macro isn’t very useful since you can easily write `trait T { ... }` yourself. They tend to be more useful when there is a lot of similar boilerplate code that only differ in a few ways. A macro can cut down most of that repetition. They can also be used to provide pseudo “literals” like the vec! macro does for the Vec data type.
- deleted 7y ago[deleted]
- zamalek 7y agoOne thing I use it for all the time is data-driven tests, e.g. https://github.com/jcdickinson/racemus/blob/master/racemus-binary/src/writer.rs#L476 https://github.com/jcdickinson/racemus/blob/master/racemus-b...
- majewsky 7y agoHere's a real-world example from the code I was working on yesterday: I have several database tables where the records have numeric IDs, and instead of having the ID type be just an int everywhere, I want them to be proper types to avoid mixups. Instead of 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 https://docs.diesel.rs/diesel/macro.table.html (link goes to the documentation of the macro).
- Fiahil 7y agoRust macros are their own little thing, they help reduce code repeatability by writing code in your place. 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.
- ashtonkem 7y agoIn general as a rust programmer you'll end up consuming orders of magnitudes more macros than you'll ever write; from what I've seen macros are more commonly written in libraries to make their use smoother, and my experience in other Macro heavy languages was similar (Clojure, SBCL). 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.
- justwalt 7y ago“Anything with an explanation point is a macro invocation, by the way.” Can you explain what this means, exactly?
- deleted 7y ago[deleted]
- steveklabnik 7y agoThis is a function invocation: 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.
- lalaithion 7y agofoo(x, y, z) is a function. foo!(x, y, z) is a macro.
- deleted 7y ago[deleted]
- jack1243star 7y agoNot OP, I think they mean the exclamation mark (!)
- Koshkin 7y agoThe looks of this makes me want to go back to Lisp.
- estebank 7y agoThis macro system is very powerful, if kludgy, but it's not the final version of the system. Macros 2.0 has been in the works for a while now and will eventually have a much nicer syntax for macro definitions. When Rust 1.0 was gearing up for release the team (rightly imo) decided that launching then with known-imperfect-but-usable macros was a worthwhile trade-off.
- steveklabnik 7y agoIt was designed and implemented by some huge Racket fans, incidentally...
- simias 7y agoRust's macros are unpleasant to write and debug but very nice to use. The language is also much more expressive than C so I don't find myself needing to (ab)use macros quite as often. Of course lisp macros are on a different level but the languages are massively different so it's not necessarily a super fair comparison.
- Eikon 7y agoOne issue with macros is that they are currently breaking most IDE integrations.
- pornel 7y agoIt's a bit like C's: #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.
- fulafel 7y agoRust macros are real, structural macros, like in most macro supporting langugaes. So they are not like the preprocessor style macros in C and relatives. 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.