Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why are Ruby global strings, like $&, ignoring mutations without error?

I'm learning Ruby (2.0) and this just surprised me:

s = "1234"
s =~ /\d+/
$&  ==> "1234"       # as expected, $& contains the matched string
$&.slice!(-2..-1)    # should mutate string
$&  ==> "1234"       # what?
s.slice(-2..-1) 
s  ==> "12"          # as expected

The slice! method is supposed to mutate the string. Other mutator methods behave in the same way. My questions: why is this not throwing an error, which is what I expect when a function can't do what it says it will do? Is this documented somewhere? Is there a rationale?

Update

So, I see that $& is not acting like a global variable. Each reference to it gives a new object, as if it's really a no-arg function:

irb> $foo = "1234"
=> "1234"
irb> $foo.object_id
=> 70205012205980
irb> $foo.object_id
=> 70205012205980   # the same
irb> $&.object_id
=> 70205003531300
irb> $&.object_id   
=> 70205011619040   # different object

So... my question becomes: is this simply "magic" from the interpreter, or is $& actually a no-arg function just as I could define in Ruby using def ... end? And, how could I tell the difference? In Python I could refer to a function foo by just using it's name:

>>> foo
<function foo at 0x10d3117d0>

Is there way to do this in Ruby? I could then look at what $& "really" is (if it's not magic).

like image 456
Rob N Avatar asked Feb 28 '13 17:02

Rob N


People also ask

Should I use global variables in Ruby?

Global variables should be used sparingly. They are dangerous because they can be written to from anywhere. Overuse of globals can make isolating bugs difficult; it also tends to indicate that the design of a program has not been carefully thought out.

How do you use a global variable in Ruby?

Assignments to global variables can be made from anywhere in the program. Global variables are always prefixed with a dollar sign. It is necessary to define a global variable to have a variable that is available across classes. When a global variable is uninitialized, it has no value by default and its use is nil.


1 Answers

The Ruby C API includes hooked and virtual variables. From README.EXT:

You can defined hooked variables. The accessor functions (getter and setter) are called on access to the hooked variables.

void rb_define_hooked_variable(const char *name, VALUE *var,
           VALUE (*getter)(), void (*setter)())

If you need to supply either setter or getter, just supply 0 for the hook you don't need. If both hooks are 0, rb_define_hooked_variable() works just like rb_define_variable().

The prototypes of the getter and setter functions are as follows:

VALUE (*getter)(ID id, VALUE *var);
void (*setter)(VALUE val, ID id, VALUE *var);

Also you can define a Ruby global variable without a corresponding C variable. The value of the variable will be set/get only by hooks.

 void rb_define_virtual_variable(const char *name,
            VALUE (*getter)(), void (*setter)())

The prototypes of the getter and setter functions are as follows:

VALUE (*getter)(ID id);
void (*setter)(VALUE val, ID id);

$& is an example of a virtual variable, it is defined in re.c, the corresponding getter is last_match_getter and there is no assocoated setter.

So $& is, in a sense, a no-arg function, only it is implemented in C. You can’t (as far as I’m aware) define your own virtual globals like this in pure Ruby (you can if you create your own C extension).

like image 185
matt Avatar answered Nov 13 '22 10:11

matt