What Does "Shadowing" Mean in Ruby

What does shadowing mean in Ruby?

Shadowing is when you have two different local variables with the same name. It is said that the variable defined in the inner scope "shadows" the one in the outer scope (because the outer variable is now no longer accessible as long as the inner variable is in scope, even though it would otherwise be in scope).

So in your case, you can't access the outer x variable in your block, because you have an inner variable with the same name.

Behaviours of a Ruby local variable shadowing an instance method

What looks like inconsistent return values for name during runtime and while debugging doesn't seem to related to Pry, but more about binding itself encapsulating the entire execution context of a method, versus the progressive change in what shadowed variables reference at runtime. To build on the example method with some more debugging code:

def say_name
puts "--- Before assignment of name: ---"
puts "defined?(name) : #{defined?(name).inspect}"
puts "binding.local_variable_defined?(:name) : #{binding.local_variable_defined?(:name).inspect}"

puts "local_variables : #{local_variables.inspect}"
puts "binding.local_variables : #{binding.local_variables.inspect}"

puts "name : #{name.inspect}"
puts "binding.eval('name') : #{binding.eval('name').inspect}"

if name.nil?
name = "Unknown"
end

puts "--- After assignment of name: ---"
puts "defined?(name) : #{defined?(name).inspect}"
puts "binding.local_variable_defined?(:name) : #{binding.local_variable_defined?(:name).inspect}"

puts "local_variables : #{local_variables.inspect}"
puts "binding.local_variables : #{binding.local_variables.inspect}"

puts "name : #{name.inspect}"
puts "binding.eval('name') : #{binding.eval('name').inspect}"

puts "My name is #{name.inspect}"
end

Now, running Person.new("Paul").say_name outputs:

--- Before assignment of name: ---
defined?(name) : "method"
binding.local_variable_defined?(:name) : true
local_variables : [:name]
binding.local_variables : [:name]
name : "Paul"
binding.eval('name') : nil
--- After assignment of name: ---
defined?(name) : "local-variable"
binding.local_variable_defined?(:name) : true
local_variables : [:name]
binding.local_variables : [:name]
name : nil
binding.eval('name') : nil
My name is nil

which shows that binding never references the method call of name and only ever the eventually-assigned name variable.

What is Shadowing outer local variable in rubocop and how do I fix this?

This means that the user provided as a block arguments will overwrite the user variable defined here user = User.where(email: auth.info.email).first

To overcome it you'll need to change the name of one of the variables.
Either something like:

result = User.where...
return result if result

Or:

where(provider: auth.provider, uid: auth.uid).first_or_create do |u|
u.fullname = auth.info.name
...
end

Some more info: https://github.com/bbatsov/ruby-style-guide#no-shadowing

What is Shadowing outer local variable in rubocop and how do I fix this?

This means that the user provided as a block arguments will overwrite the user variable defined here user = User.where(email: auth.info.email).first

To overcome it you'll need to change the name of one of the variables.
Either something like:

result = User.where...
return result if result

Or:

where(provider: auth.provider, uid: auth.uid).first_or_create do |u|
u.fullname = auth.info.name
...
end

Some more info: https://github.com/bbatsov/ruby-style-guide#no-shadowing

Ruby way of avoiding shadowing for finds

I would prefer making the variable on the left hand side more descriptive:

blue_box = boxes.find { |box| box.color == BLUE }

Even something more generic like found_foo I would consider to be more descriptive.

Shadowing a top-level constant within a binding

ERB#result takes an optional binding:

require 'erb'

class Foo
ENV = { 'RUBY_VERSION' => '1.2.3' }
def get_binding
binding
end
end

template = "Ruby version: <%= ENV['RUBY_VERSION'] %>"

ERB.new(template).result
#=> "Ruby version: 2.1.3"

b = Foo.new.get_binding

ERB.new(template).result b
#=> "Ruby version: 1.2.3"

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]

Avoid temporary variables by using name shadowing

This style is very common:

let nums = another
. bunch
. of_
. functions
. a
. bunch
. of_
. functions
$ [1..10]

It clearly delineates the code; while . serves the place of the temporary variable name.

And it avoids the dangerous issues that can happen when you start shadowing variable names -- accidentally referring to the wrong x will get you into trouble sooner or later.



Related Topics



Leave a reply



Submit