Strings may look only just like a sequence of chars, and one can wrongly assume that there are no major differences between programming languages. But there are!
Even programming languages that show a lot of similarities such as Ruby and Python have nuances when manipulating strings that remark different philosophical views about when an object is still the same object.
Ruby and Python share a lot of similitudes: an easy syntax that is almost read as plain language, they’ve object-oriented programming in mind, and both are dynamically typed languages.
I learned to code with Python, but I became a Full-Stack Web Developer with Ruby. Due to their similitudes, moving from Python to Ruby was quite easy.
Right now, I feel more proficient with Ruby. In my GitHub’s public repositories, Ruby stands out as the most used language in my projects, meanwhile, Python doesn't even figure out in the top five.
In the last month, I’ve decided to dig into Python again, so I’ve been taking different courses to study the fundamentals. When I was doing that, a difference popped up in how to handle strings between Ruby and Python that I didn’t pay attention to before.
A string is a data type used to represent text, which can also contain spaces or numbers.
Both Ruby and Python consider strings as primitive data types, meaning that they are built-in in the programming language. That’s a difference between other programming languages such as Java, where strings are just an array of chars.
In a technical interview, an interviewer remarked to me that “strings are an array of chars.” So, they are iterable. That could be true for other programming languages, but as I was already used to Ruby, I got confused.
Ending the interview, I opened an IRB console and I could assert that a string is not an iterable object in Ruby. They are not an array of chars –even chars are not a primitive data type in Ruby–, so they don’t inherit all the methods that arrays or other iterable objects use.
For example:
# If you try to iterate a string
“string”.each { |char| puts char }
# You get an error message:
`<top (required)>': undefined method `each' for "string":String (NoMethodError)
However, in Python, strings are iterable objects such as lists, tuples, or dictionaries.
# If you try to iterate a string
for char in “string”:
print(char)
# You actually iterate the string:
s
t
r
i
n
g
If you want to iterate a string in Ruby, you should convert the string into an array with the split method.
# We have a string
a_string = “string”
# After splitting the string, we get an array of chars (an array of strings would be more precise, as Ruby doesn’t have chars as data type.
array_of_chars= a_string.split(“”) # => ["s", "t", "r", "i", "n", "g"]
“array_of_chars”.each { |char| puts char }
s
t
r
i
n
g
=> ["s", "t", "r", "i", "n", "g"]
Both Python and Ruby allow indexing and slicing strings, such as arrays. Even the syntax is pretty similar: “string”[0] # Returns “s”
. With Ruby, you slice in this way “string”[0,3] # Returns “str”
, and you can do the same in Python in this way: “string”[0:3] # Returns “str”
.
Do you remember that I said that a string is not iterable in Ruby? Well, maybe you can’t iterate a string, but you can iterate OVER a string taking advantage of its index.
# Let’s create a range (an iterable object) to iterate over a string
(0...string.length).each { |n| puts string[n] }
# So, you get
s
t
r
i
n
g
We can go further and transform the string while iterating over it, but that fits better in the last remarkable difference between Ruby and Python when handling strings.
As I said, we can take advantage of indexing strings to transform them in Ruby, so take the following examples.
# Let’s create a variable with a string and its object_id
string = “Shop of Theseus”
string_id = string.object_id
# Oh, I meaned “ship” there
string[2] = “i”
# The string is updated
puts string
Ship of Theseus
# But, is still the same object?
string_id == string.object_id
We can also append new strings or subtract chars in the string. Even if we replace all the chars with other ones, Ruby considers our string as the same object. In conclusion, strings are mutable objects.
Let’s try the same with Python.
string = “Shop of Theseus”
string[2] = “i”
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'str' object does not support item assignment
Oops! We can’t do the same in Python. If we want to fix that string, we should create another string.
fixed_string = string.replace(“o”, “i”)
# Fixed string is a different object than string.
# Our variable string remains without changes
id(fixed_string) == id(string)
False
print(string)
Shop of Theseus
print(fixed_string)
Ship of Theseus
The replace method is not changing the string object, it’s creating a new string object. For Python, when you do a change to your string, even if it’s minimal, it’s not the same string anymore.
Ruby and Python stand out with opposite philosophical views about strings!
Mutable strings are not the best fit for problems that require constant values. Ruby has some ways to take off the mutable property from strings. But first, let’s check the wrong answer: create a constant.
In Ruby, you create constants beginning a variable with uppercase.
# So you can’t change a constant, right?
MY_CONSTANT = “An immutable string”
# Ruby Code
MY_CONSTANT << “?”
# Right?
puts MY_CONSTANT
An immutable string?
A string remains mutable even if a constant variable is used. The only thing that does constant variables different from other variables in Ruby is that you get a warning if you initialize the constant variable again.
MY_CONSTANT = “An immutable string”
MY_CONSTANT = MY_CONSTANT << “?”
warning: already initialized constant MY_CONSTANT
warning: previous definition of MY_CONSTANT was here
puts MY_CONSTANT
An immutable string?
If you want an immutable string in Ruby, you need to freeze it!
MY_CONSTANT = “An immutable string”.freeze
MY_CONSTANT << “?”
`<top (required)>': can't modify frozen String: "An immutable string" (FrozenError)
If you only need one immutable word in Ruby, you could prefer using symbols instead of frozen strings. You can also index symbols, but you can’t change them.
symbol = :shop
symbol[2]
“o”
symbol[2] = “i”
`<top (required)>': undefined method `[]=' for :shop:Symbol (NoMethodError)
Strings in Ruby and Python share some characteristics: in both languages, strings are primitive data types, allow indexing and slicing, and have their own built-in methods.
However, when handling strings, there are two main differences between Ruby and Python. In Python, strings are iterable and mutable objects. Meanwhile, strings in Ruby are mutable and not iterable objects. The only way to get an immutable string in Ruby is by creating a frozen string.