rubyguides

String#dup

str.dup → new_str

String#dup produces a shallow copy of the receiver: a brand-new String with the same bytes and encoding, but a different object_id. It is inherited from Object; the String class does not override it, so on a plain String it behaves exactly like Object#dup. The most useful property: the copy is always unfrozen, even if the source was frozen. That single fact is the reason most Ruby code calls dup on a String.

Signature

str.dup → new_str

dup takes no arguments. The → new_str notation means it returns a new String instance. The original is not mutated, and the copy is a separate object.

Basic shallow copy

Calling dup on a String gives you a second String object that holds the same byte sequence. Mutating the copy does not touch the original:

s = "hello"
copy = s.dup

copy                        # => "hello"
copy == s                   # => true   (equal content)
copy.equal?(s)              # => false  (different objects)
copy.object_id == s.object_id  # => false

copy << " world"
copy                        # => "hello world"
s                           # => "hello"  (untouched)

equal? is the identity check (same object_id), and it returns false here. == is value equality and returns true. That separation is the whole point of dup: you get a new object, not a new view onto the same one.

dup vs clone: the key difference

Both produce a shallow copy. The difference is what state they preserve. dup drops the frozen flag; clone keeps it. clone is also the only one that copies the singleton class:

frozen = "config".freeze
frozen.frozen?                  # => true

frozen.dup.frozen?              # => false
frozen.clone.frozen?            # => true

# clone can also be told explicitly to drop the frozen state:
frozen.clone(freeze: false).frozen?  # => false

Use dup when you want a mutable copy of a frozen value. Use clone when you want to keep the frozen flag (or copy singleton methods on a custom class).

Defensive copying with frozen_string_literal: true

The most common real-world use of String#dup is unfreezing a literal. With the frozen_string_literal: true magic comment, every literal in the file is implicitly frozen. Calling dup on such a literal is the idiomatic way to get a mutable working copy:

# frozen_string_literal: true

GREETING = "hello"

def add_suffix(str)
  str.dup << " world"
end

result = add_suffix(GREETING)
result   # => "hello world"
GREETING # => "hello"  (still frozen, still "hello")

Without the .dup, str << " world" would raise FrozenError on the frozen literal. The caller passes a frozen value, the method works on a local copy, and the caller’s constant is never touched.

Shallow copy semantics

For a plain String, “shallow” is mostly a non-issue. The bytes are stored inline in the object, and that buffer is copied across. The semantics only matter for String subclasses that store instance variables holding references to other mutable objects. In that (rare) case, the references are shared between the original and the copy, and mutating the referenced object will be visible from both sides. In ordinary cases, every dup produces a fully independent buffer.

When to use dup

  • Accept a frozen value, mutate a copy, return the copy. Standard “defensive copy” pattern in library code that has to be safe under frozen_string_literal: true.
  • Unfreeze a literal in a frozen-string-literal file. "config".dup is the canonical way to get a mutable copy without disabling the magic comment.
  • Break accidental aliasing. If two variables might point at the same buffer and you need to be sure a downstream mutation can’t reach back into yours, dup first.

Errors

String#dup does not raise under normal use. The only realistic failure mode is a TypeError raised from a String subclass that forbids being copied, for example one that overrides initialize_copy to call raise TypeError, "can't dup". Plain String instances copy cleanly, always.

See Also

  • String#clone — copies a String but preserves the frozen? state and any singleton class.
  • Object#dup — the inherited method String#dup delegates to; same semantics on plain String instances.
  • Object#clone — the counterpart that keeps frozen? and singleton methods.
  • String#freeze — the operation dup undoes on a frozen value.