skip to content

Conversion Protocols

Ruby separates explicit conversions like to_s and to_i from implicit ones like to_str and to_ary that core methods call for you. Interviewers probe Integer() raising where to_i returns 0.

on this pageshow

explore

questions

5

In Ruby, why does "Qty: " + 6 raise TypeError, and what are the idiomatic ways to build that string?

level: juniorimportance: must knowfreq 62%

answer

  1. wants a real String
  2. Integer has to_s but not to_str
  3. no implicit conversion of Integer into String
  4. reverse order asks Integer to coerce
  5. two numeric strings concatenate

basics

~20 s

String#+ accepts only a String or an object with to_str, and Integer has no to_str, so Ruby raises TypeError: no implicit conversion of Integer into String. Convert explicitly with 6.to_s, or build the string with interpolation.

solid answer

~40 s

Ruby does not convert types behind your back in most operators. `String#+` insists on a String: it calls the implicit converter `to_str` on its argument, and `Integer` defines only the explicit `to_s`, so `"Qty: " + 6` raises `TypeError` with `no implicit conversion of Integer into String`. The fixes all convert explicitly: `"Qty: " + qty.to_s`, `"Qty: #{qty}"`, whose interpolation calls `to_s`, or `format("Qty: %d", qty)`. The reverse order fails differently: `6 + "3"` raises `TypeError` with `String can't be coerced into Integer`, because `Integer#+` asks its argument to coerce itself. To add, parse first: `6 + Integer("3")`. JavaScript would silently concatenate here; Ruby, like Python, refuses.

code

ruby · 10 lines
ruby
qty = 6

"Qty: " + qty            # TypeError: no implicit conversion of Integer into String
"Qty: " + qty.to_s       # => "Qty: 6"
"Qty: #{qty}"            # => "Qty: 6"
format("Qty: %d", qty)   # => "Qty: 6"

qty + "3"                # TypeError: String can't be coerced into Integer
qty + Integer("3")       # => 9
"6" + "3"                # => "63", two CSV cells concatenated, not added

go deeper

for a junior

Recall that + does not mix strings and numbers, quote the no implicit conversion message, and show to_s and interpolation as fixes.

for a middle

Explain that the left operand's method decides the rule: String#+ needs to_str, Integer#+ needs coerce, which is why the two messages differ.

for a senior

Point to the production trap of numeric strings from files or params concatenating silently, and parse at the boundary before doing arithmetic.

for a principal

Defend strong typing in core operators as a design choice that surfaces mistakes early, and set conventions for where input text becomes numbers.

