Summary
Allow using underscore (_
) as name on function definitions, and such functions does not have a name, so they cannot be invoked, and multiple such functions are allowed in one scope without triggering redefinition error.
Motivation
Rust currently doesn’t have any kind of official compile-time static assertion, however, there are various tricks used to implement such assertions. For example, std::mem::transmute
is usually used to check size of a type like following:
std::mem::transmute::<TypeToCheck, [u8; EXPECTED_SIZE]>();
However, this trick can only be used inside function body (i.e. it doesn’t work on the top level), so people have to do something like
fn _assert_type() {
std::mem::transmute::<TypeToCheck, [u8; EXPECTED_SIZE]>();
}
If you want to build a macro to generate such static assertion, you would be in a dilemma: you either need to require your users to wrap your macro inside a function, or the macro can only be used once in each scope, because macros cannot generate random names as function name to avoid conflicts with itself.
If one can declare an underscore function with the characteristics described in the summary section, macros would be able to simply wrap everything in such function, and then they can be used everywhere without restrictions they currently face.
Guide-level explanation
If a function has name _
(a single underscore), it doesn’t create a function in the current scope, which means it cannot be invoked, and there can be multiple function definitions with the same name in a scope. Although it isn’t otherwise usable, compiler still checks the code inside. This is useful in macros to create an anonymous function body scope for wrapping statements only allowed in that context.
For example,
macro_rules! assert_size {
($type:ty => $n:expr) => {
fn _() {
::std::mem::transmute::<$type, [u8; $n]>();
}
}
}
assert_size!(Option<bool> => 1);
assert_size!(Option<Option<bool>> => 1);
Reference-level explanation
(Not sure what to write here.)
Drawbacks
Similar to the special behavior of _
in let bindings, this may cause confusions.
Rationale and alternatives
To solve the problem that macros need function scope without conflicting with itself, there are several alternatives.
Add special macro to generate identifiers with serial number
For example, add a macro serial!(prefix_)
which generates a different identifier every time it is invoked, for example, it generates prefix_1
, prefix_2
, prefix_3
, etc.
(This can probably be implemented as a proc macro, actually.)
Pros:
- It is probably implementable from library without touching the language.
- Even if implemented in std, it just needs an additional macro, no changes to core language is needed.
Cons:
- It generates redundant names which are generally not useful.
- (Maybe it would add symbols to files or even binaries? Maybe affect reproducible build?)
Allow namespace scoping on the top level context
Allow putting functions inside scopes even on the top level context, and such scopes can isolate function definitions so that they don’t conflict. For example:
mod a {
{ fn foo() {} }
{ fn foo() {} }
}
Pros:
- Straightforward in concept?
- Useful in other cases?
Cons:
- Maybe adding such support would be troublesome for compiler implementation?
Underscope module
This is the same thing as the proposal but on the module name.
Pros:
- Maybe can be used to create module level context in macros? (Would we want that?)
Cons:
-
Macros would need two level wrappers to do what they want to do, which feels redundant.
Why is this design the best in the space of possible designs? What other designs have been considered and what is the rationale for not choosing them? What is the impact of not doing this?
Prior art
(Not sure)