== checks for equal content.
equal? checks for equal identity.
a = "hello"
b = "hello"
a == b # => true
a.equal?(b) # => false
In Ruby string literals are not immutable and thus creating a string and using a literal are indeed the same. In both cases Ruby creates a new string instance each time the expressions in evaluated.
Both of these are thus the same
10.times { String.new }
# is the same as
10.times { "" }
Let's verify this
10.times { puts "".object_id }
Prints 10 different numbers
70227981403600
70227981403520
70227981403460
...
Why? Strings are by default mutable and thus Ruby has to create a copy each time a string literal is reached in the source code. Even if those literals are usually rarely modified in practice.
Thus a Ruby program typically creates an excessive amount short-lived string objects, which puts a huge strain on garbage collection. It is not unusual that a Rails app creates 500,000 short-lived strings just to serve one request and this is one of the main performance bottlenecks of scaling Rails to millions or even 100 millions of users.
To address that Ruby 2.3 introduced frozen string literals, where all string literals default to being immutable. Since this is not backwards compatible it is opt-in with a pragma
# frozen_string_literal: true
Let's verify this too
# frozen_string_literal: true
10.times { puts "".object_id }
Prints the same number 10 times
69898321746880
69898321746880
69898321746880
...
Fun fact, setting a key in a hash also creates a copy of a string
str = "key"
hash = {}
hash[str] = true
puts str.object_id
puts hash.keys.first.object_id
Prints two different numbers
70243164028580
70243132639660
str = "oh, my"is shorthand forString.new("oh, my"), almost. See String#new for the differences, which involve encoding. Generally, the literal assignment is used.