How Bad Is Redefining/Shadowing a Local Variable

How bad is redefining/shadowing a local variable?

I noticed there were a lot of errors where a local variable was redefined inside a function for example.

You are not demonstrating redefining here. You show an example of variable shadowing.

Variable shadowing is not an error syntactically. It is valid and well defined. However, if your intention was to use the variable from the outer scope, then you could consider it a logical error.

but can the compiler really mess things up

No.

The problem with shadowing is that it can be hard to keep track of for the programmer. It is trivial for the compiler. You can find plenty of questions on this very site, stemming from confusion caused by shadowed variables.

It is not too difficult to grok which expression uses which variable in this small function, but imagine the function being dozens of lines and several nested and sequential blocks. If the function is long enough that you cannot see all the different definitions in different scopes at a glance, you are likely to make a misinterpretation.

declaration of 'count' hides previous local declaration 

This is a somewhat useful compiler warning. You haven't run out of names, so why not give a unique name for all local variables in the function? However, there is no need to treat this warning as an error. It is merely a suggestion to improve the readability of your program.

In this particular example, you don't need the count in the outer scope after the inner scope opens, so you might as well reuse one variable for both counts.

Is it worth it to change and fix the variable names

Depends on whether you value more short term workload versus long term. Changing the code to use unique, descriptive local variable names is "extra" work now, but every time someone has to understand the program later, unnecessary shadowing will increase the mental challenge.

What is the problem with shadowing names defined in outer scopes?

There isn't any big deal in your above snippet, but imagine a function with a few more arguments and quite a few more lines of code. Then you decide to rename your data argument as yadda, but miss one of the places it is used in the function's body... Now data refers to the global, and you start having weird behaviour - where you would have a much more obvious NameError if you didn't have a global name data.

Also remember that in Python everything is an object (including modules, classes and functions), so there's no distinct namespaces for functions, modules or classes. Another scenario is that you import function foo at the top of your module, and use it somewhere in your function body. Then you add a new argument to your function and named it - bad luck - foo.

Finally, built-in functions and types also live in the same namespace and can be shadowed the same way.

None of this is much of a problem if you have short functions, good naming and a decent unit test coverage, but well, sometimes you have to maintain less than perfect code and being warned about such possible issues might help.

Local variables name collision

You've "shadowed" the earlier definition, so it's basically gone. If you need access to it, pick a different name. As far as the compiler is concerned, within that inner block there's only one i and it has no idea about the other i that it replaced.

Either give it a better name:

int i = 0;
{
int ii;
i = 10;
}
cout << i; // prints 10

Or use a function:

int i = 0;
f(i);
cout << i; // Depends on what f() does.

Where you have:

void f(int& i) {
i = 10;
}

Which will modify the original i.

Is it well defined to access a variable from an outer scope before it is redefined?

Is this well defined or it just happened to work?

It is well-defined. The scope of a variable declared inside a {...} block starts at the point of declaration and ends at the closing brace. From this C++17 Draft Standard:

6.3.3 Block scope      [basic.scope.block]

1     A name declared in a block (9.3) is local to that
block; it has block scope. Its potential scope begins at its point of
declaration (6.3.2) and ends at the end of its block. A variable
declared at block scope is a local variable.


This code compiles with no warnings in gcc-11

That surprises me. The clang-cl compiler (in Visual Studio 2019, 'borrowing' the /Wall switch from MSVC) gives this:

warning : declaration shadows a local variable [-Wshadow]

Using both -Wall and -Wpedantic in GCC 11.2 doesn't generate this warning; however, explicitly adding -Wshadow does give it. Not sure what "general" -Wxxx switch GCC needs to make it appear.

What is variable shadowing used for in a Java class?

The basic purpose of shadowing is to decouple the local code from the surrounding class. If it wasn't available, then consider the following case.

A Class Foo in an API is released. In your code you subclass it, and in your subclass use a variable called bar. Then Foo releases an update and adds a protected variable called Bar to its class.

