this post was submitted on 16 Jun 2024
28 points (96.7% liked)

Rust

6823 readers
3 users here now

Welcome to the Rust community! This is a place to discuss about the Rust programming language.

Wormhole

[email protected]

Credits

  • The icon is a modified version of the official rust logo (changing the colors to a gradient and black background)

founded 2 years ago
MODERATORS
28
submitted 10 months ago* (last edited 10 months ago) by [email protected] to c/rust
 

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. 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!

you are viewing a single comment's thread
view the rest of the comments
[–] v9CYKjLeia10dZpz88iU 1 points 10 months ago* (last edited 10 months ago) (1 children)

One option to OP’s problem is to use an auxiliary trait implemented on both string and Option

This looks something like the following

https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=aa68fe540dbd09821ecc482423227b46

[–] BB_C 1 points 10 months ago* (last edited 10 months ago) (1 children)

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

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

Similar impl, but using wrapper struct with From impls

[–] v9CYKjLeia10dZpz88iU 1 points 10 months ago* (last edited 10 months ago) (1 children)

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 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 1 points 10 months ago (1 children)

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 1 points 10 months ago* (last edited 10 months ago) (1 children)

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())
    }
}
[–] BB_C 1 points 10 months ago (1 children)

Yes, but then the concrete type of None literals becomes unknown, which is what I was trying to point out.