rubyguides

String#replace

str.replace(other_str) -> str

The replace method swaps out a string’s entire contents with another string. Unlike sub or gsub that substitute portions matching a pattern, replace discards everything and fills the string with new content.

What makes replace unusual is that it mutates in place and returns self - the same object, not a copy. The object’s identity stays the same.

That behavior is useful when a string acts like a reusable buffer or a shared value that other code still points to. Instead of allocating a fresh string, you are refreshing the contents of the same object.

Syntax

str.replace(other_str) -> str

Parameters

ParameterTypeDescription
other_strStringThe string to copy into self

Return Value

Returns self (the same string object, now holding different content). This is different from most string methods that return new objects.

Examples

Basic Usage

The simplest case replaces one string with another. The variable name stays the same, but the content changes completely.

s = "hello"
s.replace("world")
s # => "world"

The variable s now holds "world" instead of "hello". The key detail is that s is still the same object — only its contents changed. This is what separates replace from simple reassignment. With replace, every reference to the original object sees the update.

Returns self, not a copy

s1 = "original"
s2 = s1.replace("modified")
s1.object_id == s2.object_id  # => true (same object)

This matters when multiple variables point to the same string. After replace, all variables see the new value. After reassignment, they don’t.

The distinction is important any time the object identity itself matters, such as in caches, buffers, or stateful helpers. In those cases, mutation can be the clearer choice because the receiver stays stable while its contents change.

Self-Assignment Is a No-Op

Ruby checks whether the argument is the same object as the receiver and, if so, returns immediately with no work done.

s = "hello"
s.replace(s)  # => "hello" — no change, returns immediately

The method checks for self-assignment and returns without modifying.

That guard keeps the method safe when the source and destination already match. It also makes the method a little easier to reason about because the no-op case is explicit.

Frozen strings raise an error

A frozen string cannot be modified by any method, and replace follows that rule. If you need to swap content in a frozen context, you must create a new string instead.

s = "hello".freeze
s.replace("world")  # => FrozenError (Ruby 2.5+)

Behavior Details

Object identity is preserved

replace mutates the string in place and returns self — it does not create a copy. The object_id doesn’t change. You’re not replacing the object, you’re replacing what the object contains. The example below confirms the identity stays the same after the call:

buffer = "initial"
id = buffer.object_id
buffer.replace("changed")
buffer.object_id == id  # => true

This is useful for reusable string buffers where you want to avoid allocations.

The buffer pattern is common in code that builds a response or a command step by step. replace gives you a quick way to reset the same object instead of allocating a new one for each pass.

Taintedness transfers

If the replacement string carries a taint flag, the target string inherits that flag:

If the replacement string is tainted, the target becomes tainted:

s = "hello"
s.replace("world".taint)
s.tainted?  # => true

Tainting is a Ruby security feature that tracks whether data came from an untrusted source. The replace method transfers the taint flag from the source to the target, which helps prevent accidentally treating untrusted data as safe.

Encoding changes to match source

The encoding of the source string replaces the encoding of the target. This matters when you are working with data from different locales or byte-level protocols:

s = "hello".encode("ASCII-8BIT")
s.replace("world".encode("UTF-8"))
s.encoding  # => #<Encoding:UTF-8>

This encoding transfer is automatic and cannot be turned off. If you need to preserve the original encoding, you must re-encode the string after the replacement or use a different approach like string interpolation. The encoding always follows the source, so plan accordingly when mixing encodings.

Common Patterns

Reusable string buffer

def build_response(buffer, status, body)
  buffer.replace("HTTP/1.1 #{status}\r\n")
  buffer.replace(buffer + "\r\n#{body}")
end

buf = String.new
build_response(buf, 200, "OK")

The buffer pattern avoids allocating a new string on every call to build_response. Because the same buf object is reused, callers that hold a reference to it always see the latest value without needing to reassign. This approach works well in tight loops or request handlers where allocation pressure matters.

In-place normalisation

def normalise!(str)
  str.replace(str.strip.downcase)
end

data = "  Hello World  "
normalise!(data)
data  # => "hello world"

Edge Cases

  • Frozen string: Raises FrozenError — you can’t modify a frozen string
  • Self-assignment: Returns immediately with no changes
  • Empty string: Works fine - effectively clears the string
  • Non-string argument: Calls StringValue() which coerces via to_s

See Also

  • String#sub — Replace first pattern match (returns new string)
  • String#gsub! — Replace all pattern matches in place
  • String#tr — Character-by-character translation