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!
It’s surprisingly simple: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=f176852c61dcf0c3382f0ac97c26de03 As a side node, asking for a value, and then immediately calling
to_string
on it seems kinda hiding the allocation. I’d suggest let the user callto_string
on it themselves.(e) Changed it a bit to account for passing
None
as the third argument.I think the point is that the variable itself is an Option. Your example only works for literal Option (although the value inside the optional itself might not be a literal).
One option to OP’s problem is to use an auxiliary trait implemented on both string and Option<string>
This looks something like the following
https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=aa68fe540dbd09821ecc482423227b46
Option<&str>
. If it did, we would lose literalNone
support 😉Similar impl, but using wrapper struct with
From
implsThe 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() };
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);
Oops. I was looking at it wrong.
Re-read the end of OP’s requirements.
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()) } }
Yes, but then the concrete type of
None
literals becomes unknown, which is what I was trying to point out.