My experience is that people don't check the definition of the called function unless it's clearly not what they think it is; therefore, if I can write foo(Disabled)?;
, people will not check the type foo
takes, but instead assume it based on their past experience of the foo
function.
My overriding rule is around locality of reasoning - anything that forces me to reason non-locally is problematic by design, since I know that software developers generally aren't great at non-local reasoning (we rely on heuristics like "that's what a similarly named bit of code in a codebase I worked on 10 years ago did"). Applying that overriding rule to implicitness, the general rule I'd like you to apply is that increasing implicitness should not also increase the room for non-local ambiguity.
Taking the foo(Disabled)
example, if I have two enums visible at the point I call foo
, Bar
and Baz
, both of which have a Disabled
variant, you've introduced new non-local ambiguity - I have to look up whether foo
's argument is of type Bar
or type Baz
to determine which variant is in use. On the other hand, if the only enum in scope with a Disabled
variant is Bar
, then foo(Disabled)
is not ambiguous locally. There's a wrinkle here, in that if the file I'm in has a use Bar::Disabled;
, and foo
is fn foo(bar: Bar)
, then the ambiguity is resolved in Bar
's favour, but that at least works when I refactor foo
to take a Baz
, since the resolution is wrong.
This is, FWIW, the same rule for ambiguity that you get if you do use Bar::*; use Baz::*
, and thus there's precedent for this sort of ambiguity handling. Thus, reducing this feature to "if you don't specify the enum variant, Rust will attempt to find the type for you as-if you'd written use _::*
for all enums in scope" would fit my rule, even though I don't like it, because it's not introducing new ambiguity that isn't already one use
statement away. But that then introduces the question of whether this is worth it, given that the programmer can already do use Bar::*; use Baz::*
and get the same effect.
And this also works with my maintenance programmer example - if I split up RadioState
into separate enums for each radio type, plus an overriding state, then all of those enums will have a Disabled
variant. If I then have two of those enums in scope, I can't call set_wifi_state(Disabled)
, since, while the compiler knows that set_wifi_state
takes a WifiState
, it can see that Disabled
could be any of WifiState::Disabled
(thanks to that being in this module), crate::zigbee_radio::ZigbeeState::Disabled
(no use
statements, but this is crate-wide visible, or RadioState::Disabled
(thanks to a use crate::RadioState
in this module), and it'll refuse to continue until I fix up the ambiguity (with an explicit use WifiState::Disabled
, or changing the call to set_wifi_state
, or removing the other enum variants from visibility).