+= operator appears to modify frozen string - ruby

I am using ruby freeze method. As far as the definition of freeze is considered, it freezes the value of the object on which it is called. We can not modify the value of that object after it. I have to achieve same task, I have a an object and I am executing following code
a = "Test"
a.freeze
a += "this string"
puts a
This gives outputs as follows:
Test this string
[Finished in 0.0s]
Why it is modifying my frozen string?

Nothing is modifying your frozen String
You are re-assigning a to a new String with
a += "this string"
which is internally the same in Ruby as
a = a + "this string"
When you add two String objects in Ruby, it will create a new String containing the result (this is normal behaviour for + operator on most objects that support it). That leaves the original "Test" and "this string" values unchanged. The original, frozen String (containing "Test") will remain in memory until it is garbage collected. It can be collected because you have lost all references to it.
If you attempted to modify the object in place like this:
a << "this string"
then you should see an error message RuntimeError: can't modify frozen String
Basically, you have confused a, the local variable, with the String object to which it is pointing. Local variables can be re-assigned at any time, independently of the objects stored by Ruby. You can verify this is what has happened in your case by inspecting a.object_id before and after your a +=... line.

The freeze method prevents you from changing an object, it turns an object into a constant.
s1 = "its testing"
s1.freeze
puts "Object ID ===", s1.obejct_id
So, after freezing an object, an attempt to modify it results in TypeError.
s1 << "testing again"
it will give, RuntimeError: can't modify frozen String
BUT,
freeze operates on an object reference, not on a variable
s1 += "New Testing"
puts "Object ID ===", s1.obejct_id
will point to evaluated to a new object and also check its object ID.
For detailed information refer this site,
http://rubylearning.com/satishtalim/mutable_and_immutable_objects.html

Related

Does 'upcase!' not mutate a variable in Ruby?

