-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Short Macro Invocation Syntax: m!123 and m!"abc" #3267
Conversation
This would be useful for the windows ecosystem (perhaps inside the |
Given that the |
|
||
``` | ||
MacroInvocation : | ||
SimplePath ! Literal |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this "Literal
" unambiguous enough to distinguish between these two situations?
- "literal" token, aka
proc_macro::Literal
(string/character/numeric literals) - "literal" (e.g. expression) grammar, aka
rustc_ast::Literal
aka$lit:literal
macro inputs- besides what literal tokens support, this also includes
false
andtrue
- also it does more validation (suffixes, presumably string escapes, forcing integers into
u128
, etc.)
- besides what literal tokens support, this also includes
I would assume the former (esp. given the mention of m!identifier
later in the RFC, where arguably m!false
/m!true
would fit), but $lit:literal
being the latter muddles the waters a bit sadly.
This is confusing enough that the reference is wrongly mentioning false
/true
on stable under "tokens" (was fixed since by rust-lang/reference#1189).
(Thanks to @solson for bringing up the potential ambiguity wrt bool
literals - I would've naively assumed it was a non-concern at first)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The first one.
$lit:literal
doesn't accept very large integers, which is important for use cases like bignum!123
. (Playground.)
Allowing json!true
and json!false
might be reasonable, but then we also need to have a discussion about json!null
, js!NaN
, py!True
, and so on, which would all need the identifier
or path
grammer. So I'd like to leave that discussion for a future RFC.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, I think we could leave false
/true
to the identifier case, at most I'd add a note to the RFC that Literal
in this case refers to something different from "literal expressions"/$lit:literal
syntax.
Can probably link to other parts of the reference, but I'm not sure what exactly is the most relevant (I got confused just now trying to follow it, though a lot of that was looking at the pre-rust-lang/reference#1189 version).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
$lit:literal
doesn't accept very large integers, which is important for use cases likebignum!123
I wonder if we could fix that?
Not a blocker for this RFC, but we could allow arbitrary-length integer literals to get fed into macros, and only check if they fit when the resulting token stream from the macro gets parsed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
$x:tt
accepts arbitrary length integers just fine. (See the playground link above.) It's just that non-tt
things like $x:literal
no longer represent the raw tokens but instead a portion of already processed/parsed AST.
I still don't entirely see the value in cutting off the parentheses. The syntax of doing a wide string literal is already extremely short. There's several crates for wide string literals, and other than the Let's have a realistic example: use const_utf16::encode;
const MESSAGE: &[u16] = encode!("Hello, world!");
const MESSAGE: &[u16] = encode!"Hello, world!"; Is it really the |
I'm worried about whether users might find precedence confusing. Right now, a macro invocation is a syntactic atom in its own right, being entirely self-delimiting. This is not the case for this proposal, and it opens up the field for confusion. Is Is Is This last one is most confusing of all because negation can be both an operator, or part of a standalone integer literal and the behaviour changes depending on which it is interpreted as here. I am unconvinced that the convenience of the removal of 2 characters is worth the potential for syntactic ambiguity (we can of course come up with well-defined rules for resolve this ambiguity, such as one binding to syntax atoms, but human brains do not work this way). |
It's not about minimizing the amount of bytes of source code, or even the amount of time spent typing (though typing Something like The difference is of course small, but it can add up:
This feels like a somewhat complicated expression, nesting two 'calls' inside the outer call. Without the
To me, this is easier to read, as I visually process it as It all feels similar to why I prefer |
@zesterer That's mostly a formatting issue. You could ask the same question about
We wouldn't accept |
If the intention is to reduce visual complexity, then perhaps it is better to follow the precedence set by explicit numeric literal type annotations (such as |
I suppose one could argue that Note that I'm only proposing this short-hand for literals, and nothing else. So there's no precedence rules about where the argument stops or anything like that. |
We already have |
Conversely, if |
I think this would definitely be useful for the wide str case if the goal is to reduce visual noise. For example:
|
Would this result in |
Yup. But this also already works: println! {"hey {:?}",};
panic![".."];
let _ = vec! (1, 2);
thread_local! [
..
]; So I don't think that's a problem in practice. Using the conventional one of the three (or four) ways to invoke a macro is already part of Rust code style/formatting, and some are even handled by |
I would suggest that what people want is something built into the default experience (at the language level, or in |
I would support this, purely because for a while I've wanted some 'string literal macro' system. A way to, in user code, make things like |
That's an interesting point. This could well be far more than numerics, because with captured identifiers, would this be a de-facto transition to, say, |
The RFC mentions this as an example: |
I haven't created an RFC for this yet, but I have a WIP implementation of custom literals on my rust-lang/rust fork. Given the lack of documentation there, I'll briefly explain here. Essentially, a new trait is introduced: pub trait FromIntegerLiteral: Sized {
type Input: sealed::Integer;
fn from_integer_literal(i: Self::Input) -> Self;
} This trait is a lang item. If the compiler expects a certain type, the known type does not match, and the expected type implements All implementations of As currently written, the trait is limited to accept integers as input. However, I did intend on expanding it to any literal, which would notably include strings. It would obviously be renamed in this situation. I believe an expanded trait that permits any literal would be quite powerful and would serve much the same purpose as this proposal. One notable exclusion would be f-strings, but I believe there was general support for Personally I view custom literals as more ergonomic, more transparent, and with guaranteed const eval, more reliable. f-strings would be great to have, but I don't think this is the way to go about it. It may seem surprising that coercions work in this manner, but I assure you that the implementation linked is already mostly functional. The only thing missing is always const eval'ing the input. Custom literals combined with type ascription would be nearly identical to custom suffixes — you could do Edit: After some discussion on Zulip, the existing implementation will not work, but it is still possible to have custom literals in this user-facing manner. |
A counterargument: |
[future-possibilities]: #future-possibilities | ||
|
||
In the future, we could consider extending this syntax in a backwards compatible way by allowing | ||
slightly more kinds of arguments to be used without brackets, such as `m!-123` or `m!identifier`, or even `m!|| { .. }` or `m!struct X {}`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
m!identifier
seems unambiguous and easy to add, though also less well-motivated.
m!-123
could, for now, at least have a rustfix-applicable suggestion telling the user to use -m!123
instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
m!identifier
would possibly make it hard or impossible to allow m!p::a::t::h
or m!thing.member
, so I figured that might be good to leave for a later discussion. (Not saying that we should allow either of that. Just saying that allowing m!identifier
might block other future possibilities.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Honestly, I would put this exact explanation in the RFC text itself, since I had the exact same question and you pretty thoroughly convinced me why we shouldn't right now in a single sentence.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
telling the user to use
-m!123
instead.
More likely m!(-123)
, since it isn't necessarily guaranteed that the macro is commutative with -
Co-authored-by: Josh Triplett <josh@joshtriplett.org>
As a bystander and declarative macro fanatic, I'm a bit hesitant about this. It seems a bit surprising that this new form would only support a macro_rules! macroroni {
($x:ident) => { /* TODO */ };
}
fn main() {
macroroni!foo; //~ ERROR can only be used with macros that take a literal
} This could also crop up during a refactor: let default_animation_wide = w!"normal";
let y = self.sprite.set_animation( // hmm, i should pull `"normal"` into a const Moving const DEFAULT_ANIMATION: &str = "normal";
let default_animation_wide = w!DEFAULT_ANIMATION;
//~^ ERROR can only be used with macros that take a literal
let y = self.sprite.set_animation(DEFAULT_ANIMATION); Even if this syntax is opened up to most other metavariable types, it's backwards-incompatible to open it to macro_rules! macroroni {
($x:tt) => { panic!("{}", $x) };
() => {};
}
fn main() {
// this currently does nothing, but may panic if `tt` is accepted
macroroni!();
// If `tt` is accepted but delimited groups are rejected,
// you might start with this...
macroroni!1;
// ... then realize you need an addition...
macroroni!(1 + 1); //~ ERROR no rules expected the token `+`
// ... and run into a stumbling block.
} This syntax might also cause readability issues, especially if it's expanded to cover // I am a Linux user. What does `w!` mean?
let le_mot = w!"foo";
// Ugh, it's 12am... I need sleep...
// ? There's no variable named `shelllle_mot` in scope, is there?
let result = shell!le_mot;
// Oh! This is a macro call
let result = shell!(le_mot); On the other hand, the |
I strongly oppose this proposal. I've been programming in Rust for just under five years, so I'm probably not as experienced as most folks here. I will say however, that Rust was by far the hardest language for me to become proficient in. When I first picked it up, I had a hard time understanding the syntax by reading it (lifetimes and the turbofish were extremely confusing). Yet, I was able to approximate at first macro invocations as simple function calls that were somehow different but it didn't matter for the time being. In fact, as a newcomer, if I saw Today, I regularly try to convince folks who work on critical systems to use Rust: I worry that removing the delimiter tokens around macro invocations will make code significantly harder to understand for newcomers. I'll also add that, many years ago, I had to learn VBScript (I forget which version). But one of the most confusing things was that invoking a function with parentheses and without them had a different behavior (one would allow reading the return value but not the other one IIRC). |
I haven't thought much about |
I guess |
I like it. I hope that this form is whitespace-sensitive and that |
… r=joshtriplett improve format impl for literals The basic idea of this change can be seen here https://godbolt.org/z/MT37cWoe1. Updates the format impl to have a fast path for string literals and the default path for regular format args. This change will allow `format!("string literal")` to be used interchangably with `"string literal".to_owned()`. This would be relevant in the case of `f!"string literal"` being legal (rust-lang/rfcs#3267) in which case it would be the easiest way to create owned strings from literals, while also being just as efficient as any other impl
Is it possible to mix multiple short macro invocation? x!y!"hello" |
No, |
I agree with what several others have said about restricting this functionality to // a custom implementation of the format! macro
macro_rules! f {
// explicitly opt-in to the use of this macro as a bracket-less matcher that
// only accepts literals.
// also, `macro_rules!` can error if a bracket-less matcher corresponds to a
// transcriber that isn't explicitly a `const` block.
// I'm unsure if this requires stabilizing const blocks.
$lit:literal => const { format_args!($lit) };
// The rest of the macro definition can be used as normal
($lit:literal) => { std::format!($lit) };
// etc
} |
How would |
My strong guess is this syntax is only for macro invocations without any parentheses, so any |
It is not a literal as the rust lexer interprets. All of our literals are explained in this doc https://doc.rust-lang.org/reference/tokens.html#literals It basically includes numbers, strings, chars and all variations of those like tuple indexes, strings with custom suffixes, numbers with suffixes, byte strings, raw strings |
…iplett improve format impl for literals The basic idea of this change can be seen here https://godbolt.org/z/MT37cWoe1. Updates the format impl to have a fast path for string literals and the default path for regular format args. This change will allow `format!("string literal")` to be used interchangably with `"string literal".to_owned()`. This would be relevant in the case of `f!"string literal"` being legal (rust-lang/rfcs#3267) in which case it would be the easiest way to create owned strings from literals, while also being just as efficient as any other impl
We discussed this in a @rust-lang/lang meeting a long while ago, and discussed it again today. There was lukewarm sentiment towards having this as a general-purpose shorthand. In general, it felt like:
With that in mind: @rfcbot close |
Team member @joshtriplett has proposed to close this. The next step is review by the rest of the tagged team members: No concerns currently listed. Once a majority of reviewers approve (and at most 2 approvals are outstanding), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up! See this document for info about what commands tagged team members can give me. |
@rfcbot reviewed |
🔔 This is now entering its final comment period, as per the review above. 🔔 |
The final comment period, with a disposition to close, as per the review above, is now complete. As the automated representative of the governance process, I would like to thank the author for their work and everyone else who contributed. This is now closed. |
Rendered