The problem with languages that allow finer scoping (declare inside the code for a single block) is that I always have the feeling the lie to me.
Terminologie: "variable is defined" = has a defined value (has been intentionally been assigned the value it currently holds). That is including nil if that was intentionally assigned. (The question as to if/when nil should be allowed is not a scoping issue: Sure, cases can be constructed were lack of scope forces it. But, not all cases can be removed by finer scope.)
First of all: It does not solve the problem. It just shrinks it, so it is less visible.
Even if I take a loop, or wrap any arbitrary bit of code into a begin/end to create a scope: A variable is not necessarily defined at all places in that code (that requires additional work on top of scope).
- "not necessarily" It can be in some cases, but it wont be in all. Hence "shrinking, not solving"
- You could have code with lots of different if/then, and initialization happening inside each conditional block (or does not happen).
- If your answer is, that the var must be assigned when declared => that is not scope related. That could be done (if implemented) in the current procedure wide declaration too, have an initial value for each declared local var / so: not scope)
And then you can't even always define scopes as little as you would need. Scopes for different variables may overlap. begin/end can only be nested, but not overlap.
So in the end, if I look at a language with an inline declared variable, I always face the question: Has the author taken care of it "defined state". Is it defined in all code paths? Even if this var is initialized at declaration (which is not a scope related feature), can I be sure that it at no time is assigned a value that was not defined?
And here defined has to be taken one level further. "Valid". E.g. for an array index: a value that is in the range of the array size. And it may not be enforceable by type either, if the array has variable size.
And the question of "valid" means, that even if all variables must be assigned at the time of declaration (again, not a scope issue), that does not make the value "valid". And at the end of the day, "valid" is what I am concerned about. "defined" alone is not good. It may be good in a subset of cases, but that means "shrinking, not solving".
So as I said: at the end of the day, if a variable is declared inline, I still have to verify if it is valid at the place where it is accessed. I still have to either
trust the Author that they made sure of it (checked the entire scope in which they declared it) or I have to check it myself.
Only argument here is that if I like procedures that are 1000 lines long, then I can use sub-scopes to reduce the code I need to check. But maybe instead of helping with overlong procedures, it should be encouraged to write code in smaller procedures to begin with?
And then the real anti-example is (older) javascript.
https://eslint.org/docs/latest/rules/no-use-before-define They had to have a linter to make sure that inline declarations did not make it worse.
In the end, any code needs to be written and read with care.
IMHO the best way in any language to allow tracking of a variable is to keep the method in which it exists as short as possible. And if that is done, then sub-scopes are just complexity that is not actually needed.
And yes, the famous loop-counter after loop. Well it is the same as any other var that gets initialized only in a subset of the paths the code can take through some branches.
How much would you really gain, if that one particular example would be changed, and the compiler would somehow prevent it. The general problem would still exist. And it would exist with any level of scoping.