These are chat archives for rust-lang/rust

31st
Jan 2016
Konstantin Stepanov
@kstep
Jan 31 2016 08:20
Hi. Is there some way to pass lifetime into macro to use it for struct definition, so I can use this lifetime in struct field types?

So I can build this:

struct Struct<'a> {
  x: &'a str
}

with a macro, passing both field and lifetime:

macro_rules! make_struct {
  ($name:ident<$lifetime:???> { $($field:ident: $typ:ty);* }) => {
    struct $name<$lifetime> {
      $($field: $typ;)*
    } 
 }
}

make_struct!(Struct<'a> { x: &'a str })
I can use opaque lifetimes as part of type (with $typ:ty), but struct keyword waits for ident after itself, so I have to ask for $name:ident.
Konstantin Stepanov
@kstep
Jan 31 2016 08:26
And I can't define lifetimes inside macro, as I can't use it in my types, I pass to macro. I must pass both lifetime and types using it into macro from the outside to preserve hygiene.
As far as I could find, there's no solution for my problem so far. Am I wrong? Maybe there is solution I don't see?
John C F
@critiqjo
Jan 31 2016 17:08

@kstep Why would you need to use an explicit identifier for lifetime?

struct Struct1<'a> { x: &'a str }
struct Struct2<'a> { x: &'a str } // there's no issue in reusing 'a (both are independent/unrelated)

Why even such a macro? It just seems bad design... Anyway, here is a working version: http://is.gd/7apjNY

Konstantin Stepanov
@kstep
Jan 31 2016 17:12
@critiqjo because I want to generate a struct, and then a group of setter methods based on struct fields including constructor which accepts required field values, and a trait implementation which again depends on struct fields.
A lot if boilerplate code I need to repeat many times.
John C F
@critiqjo
Jan 31 2016 17:16
@kstep why not simply:
struct Struct<'a, T> { x: &'a T }
impl<'a, T> Struct<'a, T> {
    // boilerplate methods
}
Konstantin Stepanov
@kstep
Jan 31 2016 17:17
@critiqjo I did so. A lot of times. A lot of code which can be described in a single place. That's why I want some macro to remove the boilerplate.
To put you in since context, that's the code in talking about: https://github.com/kstep/vkrs/blob/master/src/audio.rs
Note the struct Getimplementing builder pattern in place and a Request trait implementation for it.
Note the request! {} macro usage, and compare the volume of boilerplate code I removed.
Konstantin Stepanov
@kstep
Jan 31 2016 17:23
The macro definition I'm talking about: https://github.com/kstep/vkrs/blob/request-macro/src/macros.rs
John C F
@critiqjo
Jan 31 2016 17:26
@kstep but I believe it's an antipattern! Your code is (much) harder to read, understand, and modify for a potential contributer. And furthermore, you are not getting any performance improvement (compilation would take a little longer).
Sacrificing readability for a few lines of code with no runtime improvement is definitely a no go (at least for me)...
But on the other side, I haven't seen or thought of doing something like that at all... I was kinda surprised when I saw the macro-ed code! :smile:
Konstantin Stepanov
@kstep
Jan 31 2016 18:13
I can argue. It's not “just a few lines of code”, I have to write a lot of similar code to describe each request. And there are a lot of requests to describe. And all this code just describes the same set of fields again and again, more like request logic duplication three times in a row. IMHO a macro here would make me keep request logic knowledge in one single place instead of repeating it in three different ways.
You can see a full list of methods I need to implement to describe full VK API here: https://vk.com/dev.php?method=methods. There are at least 200 (or even more) possible API requests, and I just scratched just a few of them. If I go on with more verbose methods description, I would go insane before I finish with a half of API methods.
Konstantin Stepanov
@kstep
Jan 31 2016 18:19
But thanks for your advice and time, I'll try to adapt your primer for my case.
John C F
@critiqjo
Jan 31 2016 19:52
@kstep here's something that's more readable (and modifiable): http://is.gd/JcGPqt
Konstantin Stepanov
@kstep
Jan 31 2016 20:38
@critiqjo in the end I splitted up my request! macro into two macros: request_builder_impl! (which implements impl Type { ... } body only) and request_trait_impl! (which implements impl ::api::Request for Type { ... } body) so I can write impl<'a> Type<'a> and impl<'a> Request for Type<'a> putting lifetime outside of macro, while being able to remove some of boilderplate by putting macro call inside of impl ... { request_impl!(...) } structure. I also preserved more common request! macro which combines other two macros with all outer impl ... clauses included, as it works very well for structures without lifetimes.
So for structures without lifetimes I use request! macro, which hides all boilerplate code, and for structures with lifetimes I repeat fields list (with some context meta info) three times: 1) in structure definition (struct Get<'a> {...}), 2) in structure implementation (impl<'a> Get<'a> { request_builder_impl! { ... } }) and 3) in trait implementation (impl<'a> ::api::Request for Get<'a> { request_trait_impl! { ... } }). So for lifetimed struct case it removes large part of boilerplate, which is not perfect, but still much more better, than it was without macros at all. I think I can live with it for now.
Thank you for your advices!