Now your class won't run because of a conflict you could not anticipate.

However, don't do this on purpose. Only let this happen when you really don't care about what is happening outside the scope.

Why doesn't assignment to variable y work in a Ruby block?

The source of confusion here is so-called shadowing. You have two local variables called x here, but only one called y. The second x variable is shadowing the first one - meaning that any attempt to access or assign to the first variable is doomed to fail.

When you do ary.each do |x|, you are creating new variable, which has nothing (except for the name) to do with the variable x you have created in the outer scope.

y on the other hand is not shadowed and the loop binding is accessing (and assigning to) the variable defined in the parent scope. Unfortunately there is no way (at least that I am aware of) of explicit local variable creation like var/let/const in javascript.

In short, your code executes as follow:

x = 1               
y = 1
ary = [1, 2, 3]

ary.each do |x2|
y = x2
end

p [x, y]

So in fact, assignment to y works just as expected. You assign it value of 1 before the block, and then you assign 3 more times within the block. The last assignment is y=3 so that's its final value.

BEWARE, THE HACK:

Well there actually is a way of forcing the local variable creation, and it is to add an extra yielding argument to each block. This should never be done in the actual code, and I only present it for completeness. You should actually avoid any variable shadowing whenever possible.

x = y = 1
ary = [2,3,4]

ary.each do |x, y| # this creates new local variables x and y, y is always set to nil for each iteration.
y = x # y here is a shadowing variable, not the y of the parent scope
puts [x,y].inspect
end

puts [x,y].inspect

# OUTPUTS:

[2, 2]
[3, 3]
[4, 4]
[1, 1]

Does Rust free up the memory of overwritten variables?

Rust does not have a garbage collector.

Does Rust free up the memory of overwritten variables?

Yes, otherwise it'd be a memory leak, which would be a pretty terrible design decision. The memory is freed when the variable is reassigned:

struct Noisy;
impl Drop for Noisy {
fn drop(&mut self) {
eprintln!("Dropped")
}
}

fn main() {
eprintln!("0");
let mut thing = Noisy;
eprintln!("1");
thing = Noisy;
eprintln!("2");
}
0
1
Dropped
2
Dropped

what happens with the first hello

It is shadowed.

Nothing "special" happens to the data referenced by the variable, other than the fact that you can no longer access it. It is still dropped when the variable goes out of scope:

struct Noisy;
impl Drop for Noisy {
fn drop(&mut self) {
eprintln!("Dropped")
}
}

fn main() {
eprintln!("0");
let thing = Noisy;
eprintln!("1");
let thing = Noisy;
eprintln!("2");
}
0
1
2
Dropped
Dropped

See also:

  • Is the resource of a shadowed variable binding freed immediately?

I know it would be bad to name two variables the same

It's not "bad", it's a design decision. I would say that using shadowing like this is a bad idea:

let x = "Anna";
println!("User's name is {}", x);
let x = 42;
println!("The tax rate is {}", x);

Using shadowing like this is reasonable to me:

let name = String::from("  Vivian ");
let name = name.trim();
println!("User's name is {}", name);

See also:

  • Why do I need rebinding/shadowing when I can have mutable variable binding?

but if this happens by accident because I declare it 100 lines below it could be a real pain.

Don't have functions that are so big that you "accidentally" do something. That's applicable in any programming language.

Is there a way of cleaning memory manually?

You can call drop:

eprintln!("0");
let thing = Noisy;
drop(thing);
eprintln!("1");
let thing = Noisy;
eprintln!("2");
0
Dropped
1
2
Dropped

However, as oli_obk - ker points out, the stack memory taken by the variable will not be freed until the function exits, only the resources taken by the variable.

All discussions of drop require showing its (very complicated) implementation:

fn drop<T>(_: T) {}

What if I declare the variable in a global scope outside of the other functions?

Global variables are never freed, if you can even create them to start with.



Related Topics



Leave a reply



Submit