Hey,

Is there any way to create a macro that allows a Some<T> or T as input?

It’s for creating a Span struct that I’m using:

struct Span {
    line: usize,
    column: usize,
    file_path: Option<String>,
}

…and I have the following macro:

macro_rules! span {
    ($line:expr, $column:expr) => {
        Span {
            line: $line,
            column: $column
            file_path: None,
        }
    };

    ($line:expr, $column:expr, $file_path: expr) => {
        Span {
            line: $line,
            column: $column
            file_path: Some($file_path.to_string()),
        }
    };
}

…which allows me to do this:

let foo = span!(1, 1);
let bar = span!(1, 1, "file.txt");

However, sometimes I don’t want to pass in the file path directly but through a variable that is Option<String>. To do this, I always have to match the variable:

let file_path = Some("file.txt");

let foo = match file_path {
    Some(file_path) => span!(1, 1, file_path),
    None => span!(1, 1),
}

Is there a way which allows me to directly use span!(1, 1, file_path) where file_path could be "file.txt", Some("file.txt") or None?

Thanks in advance!

    • v9CYKjLeia10dZpz88iU
      link
      fedilink
      arrow-up
      1
      ·
      edit-2
      6 months ago

      Two of your macro rules are not used 😉 (expand to see which ones).

      The macro rules are all used. (Macros are matched from top to bottom by the declared match types. The ident/expressions can’t match until after the more text based Option matching.)

      let _foo = Span { line: 1, column: 1, file_path: None };
      let _bar = Span { line: 1, column: 1, file_path: "file.txt".upgrade() };
      let _baz = Span { line: 1, column: 1, file_path: Some("file.txt".to_string()) };
      let _baz = Span { line: 1, column: 1, file_path: None };
      let _baz = Span { line: 1, column: 1, file_path: borrowed.upgrade() };
      let _baz = Span { line: 1, column: 1, file_path: owned.upgrade() };
      

      This doesn’t support Option<&str>. If it did, we would lose literal None support 😉

      I didn’t make Option<&str> an option because the struct is for type Option<String>. It does support Option<String> though.

      impl OptionUpgrade for Option<String> {
          fn upgrade(self) -> Option<String> {
              self
          }
      }
      

      It looks like the following, and uses the last match case.

      let opt: Option<String> = Some("text".into());
      let opt = span!(1, 1, opt);
      

      With macro expansion

      let opt: Option<String> = Some("text".into());
      let opt = Span { line: 1, column: 1, file_path: opt.upgrade() };
      

      There’s not anything stopping it from supporting Option<&str> though. This would be the implementation

      impl OptionUpgrade for Option<&str> {
          fn upgrade(self) -> Option<String> {
              self.map(|v| v.into())
          }
      }
      
      • BB_C
        link
        fedilink
        arrow-up
        1
        ·
        6 months ago

        The macro rules are all used.

        Oops. I was looking at it wrong.

        I didn’t make Option<&str> an option because the struct is for type Option<String>.

        Re-read the end of OP’s requirements.

        • v9CYKjLeia10dZpz88iU
          link
          fedilink
          arrow-up
          1
          ·
          edit-2
          6 months ago

          I made an edit.

          There’s not anything stopping it from supporting Option<&str> though. This would be the implementation

          impl OptionUpgrade for Option<&str> {
              fn upgrade(self) -> Option<String> {
                  self.map(|v| v.into())
              }
          }
          

          It’s also possible to just make it generic over Option types

          impl<A: ToString> OptionUpgrade for Option<A> {
              fn upgrade(self) -> Option<String> {
                  self.map(|v| v.to_string())
              }
          }