In many languages, variable shadowing is restricted to various degrees, or not at all. To me, unrestricted variable shadowing seems like a classic "footgun" in that, while a savvy programmer isn't likely to cause problems for themselves or others with it, it can easily lead to readability issues as well as confusion in actually writing further code when used hastily. I thought to ask this question after taking several minutes to read code which shadowed a function argument about five indents deep, then used the shadowing variable down another four or so--clearly a problem of its own, but one that may have been addressed if the compiler complained about it. I specify "in the general case" because there are cases in which shadowing is "obviously" desirable:
- Function-internal names shadowing exported names. Naming things is hard, and you definitely want to have good names for what you expose, but that shouldn't have to get in the way of the innards, especially if there's a qualified-name mechanism for accessing the shadowed stuff on the off chance you need it anyhow. This can arguably still create readability concerns, but you also don't want to have to resolve conflicts with internal variable names when you add new exposed functionality.
- It's conceptually "the same thing". Specifically, either the shadowed variable is downcast (hopefully conditionally) to the shadowing variable, or the shadowing variable is extracted from some kind of optional/nullable/monadic-error type. This could either be allowed explicitly by adding some kind of structural constraint on the right-hand side of a shadowing initialization, by subsuming it into a dedicated language feature like Kotlin's smart casts or Swift's bare
if let, or by encouraging the use of functional constructs alongside the next bullet point.
- Inner function parameters. Even ignoring the case of a language where inner functions are not closures, inner functions can often work with similar concepts to but different data than what's using them, and when all of the shadowing risks are collected in one place for a probably-not-too-large scope, it ought to be harmless if not strictly necessary.
Additionally, there's the borderline case of argument preprocessing, where some transformation is applied to an argument of a function at or near the top of the function body--whether it's to apply a default value, normalize some property, etc. This feels like a problem best solved with more descriptive variable names and/or moving logic into internal helper functions, but overly long variable names can be clunky to throw around, and internal helper functions seem to generally not be considered idiomatic in languages that don't have features to actively encourage them, like Haskell's
In a sense, this is really asking about if these exceptions are adequate to permit all "legitimate" shadowing, and how they intersect with other facets of language design. Nevertheless, what are the simple pros and cons of general variable shadowing?