Making Constructor set fields with Default

Sometimes, we'd like to make a template template that behave in different ways for different fields. For example, let's suppose that we want our Constructor template to be able to set fields to their default values, and not take them as arguments.

We can do this with an explicit conditional for each field:

#![allow(unused)]
fn main() {
use derive_deftly::define_derive_deftly;
define_derive_deftly! {
   Constructor:

   impl<$tgens> $ttype where $twheres {
     $tvis fn
        // This is the function name, same as before.
        ${if tmeta(constructor(newfn)) {
            ${tmeta(constructor(newfn)) as ident}
          } else {
            new
          }
        }
     (
       // These are the function arguments:
       $(
          ${when not(fmeta(constructor(default))) } // (1)
          $fname: $ftype ,
        )
     ) -> Self {
          Self {
              $( $fname:
                  ${if fmeta(constructor(default)) {
                    ::std::default::Default::default() // (2)
                  } else { $fname } }
                 , )
          }
      }
   }
}

use derive_deftly::Deftly;
#[derive(Deftly)]
#[derive_deftly(Constructor)]
struct Foo {
    #[deftly(constructor(default))]
    s: Vec<String>,
    n: u32,
}
}

Here we're using a new construct: $when. It's only valid inside a loop like $( ... ) or ${for ...}. It causes the output of the loop to be suppressed whenever the condition is not true.

The condition in this cases is not(fmeta(constructor(default))). (See // (1).) You've seen fmeta before; it's true when a given attribute is present on the current field. The not condition is just how we express negation. All together, this $when keyword causes each field that has #[deftly(Constructor(default))] applied to it to be omitted from the list of arguments to the new() function.

Note at // (2) that we're using ::std::default::Default::default(), rather than calling Default::default() unconditionally. Remember, macros expand at the position where they are invoked, and it's possible that we'll be invoked in a module that has been built without the standard prelude.

Besides $not, you can use other boolean operators in conditions too: there is an any(...) that is true whenever at least one of its arguments is true, and an all(...) that is true when all of its arguments are true.