#to_s or #to_str? Explicitly casting vs. implicitly coercing types in Ruby
Tags: ruby, beginners, programming, learning
Published: 2018-09-25 12:52:55

---
title: #to_s or #to_str? Explicitly casting vs. implicitly coercing types in Ruby
description: We'll look into difference between explicit casting and implicit coercing in Ruby, and the difference between typecasting Leonard Nimoy and Michael Keaton.
cover_image: https://blog.appsignal.com/images/blog/2018-09/spock.jpg
tags: ruby, beginners, programming, learning
published: true
canonical_url: https://blog.appsignal.com/2018/09/25/explicitly-casting-vs-implicitly-coercing-types-in-ruby.html
---

Type coercion is the changing of an object's type into another type, together with its value. For example, changing an Integer into a String with `#to_s` or a Float into an Integer with `#to_i`. The perhaps lesser-known `#to_str` and `#to_int` methods some objects implement do the same at first glance, but there are some differences.

In this edition of AppSignal academy, we'll dive into explicitly casting and implicitly coercing types in Ruby, while briefy touching on typecasting actors. We'll cover the differences between both methods, and discuss how they're used.

Let's first look at how we usually coerce values to different types in Ruby with explicit casting helpers.

## Explicit Casting Helpers

The most common casting helpers are `#to_s`, `#to_i`, `#to_a` and `#to_h`. These are explicit casting methods. They help us easily transform a value from one type to another.

The explicit helpers come with a clear promise. Whenever `#to_s` is called on an object, it'll *always* return a string, even if the object doesn't really convert to a string well. It's like casting Michael Keaton as Batman. You'll get a batman, even if a comedy actor isn't especially suited for the role.

Ruby offers these helper methods on almost any basic object in the Ruby standard library.

```ruby
:foo.to_s # => "foo"
10.0.to_i # => 10
"10".to_i # => 10
```

These methods, especially `#to_s`, are implemented on most basic types in Ruby. While the casting almost always returns a value, the result may not be what we expect.

```ruby
"foo10".to_i          # => 0
[1, 2, 3].to_s        # => "[1, 2, 3]"
{ :foo => :bar }.to_s # => "{:foo=>:bar}"
{ :foo => :bar }.to_a # => [[:foo, :bar]]
Object.to_s           # => "Object"
Object.new.to_s       # => "#<Object:0x00007f8e6d053a90>"
```

Calling the `#to_s`, `#to_i`, `#to_a` and `#to_h` helpers forces any value to the selected type. They return a representation of the type it's coerced to regardless of what happens to the value.

## Implicit Coercion Methods

Calling type casting methods on values that do not act like the type we are casting to can cause errors or loss of data. Ruby also offers implicit coercion methods which only return a value when objects act like the type. This way we can be sure that the value acts like the type we want. These implicit coercion methods are `#to_str`, `#to_int`, `#to_ary` and `#to_hash`.

Implicit coercion is like casting Leonard Nimoy as any role but Spock. They'll work if the character is close enough to Spock, but fail if they're not. The `#to_str` helper *tries* to convert to a string, but will raise a `NoMethodError` if the object doesn't implement the method and can't be implicitly coerced.

```ruby
10.to_int                           # => 10
10.0.to_int                         # => 10
require "bigdecimal"
BigDecimal.new("10.0000123").to_int # => 10

# Unsuccessful coercions
"10".to_int             # => NoMethodError
"foo10".to_int          # => NoMethodError
[1, 2, 3].to_str        # => NoMethodError
{ :foo => :bar }.to_str # => NoMethodError
{ :foo => :bar }.to_ary # => NoMethodError
Object.to_str           # => NoMethodError
Object.new.to_str       # => NoMethodError
```

We can see that Ruby is a bit more strict now in what it does and doesn't coerce to the requested types. If the coercion is not possible, the `#to_*` method is not implemented on the object and calling it raises a `NoMethodError`.

When using implicit coercions, e.g. `#to_str`, we ask the function to return a String object, only if the original type also acts like a String. For this reason, `#to_str` is only implemented on String in the Ruby Standard Library.

### How Ruby Uses Implicit Coercion

Other than being more precise in what we're asking for during a coercion, what else is implicit coercion useful for? Turns out Ruby uses implicit coercions itself in a fair bit of scenarios. For instance, when combining objects with `+`.

```ruby
name = "world!"
"Hello " + name # => "Hello world!"

# Without #to_str
class Name
  def initialize(name)
    @name = name
  end
end
"Hello " + Name.new("world!") # => TypeError: no implicit conversion of Name into String
```

Here, we see Ruby raise a `TypeError` since it can't do an implicit conversion from the `Name` type to a `String`.

If we implement `#to_str` on the class, Ruby knows how to coerce the `Name` type.