I'm just trying to make sure I understand what's happening here. I get that += is reassignment so maybe that's why str isn't modified but why doesn't upcase! modify str here either?
def change_me(str)
str += "?"
str.upcase!
end
question = "whats your name"
change_me(question)
puts question
'whats your name'
=> nil
Does 'upcase!' not mutate a variable in Ruby?
It is impossible for a method to mutate a variable. Ruby is an object-oriented language, so methods can mutate objects (more precisely, a method can mutate its receiver), but variables aren't objects in Ruby. (Like most other languages as well, there is almost no language where variables are objects.)
The only way to mutate a variable is through assignment. Note that we generally don't talk about "mutating" variables, we talk about "re-binding" or "re-assigning".
I'm just trying to make sure I understand what's happening here. I get that += is reassignment so maybe that's why str isn't modified but why doesn't upcase! modify str here either?
Again, you are confusing variables and objects. upcase! modifies the object that is referenced by str, but it does not modify str.
It sounds like you expect Ruby to be a pass-by-reference language, but it is not. Ruby is purely pass-by-value, always, no exceptions. More precisely, the value being passed is an unmodifiable, unforgeable pointer to an object.
Here's what happens, following the flow of execution:
question = "whats your name"
The string literal "whats your name" is evaluated, resulting in a String object with the content whats your name.
The local variable question is initialized with an immutable, unforgeable pointer to the string object created in step #1.
change_me(question)
The local variable question is dereferenced, resulting in the immutable, unforgeable pointer to the string object created in step #1.
A copy of that pointer is made.
The copy from step #4 is placed into the argument list of the call to change_me
str += "?"
Inside of the change_me method body, the parameter binding str is bound to the copied immutable unforgeable pointer from step #4 and #5.
This line desugars into str = str + "?", so what happens is:
str is dereferenced, resulting in the copied immutable, unforgeable pointer from step #4, #5, and #6.
We follow the pointer and send the message + to the object with an immutable, unforgeable pointer to the string object created by evaluating the string literal "?" as an argument.
String#+ returns a new string (or, more precisely, an immutable, unforgeable pointer to a new string).
str is re-bound to the new immutable, unforgeable pointer returned by the call to str+("?").
str.upcase!
str is dereferenced, resulting in the new immutable, unforgeable pointer from step #7c #7d.
We follow the pointer and send the message upcase! to the object.
String#upcase! will mutate the receiver object (in this case, the newly created string from step #7c) to make all letters uppercase.
String#upcase! will return either an immutable, unforgeable pointer to the receiver object itself (i.e. the pointer that was used to call the method) if it did any changes to the receiver, or it will return an immutable, unforgeable pointer to the object nil if the string was already uppercase or didn't contain any letters.
Back to change_me(question)
This return value, however, is just ignored, it is thrown away, it is not printed, not assigned to a variable, not passed as an argument to a different method, not stored in a data structure.
puts question
Okay, I will save the details now that the variable is dereferenced, etc.
The crucial part is: the variable question was never touched, it was never re-assigned, so it still contains the exact same thing it contained the whole time: the immutable, unforgeable pointer to the string object from steps #1 and #2.
We assigned this object to the variable, and we:
never re-assigned the variable, so the variable still points to the same object
never asked the object to mutate itself, so the contents of the object are still the same
Therefore, the object is still unchanged, and the variable still points to the same object, and thus we get the result that nothing has changed.
We changed the binding for the str parameter binding inside of the change_me method, but that binding is local to the method. (Parameter bindings are effectively equivalent to local variables.) Therefore, it ceased to exist the moment the method returned.
And we changed the newly created string object, but since we never obtained a pointer to this object, there is no way that we can reach it. One pointer was stored in str, but that is gone. Another pointer was returned from change_me, but we threw that away, so that is gone, too. Since there is no reference this string object, the object is unreachable.
In fact, the change_me method doesn't do anything at all that can be observed from the outside. It creates a string object, then mutates it, but no reference to this object ever leaves the method. Therefore, it is as good as if the mutation never happened, and the string object never existed in the first place.
In fact, a sufficiently clever compiler would be able to optimize your entire code to this:
puts "whats your name"
when you are doing str += "?" you are creating a new string, so str points to a different string than the one you are passing as an argument.
What you are doing is essentially this:
def change_me(str)
new_str = str + "?"
new_str.upcase!
end
That is why your previous string is not being changed. If you want the function to have side effects, you should do:
def change_me(str)
str << "?"
str.upcase!
end
However, I think modifying strings in place is a bit questionable. I think it would be safer to return a new string and overwrite your reference if needed.
Let's see if I can boil all this down a bit for you. First, have a careful look at Mario's "what you are doing is essentially this" code example. Understand that you are calling your #upcase! method on an entirely new object, since you reassigned str to a new object when you tried to tack a ? onto it.
Now, have a look at this:
def change_me(str)
str.upcase!
42
end
x = 'hello'
puts x # => hello
change_me(x)
puts x # => HELLO
As you can see, this code returns 42. Now, as Douglas Adams has told us, 42 is the meaning of life. But if so, the meaning of life is entirely irrelevant here, because as Jörg has been trying to explain to you, you don't do anything with the return value of your method call.
You will also see that your str object does get mutated here. That's because in this case, you haven't reassigned the str variable to a different object inside your method, as your code does. (Again, look carefully at Mario's first example.)
Now, if, in your method, you want to tack something onto the end of the object that you send into your method, you need to use << instead of +. Look at Mario's second code example, and give that a try.
To dig down into this and learn it thoroughly, the #object_id method is very useful. Try running this code:
def change_me(str)
p str.object_id
str += "?"
p str.object_id
str.upcase!
p str.object_id
end
def change_me_2(str)
p str.object_id
str << "?"
p str.object_id
str.upcase!
p str.object_id
end
If you spend some time evaluating object ids, you'll sort this out for yourself pretty quickly.
Finally, I second Mario's point of view that modifying strings in place is a bit questionable in practice. Unless there's some reason that you can't do it this way, I would do this:
def change_me(str)
str.upcase + '?'
end
And then:
question = "what's your name"
question = change_me(question)
Or simply:
question = change_me("what's your name")
Finally, here's a little quiz. Take your code and change the way you call it so:
def change_me(str)
str += "?"
str.upcase!
end
question = "whats your name"
puts change_me(question)
Why does this do what you intended? Now, change str.upcase! to str.upcase, and you will see that it also does what you intended. Why doesn't it make any difference whether you use the ! or not?

What does it mean that strings are "mutable" and why care?

I have been using Ruby on Rails for a while without studying Ruby, now I am taking Odin Project. I am not really sure about the answer of this question:
What does it mean that strings are "mutable" and why care?
Update:
so now I understand mutable string basically means the value in the memory can be changed after string is created.
immutable string means the value in the memory cannot be changed once created, only the reference can be changed.
based on the result of following code:
a = "foo"
a.object_id
=> 70218039369160
b = "bar"
a << b
=> "foobar"
a.object_id
=> 70218039369160
can I say string in Ruby is mutable? because the value in same memory is changed
a += b
=> "foobar"
a.object_id
=> 70218039184800
and the + method in Ruby actually create a new String object instead of change the value of the original String object, that's why the object id changed.
my question is will it cause any security problem if I use += and << interchangeably?
It means that you can modify an exising instance of a string, without constructing a new one. Consider the following code:
str1 = "foo"
str2 = str1
str1 += "bar"
In languages like javascript where strings are immutable, the value of str2 will still be "bar" after that code is executed, as you can see here. However, in languages where strings are mutable, like ruby, when you append "bar" at the end of str1 (using the ruby operator << which does that) you are actually modifying the instance, not creating a new one, so str2 will also be modified, as you can see here.
PS: Note that the append at the end of the string operator in ruby is << instead of += (+= actually creates a new string, but not because it's forced to do it, like in javascript).

Exclamation mark and assignment inside of a function

Can someone explain why this:
def do_something str
str = "bar"
end
​
str_main = "foo"
do_something str_main
​
puts str_main
displays foo?
And this:
def do_something str
str.capitalize!
end
​
str_main = "foo"
do_something str_main
​
puts str_main
displays Foo?
Because of the way Ruby passes arguments.
When the method is being called, you have two references, str_main and str, to the same object "foo".
In the first example, when you use str = "bar", you are just changing what the str reference points to. So now you have str_main -> "foo" and str -> "bar". Therefore, the original object is not changed.
In the second example, you didn't change the str reference and changed the string in place with a mutator method, thus changing the same object that str_main points to.
The exclamation mark or bang operator modifies the original value. It is a destructive method. For example, let's say you had a string
string = "hi";
If you call the upcase method, you will get the following
string.upcase
=> "HI"
However, if you call string again, you will get the initial value.
string
=> "hi"
Now, let's say you use the destructive method upcase!
string.upcase!
=> "HI"
Now, if you call string again, you will see that the value was mutated.
string
=> "HI"
In Ruby, references are passed by value. So, a reference to str_main is passed to method do_something, a copy of reference is present in variable str.
This, however, does not mean that value that is referred to by both variables also has been copied around - there is still a single copy of referred to value, which is the string defined in Main.
Hence, when you assign a new value to str, this does not alter the value of str_main. However, when you modify the value that is referred by str, its changes are visibble outside.
All ruby methods return the last thing evaluated. However, object assignment stays within the scope of the current code block. Assigning str_main to a new value within a method will not affect str_main, unless it was an instance variable (#str_main). Doing such allows you to reassign an object across scopes, or depths, of your program. This is why your first method outputs 'foo' instead of 'bar'.
Now, the second example. #capitalize is a method called on a string object. It returns a new String instance, where its value is original object capitalized.
string = 'foobar'
string.capitalize # => 'Foobar'
puts string # => 'foobar'
Notice how string is only modified temporarily, and when called again it is back to normal.
Many methods in ruby have counterparts ending in !. This convention is the same as: object = object.some_method. Instead of creating a new instance of an object, these methods edit the original object's value. In the case of #capitalize!, the string is capitalized and modified for future calls.
string = 'foo'
string.capitalize! # => 'Foo'
puts string # => 'Foo'
Back to your second example. Using the #capitalize! method within the scope of do_something allows you to modify the str_main object. In a similar way to making str_main an instance variable.

Ruby variables referencing other variables

I have two variables, one (b) that references the other (a). When I modify a with a method, b is also modified:
a = "TEXT"
b = a
print b
#=> TEXT
a.downcase!
print b
#=> text
However, when I modify a directly, b retains its value:
a = "TEXT"
b = a
print b
#=> TEXT
a = "Something Else"
print b
#=> TEXT
Why is the behavior of b different when the variable it initially referenced is modified directly as opposed to by a method?
Is this an improper thing to do in Ruby, and if so, what would a better practice be for referencing one variable with another?
Ruby works with references, and you are making a little mistake in there.
This:
a.downcase!
as the 'bang' method suggests, is changing the value referenced by a.
So a is still the referencing the same object, which was just changed by the downcase! method
But this:
a = "Something Else"
is actually saying to a to reference a new object which happens to also be a string.
Since b was referencing another object and that object didn't changed, it still prints TEXT.
You can use the object_id to see what is going on here.
a = "text"
a.object_id
=> 70200807828580
b = a
b.object_id
=> 70200807828580 # b points to the same object that a does.
a = "new"
a.object_id
=> 70200807766420 # a now points to a new object
b.object_id
=> 70200807828580 # b still points to the original object.
So you see that the variable actually doesn't store the object itself. Instead it stores the id of the object. That's why if you copy an object you usually just copy the id of it rather than creating a whole new object.

Changing value of ruby variables/references

I just stumbled upon something i don't quite understand. I know that variables in ruby are references. So that awesome stuff is possible. But when i pass a variable to a method, it behaves strangely:
my_var_a = "nothing happend to me"
my_var_b = "nothing happend to me"
def parse_set(my_var_set)
my_var_set = "my value changed"
end
def parse_sub(my_var_sub)
my_var_sub.sub! /(.*)/, "my value changed"
end
parse_set(my_var_a)
parse_sub(my_var_b)
my_var_a # => "nothing happend to me"
my_var_b # => "my value changed"
Can you explain to me why it works with sub! and = leaves the object unchanged? How can I avoid to use sub! but having the same result?
my_var_a and my_var_set are different references, but they point at the same object. If you modify the object in my_var_set, the change shows up in my_var_a. However, if you repoint my_var_set at a new object, that doesn't change what my_var_a points at.
Edit: clarification...
What Ruby does is called passing references by value. When you say
my_var_a = "nothing happend to me"
Ruby saves the string "nothing happend to me" in a memory location (let's call it 1000), and saves the my_var_a reference in another memory location (let's say 2000). When your code uses my_var_a, the interpreter looks at location 2000, see that it points to 1000, then gets the actual string value from 1000.
When you call parse_set(my_var_a), Ruby actually creates a new reference named my_var_set and points it to the string that my_var_a was pointing at (memory location 1000). However, my_var_set is a copy of the my_var_a reference -- let's say my_var_set was created at memory location 3000. my_var_a and my_var_set are 2 completely different references in memory, they just happen to point at the same exact memory location which holds the string value.
The statement my_var_set = "my value changed" in parse_set creates a new string in memory and points my_var_set at that new memory location. However, this doesn't change what the original my_var_a reference points at! Now that my_var_set points at a different memory location, nothing that you do to that variable will affect my_var_a.
The same reference copy happens for parse_sub as well. But the reason that parse_sub changes the string is because you're calling a method directly on the my_var_sub reference. When you do this, the interpreter gets the object that my_var_sub is pointing at and then modifies it. So that change will show up in the my_var_a reference, because it still points at the same string.
irb(main):008:0* a = 'abc'
=> "abc"
irb(main):009:0> a.replace('def')
=> "def"
irb(main):010:0> a
=> "def"
I've had to use replace exactly zero times in all the years I've been programming in Ruby. I wonder if there's a better design for your code.
Most string methods which change the receiver are suffixed by ! (sub!, capitalize!, chomp!, etc.) Some that change the receiver are not suffixed by ! (replace is one). If you call a method that changes the receiver, any and all references to that object will see the change. if you call a method that does not change receiver, the method returns a new string.
gsub does not modify the receiver, but instead returns a new instance of String:
a = "foo"
b = a
p a.sub(/o/, '#') # "f##"
p a # => "foo"
p b # => "foo"
gsub! does modify the receiver:
a = "foo"
b = a
p a.sub!(/o/, '#') # => "f#o"
p a # => "f#o"
p b # => "f#o"
"How can I avoid to use sub! but having the same result?"
def parse_set()
"my value changed"
end
a = parse_set()
Set it as an instance variable of an object (although this works just in your main script, I recommend making your own class if you want to use the instance variables approach).
#my_var_a = "nothing happend to me"
def parse_set()
#my_var_a = "my value changed"
end

Resources