- Feature Name:
inferred-enum-type
- Start Date: 2022-01-31
- RFC PR: rust-lang/rfcs#0000
- Rust Issue: rust-lang/rust#0000
Summary
Allow inference of base enum type names via a _::Variant
syntax.
Motivation
Matching over enums with many variants and/or with large type names causes lots of repetition in code that could be handled by the compiler.
If this is allowed in match arms, it seems intuitive to extend it to assignments using enums and all other expressions.
Enum variants can already be imported directly, by e.g. use MyEnum::*;
, however this can only import at module scope, causing name pollution and possible type name clashes (especially since enum variants are often named very generically).
Another possible approach possible today is use MyEnum as E
. However doing this is even more indirect and possibly more confusing than inferring the type name. Importing as a single-letter character also has similar plain-text editor downsides as this RFC, which could be interpreted as having less reason to not do this RFC.
Example
The author recently wrote this code as part of an overhaul of errors to an enum type for the http-types
crate:
pub fn associated_status_code(&self) -> Option<StatusCode> {
match self {
HeaderError::SpecificityInvalid => Some(StatusCode::BadRequest),
HeaderError::DateInvalid(_) => Some(StatusCode::BadRequest),
HeaderError::TransferEncodingUnnegotiable => Some(StatusCode::NotAcceptable),
HeaderError::TransferEncodingInvalidEncoding(_) => Some(StatusCode::BadRequest),
HeaderError::TraceContextInvalid(_) => Some(StatusCode::BadRequest),
HeaderError::ServerTimingInvalid(_) => Some(StatusCode::BadRequest),
HeaderError::TimingAllowOriginInvalidUrl(_) => Some(StatusCode::BadRequest),
HeaderError::ForwardedInvalid(_) => Some(StatusCode::BadRequest),
HeaderError::ContentTypeInvalidMediaType(_) => Some(StatusCode::BadRequest),
HeaderError::ContentLengthInvalid => Some(StatusCode::BadRequest),
HeaderError::AcceptInvalidMediaType(_) => Some(StatusCode::BadRequest),
HeaderError::AcceptUnnegotiable => Some(StatusCode::NotAcceptable),
HeaderError::AcceptEncodingInvalidEncoding(_) => Some(StatusCode::BadRequest),
HeaderError::AcceptEncodingUnnegotiable => Some(StatusCode::NotAcceptable),
HeaderError::ETagInvalid => Some(StatusCode::BadRequest),
HeaderError::AgeInvalid => Some(StatusCode::BadRequest),
HeaderError::CacheControlInvalid => Some(StatusCode::BadRequest),
HeaderError::AuthorizationInvalid(_) => Some(StatusCode::BadRequest),
HeaderError::WWWAuthenticateInvalid(_) => Some(StatusCode::BadRequest),
HeaderError::ExpectInvalid => Some(StatusCode::BadRequest),
_ => None, // Contextually, there are more which end up becoming InternalServerError.
}
}
This could be re-written as such:
pub fn associated_status_code(&self) -> Option<StatusCode> {
match self {
_::SpecificityInvalid => Some(_::BadRequest),
_::DateInvalid(_) => Some(_::BadRequest),
_::TransferEncodingUnnegotiable => Some(_::NotAcceptable),
_::TransferEncodingInvalidEncoding(_) => Some(_::BadRequest),
_::TraceContextInvalid(_) => Some(_::BadRequest),
_::ServerTimingInvalid(_) => Some(_::BadRequest),
_::TimingAllowOriginInvalidUrl(_) => Some(_::BadRequest),
_::ForwardedInvalid(_) => Some(_::BadRequest),
_::ContentTypeInvalidMediaType(_) => Some(_::BadRequest),
_::ContentLengthInvalid => Some(_::BadRequest),
_::AcceptInvalidMediaType(_) => Some(_::BadRequest),
_::AcceptUnnegotiable => Some(_::NotAcceptable),
_::AcceptEncodingInvalidEncoding(_) => Some(_::BadRequest),
_::AcceptEncodingUnnegotiable => Some(_::NotAcceptable),
_::ETagInvalid => Some(_::BadRequest),
_::AgeInvalid => Some(_::BadRequest),
_::CacheControlInvalid => Some(_::BadRequest),
_::AuthorizationInvalid(_) => Some(_::BadRequest),
_::WWWAuthenticateInvalid(_) => Some(_::BadRequest),
_::ExpectInvalid => Some(_::BadRequest),
_ => None, // Contextually, there are more which end up becoming InternalServerError.
}
}
Guide-level explanation
When using an enum type in an expression or match arm, the type name of the enum can be replaced with _
if the type name is already concrete and known from elsewhere.
Examples
enum CompassPoint {
North,
South,
East,
West
}
let mut direction = CompassPoint::West;
direction = _::East;
match direction {
_::East => { ... }
_::West => { ... }
_ => { ... }
}
matches!(direction, _::South | _::North);
fn function(cp: CompassPoint) {}
let direction = _::West;
function(direction);
function(_::South);
Reference-level explanation
When using an enum type in an expression or match arm, the type name of the enum can be replaced with _
if the type name is already concrete and known from elsewhere.
(TODO: write this part up with more specifics...)
Drawbacks
For users of Rust who do not use an IDE or an editor with a tool such as rust-analyzer, or, for those viewing code hosted online, such as on GitHub, it may be less clear what the type in question is, especially if it originates far away from the code doing the match.
Rationale and alternatives
Enum variants can already be imported directly, by e.g. use MyEnum::*;
, however this can only import at module scope, causing name pollution and possible type name clashes (especially since enum variants are often named very generically).
The placeholder _
is chosen as it is already widely used in other places to indicate inferred types (such as in let v = Vec<_> = iter.collect();
) and also lifetimes, and does not cause any conflicts in this position.
As an alternative, it may be possible the _
could be dropped and just the leading ::
used instead.
The [Swift language's similar feature][Swift] uses just the .
for this same purpose, with the leading type name omitted.
It is also possible that this feature would be deemed too unclear in enough situations as described in drawbacks.
Prior art
Inference in other contexts
Inference for type names in other contexts is already very widely used in Rust. As example, it is especially common to infer the inner type of the indicated container when using the Iter::collect()
function. However, it does not appear in a position elsewhere where it is followed by more specific information (the variant of an enum in this case).
Swift
A nearly identical feature exists in the Swift programming language, as described in its reference. In fact, the short hand seems to be the suggested way of writing match statement statements in Swift.
enum CompassPoint {
case north
case south
case east
case west
}
var direction = CompassPoint.west
direction = .east // Type is known here so we can omit
Unresolved questions
- Is this too unclear in plain text editors, especially when viewed on GitHub?
Future possibilities
Should the _
be deemed unnecessary in the future, it could be dropped as an additive feature.
This is resulting from these two rust rfcs issues:
I'm hoping to get this up on rust-lang/rfcs in, let's say, under a week. Please let me know if there any egregious issues with this RFC or if somehow ::Variant
would be preferable and easily possible.