70

I have a program that looks like:

$offset = Point.new(100, 200);

def draw(point)
  pointNew = $offset + point;
  drawAbsolute(point)
end

draw(Point.new(3, 4));

the use of $offset seems a bit weird.

In C, if I define something outside of any function, it is a global variable automatically. Why in Ruby does it have to be $offset but cannot be offset and still be global? If it is offset, then it is a local? But local to where, because it feels very much global.

Are there better ways to write the code above? The use of $offset may seem a bit ugly at first.


Update: I can put this offset inside a class definition, but what if two or several classes need to use this constant? In this case do I still need to define an $offset?

1
  • 25
    Coming from C you might not know this, but you don't need to put semi colons on the end of your lines in Ruby. You only need to use ; to separate multiple statements on the same line e.g. "a = 5; b = 10" Commented Jun 25, 2009 at 7:43

4 Answers 4

113

Variable scope in Ruby is controlled by sigils to some degree. Variables starting with $ are global, variables with @ are instance variables, @@ means class variables, and names starting with a capital letter are constants. All other variables are locals. When you open a class or method, that's a new scope, and locals available in the previous scope aren't available.

I generally prefer to avoid creating global variables. There are two techniques that generally achieve the same purpose that I consider cleaner:

  1. Create a constant in a module. So in this case, you would put all the classes that need the offset in the module Foo and create a constant Offset, so then all the classes could access Foo::Offset.

  2. Define a method to access the value. You can define the method globally, but again, I think it's better to encapsulate it in a module or class. This way the data is available where you need it and you can even alter it if you need to, but the structure of your program and the ownership of the data will be clearer. This is more in line with OO design principles.

Sign up to request clarification or add additional context in comments.

Comments

54

One thing you need to realize is in Ruby everything is an object. Given that, if you don't define your methods within Module or Class, Ruby will put it within the Object class. So, your code will be local to the Object scope.

A typical approach on Object Oriented Programming is encapsulate all logic within a class:

class Point
  attr_accessor :x, :y

  # If we don't specify coordinates, we start at 0.
  def initialize(x = 0, y = 0)
    # Notice that `@` indicates instance variables.
    @x = x
    @y = y
  end

  # Here we override the `+' operator.
  def +(point)
    Point.new(self.x + point.x, self.y + point.y)
  end

  # Here we draw the point.
  def draw(offset = nil)
    if offset.nil?
      new_point = self
    else
      new_point = self + offset 
    end
    new_point.draw_absolute
  end

  def draw_absolute
    puts "x: #{self.x}, y: #{self.y}"
  end
end

first_point = Point.new(100, 200)
second_point = Point.new(3, 4)

second_point.draw(first_point)

Hope this clarifies a bit.

2 Comments

+1 for the refactoring. With respect to top-level code being local to the Object scope: that's true for variables without the $ prefix, but, by contrast, methods you put there are global (while also becoming private members of the Object class).
so I think in my case, if all Point objects are with respect to a certain constant offset, then I can have an offset (x, y) as a class constant, so the draw_absolute() will use this class constant as an offset
10

One of the reasons why the global variable needs a prefix (called a "sigil") is because in Ruby, unlike in C, you don't have to declare your variables before assigning to them. The sigil is used as a way to be explicit about the scope of the variable.

Without a specific prefix for globals, given a statement pointNew = offset + point inside your draw method then offset refers to a local variable inside the method (and results in a NameError in this case). The same for @ used to refer to instance variables and @@ for class variables.

In other languages that use explicit declarations such as C, Java etc. the placement of the declaration is used to control the scope.

1 Comment

How does the $ help Ruby know if this is a new variable or an existing one? If you assign something to $foo thinking that it's some existing $foo, but it happens that there isn't any $foo yet, or there was but someone has removed it, then Ruby will create $foo as a new global nevertheless, despite it's not the same $foo you have thought. $ will not repair the lack of proper declarations.
-3

I think it's local to the file you declared offset. Consider every file to be a method itself.

Maybe put the whole thing into a class and then make offset a class variable with @@offset = Point.new(100, 200);?

1 Comment

No, a local variable is not local to a file, but to the innermost block in which it is declared.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.