## Why + refuses In Ruby, `+` is an ordinary method call on the left operand: `"Qty: " + 6` is `"Qty: ".+(6)`. `String#+` concatenates two strings, so it must turn its argument into a String first. It does not call just any conversion method. It calls **`to_str`**, the *implicit* conversion, which a class defines only when its objects really are strings in all but name. - `String` defines `to_str`, returning itself. - `Integer`, `Float`, `Symbol`, `Array` and `nil` do **not** define `to_str`. - They all define `to_s`, the *explicit* conversion you call yourself when you want a textual representation. So `String#+` finds no `to_str` on `6` and raises `TypeError` with the message `no implicit conversion of Integer into String`. The message names the missing *implicit* conversion; it is not saying `6` cannot be printed. ## The two error messages Which message you get depends on which operand's method runs: | Expression | Method that runs | Result | |---|---|---| | `"Qty: " + 6` | `String#+` | `TypeError`: `no implicit conversion of Integer into String` | | `6 + "3"` | `Integer#+` | `TypeError`: `String can't be coerced into Integer` | | `6 + nil` | `Integer#+` | `TypeError`: `nil can't be coerced into Integer` | | `"6" + "3"` | `String#+` | `"63"`, concatenation, no error | Numeric operators handle unknown operands through the **coerce** protocol: `Integer#+` asks the other object to `coerce` itself into a compatible number. A `String` does not respond to `coerce`, so the attempt fails with the second message. ## Idiomatic fixes Every fix makes the conversion explicit: 1. **Call `to_s`**: `"Qty: " + qty.to_s`. Clear, but repetitive when there are many pieces. 2. **Interpolate**: `"Qty: #{qty}"`. Interpolation calls `to_s` on the embedded value, so it works for any object and is the most common choice. 3. **Format**: `format("Qty: %d", qty)` when you need padding or number formatting. 4. **Parse the other way** when you want arithmetic: `6 + Integer("3")` is `9`. ## Other operators follow the same rule The same implicit protocol governs other core operators, so the pattern generalises: - `[1] + 2` raises `TypeError` with `no implicit conversion of Integer into Array`: `Array#+` wants `to_ary`. - `"ab" * "2"` raises `TypeError` with `no implicit conversion of String into Integer`: `String#*` wants an integer count. - `"total" + :qty` raises `no implicit conversion of Symbol into String`: `Symbol` has `to_s` but no `to_str`. One operator is a trap because it does **not** raise. `String#<<` accepts an `Integer` and treats it as a **codepoint**, so appending `6` to a label adds the invisible control character U+0006 instead of the digit. Convert with `to_s` before appending a number. ## Strictness is deliberate Ruby is dynamically typed but **strongly** typed: values keep their types, and most core methods refuse to guess. The comparison interviewers raise is JavaScript, where `"Qty: " + 6` produces `"Qty: 6"` and `"6" * 1` produces `6`; Python refuses like Ruby. Ruby's refusal surfaces a mistake at the line that made it rather than as a wrong value later. - An operator that accepts "anything that can become a string" would also accept `nil`, turning missing data into empty text. - Implicit conversion is reserved for objects that *are* the target type, so a successful `+` tells you both operands were strings. ## Worked example: a grocery-order CSV A grocery-order file has columns for item, quantity and extra quantity. Every cell a CSV parser returns is a `String`, or `nil` for a blank cell, never an `Integer`. - `row[1] + row[2]` with cells `"6"` and `"3"` returns `"63"`: two strings concatenate without complaint, which is a silent bug rather than an error. - `"Total: " + (Integer(row[1]) + Integer(row[2]))` raises `TypeError`, because the sum is an `Integer`. - Correct: parse each cell with `Integer(...)`, add the integers, then interpolate: `"Total: #{total}"`. A blank cell adds one more trap: `Integer(nil)` raises `TypeError` with `can't convert nil into Integer`, so decide how blanks are handled before summing. ## Summary - `String#+` needs a String or an object with `to_str`; numbers do not qualify. - `Integer#+` needs a number or an object with `coerce`; strings do not qualify. - Convert explicitly with `to_s`, interpolation or `Integer()`, and remember that two numeric strings concatenate instead of adding.

  • In Ruby, why is the error for 6 + "3" worded differently from "Qty: " + 6?
    A different method runs. `6 + "3"` calls `Integer#+`, which handles non-numeric operands by asking them to `coerce`; `String` has no `coerce`, so it raises `String can't be coerced into Integer`. `"Qty: " + 6` calls `String#+`, which needs `to_str`, hence `no implicit conversion of Integer into String`.
  • In Ruby, what does label << 6 do when label is a String?
    It does not raise. `String#<<` treats an `Integer` argument as a codepoint and appends that character, so `6` adds the invisible control character U+0006 rather than the digit. Append `6.to_s`, or interpolate, when you mean the text "6".

saying these in an interview costs you the question

  • String#+ calls to_s on its argument, so any object can be appended
  • Ruby converts 6 to "6" automatically in a + expression
  • "6" + "3" returns 9 in Ruby
  • The TypeError means the integer cannot be printed
  • 6 + "3" and "Qty: " + 6 raise the same error message
open as a page

In Ruby, how do Integer("12abc") and "12abc".to_i differ, and when should parsing a quantity raise instead of returning 0?

level: middleimportance: must knowfreq 66%

basics

~10 s

"12abc".to_i returns 12 and returns 0 when nothing parses. Integer("12abc") raises ArgumentError, and Integer(nil) raises TypeError. Use Integer() or Float() when bad input must be reported; exception: false returns nil instead.

open as a page

In Ruby, what do Kernel#Array() and Kernel#String() return for nil, a single value and an existing array, and why use them?

level: middleimportance: should knowfreq 40%

basics

~10 s

Array(x) tries to_ary, then to_a, then wraps: Array(nil) is [], Array("a") is ["a"] and an array comes back as is. String(x) tries to_str, then to_s: String(nil) is "" and String(6) is "6".

open as a page

In Ruby, what separates explicit to_s, to_a and to_h from implicit to_str, to_ary and to_hash, and when should a class define the implicit ones?

level: seniorimportance: should knowfreq 42%

basics

~20 s

Explicit converters such as to_s, to_a and to_h are representations you ask for, and most classes may define them. Implicit ones such as to_str, to_ary and to_hash claim the object is that type, so core methods call them automatically.

open as a page

In Ruby, how does Numeric#coerce let 6 * price work when price is a custom Money object, and what fails without it?

level: seniorimportance: nice to knowfreq 22%

basics

~10 s

Integer#* does not know Money, so it calls price.coerce(6), expects a two-element array [x, y], and evaluates x * y. Without coerce, Ruby raises TypeError: Money can't be coerced into Integer.

open as a page