```ruby
# With #to_str
class Name
  def to_str
    @name
  end
end
"Hello " + Name.new("world!") # => "Hello world!"
```

The same works for Arrays and `#to_ary`.

```ruby
class Options
  def initialize
    @internal = []
  end

  def <<(value)
    @internal << value
  end
end

options = Options.new
options << :foo
[:some_prefix] + options # => TypeError: no implicit conversion of Options into Array

class Options
  def to_ary
    @internal
  end
end
[:some_prefix] + options # => [:some_prefix, :foo]
```

But `#to_ary` is used in more scenarios. We can use it to destructure an Array into separate variables.

```ruby
options = Options.new
options << :first
options << :second
options << :third
first, second, third = options
first  # => :first
second # => :second
third  # => :third
```

It also does conversion of the object into block parameters.

```ruby
[options].each do |(first, second)|
  first # => :first
  second # => :second
end
```

There are more scenarios where the implicit coercion methods are used, such as `#to_hash` with `**`. This coerces the value to a hash with `#to_hash` before passing it to the `parse_options` method.

```ruby
class Options
  def to_hash
    # Create a hash from the Options Array
    Hash[*@internal]
  end
end

def parse_options(opts)
  opts
end

options = Options.new
options << :key
options << :value
parse_options(**options) # => {:key=>:value}
```

## Enforcing Types

Ruby also offers more resilient coercion methods when the type is of an unknown type and we want to make sure we get the correct type. There's one for every basic type ([`String(...)`](http://ruby-doc.org/core-2.5.1/Kernel.html#method-i-String), [`Integer(...)`](http://ruby-doc.org/core-2.5.1/Kernel.html#method-i-Integer), [`Float(...)`](http://ruby-doc.org/core-2.5.1/Kernel.html#method-i-Float), [`Array(...)`](http://ruby-doc.org/core-2.5.1/Kernel.html#method-i-Array), [`Hash(...)`](http://ruby-doc.org/core-2.5.1/Kernel.html#method-i-Hash), etc.).

```ruby
String(self)       # => "main"
String(self.class) # => "Object"
String(123456)     # => "123456"
String(nil)        # => ""

Integer(123.999)   # => 123
Integer("0x1b")    # => 27
Integer(Time.new)  # => 1204973019
Integer(nil)       # => TypeError: can't convert nil into Integer
```

The `String(...)` method first tries to call `#to_str` on the value, and when that fails, it calls its `#to_s` method. Not all objects define a `#to_str` method, therefore checking with both the implicit coercion (`#to_str`) and explicit (`#to_s`) casting methods increases the chances that the String conversion will work and you'll get the value you want. By first calling for implicit coercion we're more likely to get a result that has the same value but is of the coerced type, and not something like `"#<Object:0x00007f8e6d053a90>"`.

```ruby
class MyString
  def initialize(value)
    @value = value
  end

  def to_str
    @value
  end
end

s = MyString.new("hello world")
s.to_s    # => "#<MyString:0x...>"
s.to_str  # => "hello world"
String(s) # => "hello world"
```

You should only implement the implicit casting methods for objects that act like the to be coerced type, e.g. `#to_str` for your own String class.

Other than first trying implicit coercion, the `String(...)` helper also checks the returned type. `#to_str` is just a method which can return any type of value, even non-Strings. To ensure we get a value of the requested type `String(...)` raises a `TypeError` if the types don't match.

```ruby
class MyString
  def to_str
    nil
  end
end

s = MyString.new("hello world")
s.to_s    # => "#<MyString:0x...>"
s.to_str  # => nil
String(s) # => "#<MyString:0x...>"
```

Here, we can see that Ruby ignores the result of `#to_str` because it returned `nil`, which is not of the String-type. Instead, it falls back to the `#to_s` result.

If `#to_s` also returns `nil` and thus isn't of the correct type, `String(...)` will raise a `TypeError`.

```ruby
class MyString
  def to_str
    nil
  end

  def to_s
    nil
  end
end

s = MyString.new("hello world")
s.to_s    # => nil
s.to_str  # => nil
String(s) # => TypeError: can't convert MyString to String (MyString#to_s gives NilClass)
```

While they may be more reliable in enforcing type coercion, note that the casting helper methods (`String(...)`, `Integer(...)`, etc.) are usually a bit slower as they need to perform more checks on the given value.

## In Conclusion

When you want to make sure you're dealing with the right type of data for an object, type coercion is a useful process. In this post, we refreshed our knowledge of explicit casting helpers like `#to_s`, `#to_i`, `#to_a` and `#to_h`. We also looked at instances when implicit helpers like `#to_str`, `#to_int`, `#to_ary` and `#to_hash` are useful and how they're used by Ruby itself.

We hope you found this type coercion overview useful and how you found the actor typecasting analogy. As always, let us know if there's a topic you'd like us to cover. If you have any questions or comments, don't hesitate to leave a comment.