Overview
Strong and weak typing represent two fundamental approaches to type safety in programming languages. These classification systems describe how strictly a language enforces type constraints and whether it permits implicit type conversions during operations.
Strong typing enforces strict type rules, preventing operations between incompatible types without explicit conversion. Languages with strong typing systems reject operations that attempt to treat a value as a type it does not represent, requiring developers to explicitly convert types when needed.
Weak typing permits implicit type conversions, automatically coercing values between types to complete operations. Languages with weak typing systems attempt to make operations succeed by converting operands to compatible types, prioritizing operational flexibility over type strictness.
The distinction between strong and weak typing exists independently from static versus dynamic typing. Static and dynamic typing describe when type checking occurs (compile-time versus runtime), while strong and weak typing describe how rigorously the type system enforces type constraints.
# Strong typing example in Ruby
"5" + 3
# => TypeError: no implicit conversion of Integer into String
# Explicit conversion required
"5" + 3.to_s
# => "53"
"5".to_i + 3
# => 8
Ruby demonstrates strong typing despite being dynamically typed. The language refuses to implicitly convert between strings and integers, requiring explicit conversion methods. This prevents ambiguous operations and catches type-related errors at runtime.
Understanding the strong versus weak typing distinction helps developers predict language behavior, write more reliable code, and make informed decisions about language selection for specific projects.
Key Principles
Type systems enforce rules about how values can be used in programs. The strength of a type system describes the degree to which these rules are enforced and whether the language permits automatic type conversions.
Type Coercion represents the automatic conversion of values from one type to another. Weak typing systems perform implicit coercion frequently, converting operands to compatible types to complete operations. Strong typing systems restrict implicit coercion, requiring explicit conversion in most cases.
Type Safety measures how effectively a type system prevents type errors. Strong typing provides greater type safety by catching incompatible type operations early, either at compile-time in statically typed languages or at runtime in dynamically typed languages. Weak typing sacrifices some type safety for operational convenience.
Explicit Conversion requires developers to specify type conversions using dedicated methods or operators. Strong typing systems mandate explicit conversion for operations between different types, making the programmer's intent clear and preventing accidental type mismatches.
# Strong typing requires explicit intent
value = "100"
result = value.to_i * 2
# => 200
# No implicit conversion happens
number = 42
text = "The answer is " + number
# => TypeError: no implicit conversion of Integer into String
# Explicit conversion shows intent
text = "The answer is " + number.to_s
# => "The answer is 42"
Type Compatibility determines which types can be used together in operations. Strong typing defines narrow compatibility rules, allowing operations only between closely related types. Weak typing defines broad compatibility rules, attempting to find valid interpretations for operations between diverse types.
Operation Semantics become ambiguous in weakly typed languages. When a language performs implicit conversions, the same operator may produce different results depending on operand types. Strongly typed languages maintain consistent operation semantics by rejecting ambiguous operations.
# Ruby's strong typing maintains clear semantics
[1, 2, 3] + [4, 5]
# => [1, 2, 3, 4, 5] - Array concatenation
[1, 2, 3] + 4
# => TypeError: no implicit conversion of Integer into Array
# Explicit conversion required for different operation
[1, 2, 3] << 4
# => [1, 2, 3, 4] - Array append
Type Consistency ensures that values maintain their types throughout operations. Strong typing preserves type consistency, preventing silent type changes that could introduce bugs. Weak typing allows types to change implicitly, which can lead to unexpected behavior.
The relationship between typing strength and error detection affects development workflows. Strong typing catches type mismatches immediately, providing clear error messages that identify problematic operations. Weak typing may allow type errors to propagate through a program, manifesting as logic errors rather than type errors.
Ruby Implementation
Ruby implements a strongly typed, dynamically checked type system. The language performs type checking at runtime and enforces strict rules about type compatibility, refusing operations between incompatible types without explicit conversion.
Type Checking at Runtime means Ruby evaluates types during program execution rather than compilation. Each operation checks operand types before executing, raising TypeError exceptions when encountering incompatible types.
# Runtime type checking
def calculate(x, y)
x + y
end
calculate(5, 10)
# => 15
calculate("hello", " world")
# => "hello world"
calculate(5, "10")
# => TypeError: String can't be coerced into Integer
No Implicit Numeric Coercion distinguishes Ruby from weakly typed languages. Ruby refuses to convert strings to numbers or numbers to strings automatically, preventing ambiguous arithmetic operations.
# No automatic string-to-number conversion
"5" * 3
# => "555" - String repetition, not multiplication
5 * "3"
# => TypeError: String can't be coerced into Integer
# Explicit conversion required
5 * "3".to_i
# => 15
String Concatenation Restrictions enforce type safety in text operations. Ruby's concatenation operator requires all operands to be strings, rejecting automatic conversion of other types.
# String concatenation requires strings
message = "Count: " + 42
# => TypeError: no implicit conversion of Integer into String
# Multiple explicit conversion approaches
message = "Count: " + 42.to_s
# => "Count: 42"
message = "Count: #{42}"
# => "Count: 42" - String interpolation handles conversion
message = "Count: " << 42.to_s
# => "Count: 42"
Duck Typing Coexists with Strong Typing in Ruby. The language checks that objects respond to required methods rather than belonging to specific classes, but still enforces type safety in operations.
# Duck typing accepts any object with required methods
def process(item)
item.to_s.upcase
end
process(42)
# => "42"
process(:symbol)
# => "SYMBOL"
# But operations still enforce type compatibility
def add(a, b)
a + b
end
add("hello", " world")
# => "hello world"
add("hello", 123)
# => TypeError: no implicit conversion of Integer into String
Type Coercion Protocols exist for specific scenarios. Ruby provides coercion methods like to_int, to_str, and coerce that allow objects to define explicit conversion behavior, but these conversions must be explicitly invoked.
# Coercion methods enable explicit conversion
class Temperature
def initialize(celsius)
@celsius = celsius
end
def to_i
@celsius.to_i
end
def to_f
@celsius.to_f
end
end
temp = Temperature.new(22.5)
temp + 5
# => TypeError: Temperature can't be coerced into Integer
temp.to_i + 5
# => 27
Boolean Context Conversion represents one of Ruby's few implicit conversions. Ruby treats nil and false as falsy, with all other values as truthy, but this differs from type coercion since the original value remains unchanged.
# Boolean context evaluation
if 0
puts "Zero is truthy in Ruby"
end
# => Zero is truthy in Ruby
if ""
puts "Empty string is truthy in Ruby"
end
# => Empty string is truthy in Ruby
# No automatic boolean conversion in operations
true + false
# => TypeError: false can't be coerced into Integer
Comparison Operations maintain strong typing except for numeric types. Ruby allows comparison between integers and floats but rejects comparisons between incompatible types.
# Numeric comparison works across integer and float
5 > 3.7
# => true
# Non-numeric comparisons require same type
"5" > 3
# => ArgumentError: comparison of String with Integer failed
# Explicit conversion required
"5".to_i > 3
# => true
Ruby's strong typing philosophy prioritizes clarity and error detection over convenience. The language forces developers to explicitly state their intentions when working with different types, reducing ambiguity and catching type errors early in program execution.
Practical Examples
Real-world scenarios demonstrate how strong and weak typing affect development patterns and error handling strategies.
Financial Calculations benefit from strong typing by preventing silent data corruption. Consider a system calculating monetary values:
# Strong typing prevents accidental string arithmetic
def calculate_total(base_price, tax_rate)
base_price * (1 + tax_rate)
end
# Type mismatch caught immediately
calculate_total("100.00", 0.08)
# => TypeError: String can't be coerced into Float
# Explicit conversion documents data source
user_input = "100.00"
calculate_total(user_input.to_f, 0.08)
# => 108.0
# This prevents silent errors
# In weakly typed language: "100.00" * 1.08 might yield "100.00100.00100.00..."
The strong typing system catches the type mismatch immediately rather than producing incorrect results. Financial applications require precise calculations, and strong typing prevents catastrophic errors from type confusion.
Data Processing Pipelines expose differences in how type systems handle mixed data. Processing user input requires careful type management:
# Processing form data with strong typing
class UserRegistration
def initialize(params)
@params = params
end
def age_valid?
age = @params[:age]
# Strong typing prevents silent comparison errors
age.to_i >= 18
rescue NoMethodError
false
end
def process
# Explicit conversions document expectations
{
name: @params[:name].to_s.strip,
age: @params[:age].to_i,
score: @params[:score].to_f,
active: !!@params[:active]
}
end
end
# Type mismatches caught during processing
registration = UserRegistration.new(age: "seventeen")
registration.age_valid?
# => false - Explicit conversion to_i handles invalid input predictably
Strong typing forces explicit handling of type conversions, making data validation and sanitization steps visible in the code. This improves maintainability and debugging.
API Response Handling demonstrates how type systems affect integration code. Parsing JSON responses requires type awareness:
# Strong typing in API response handling
class ApiClient
def fetch_user(id)
response = make_request("/users/#{id}")
parse_user(response)
end
private
def parse_user(response)
# Strong typing requires explicit structure validation
unless response.is_a?(Hash)
raise TypeError, "Expected Hash, got #{response.class}"
end
{
id: parse_integer(response['id']),
name: parse_string(response['name']),
score: parse_float(response['score']),
created_at: parse_datetime(response['created_at'])
}
end
def parse_integer(value)
return nil if value.nil?
Integer(value)
rescue ArgumentError, TypeError
raise TypeError, "Cannot convert #{value.inspect} to Integer"
end
def parse_string(value)
return "" if value.nil?
value.to_s
end
def parse_float(value)
return 0.0 if value.nil?
Float(value)
rescue ArgumentError, TypeError
raise TypeError, "Cannot convert #{value.inspect} to Float"
end
def parse_datetime(value)
return nil if value.nil?
Time.parse(value)
rescue ArgumentError
raise TypeError, "Cannot parse #{value.inspect} as datetime"
end
end
Strong typing requires explicit validation and conversion of each field. This verbosity provides safety, ensuring type errors are caught immediately rather than causing failures in subsequent operations.
Mathematical Operations reveal how type systems handle numeric operations differently:
# Strong typing in calculations
class Calculator
def self.percentage(value, percent)
# Both arguments must be numeric
value * (percent / 100.0)
end
def self.safe_percentage(value, percent)
# Explicit conversion with error handling
numeric_value = convert_to_numeric(value)
numeric_percent = convert_to_numeric(percent)
numeric_value * (numeric_percent / 100.0)
end
def self.convert_to_numeric(value)
case value
when Numeric
value
when String
Float(value)
else
raise TypeError, "Cannot convert #{value.class} to Numeric"
end
rescue ArgumentError
raise TypeError, "Invalid numeric string: #{value.inspect}"
end
end
Calculator.percentage(200, 15)
# => 30.0
Calculator.percentage("200", "15")
# => TypeError: String can't be coerced into Float
Calculator.safe_percentage("200", "15")
# => 30.0 - Explicit conversion allows controlled handling
The strongly typed version forces callers to provide correct types or handle conversion explicitly, preventing subtle bugs from silent type coercion.
Design Considerations
Selecting between strong and weak typing systems involves evaluating trade-offs between safety, flexibility, and development speed.
Error Detection Timing affects debugging efficiency. Strong typing catches type mismatches immediately when they occur, providing clear error messages that point to the exact operation causing problems. This immediate feedback shortens debugging cycles and prevents type errors from propagating through the system.
Weak typing may allow type errors to pass through initial operations, manifesting as logic errors later in execution. Tracking down these errors requires tracing backwards to find where incorrect type coercion occurred, potentially involving multiple layers of function calls.
Development Speed Trade-offs influence prototyping and iteration cycles. Strong typing requires writing more explicit type conversions during initial development, potentially slowing rapid prototyping. However, this upfront effort reduces debugging time and prevents issues in later development stages.
Weak typing enables faster initial development by accepting operations between different types without conversion code. This flexibility helps with rapid prototyping but may introduce subtle bugs that surface during integration or production use.
Code Clarity benefits from strong typing through explicit type handling. Reading strongly typed code reveals exactly what type conversions occur and where, documenting the programmer's intentions. This explicitness helps new team members understand data flow and makes code reviews more effective.
# Strong typing makes intentions explicit
def format_price(amount, currency)
# Clear conversion shows amount must be numeric
formatted_amount = sprintf("%.2f", amount.to_f)
"#{currency}#{formatted_amount}"
end
# Versus weak typing hiding conversions
# In weakly typed language, conversions happen implicitly
# making it unclear what input types are expected
Maintenance Burden varies between approaches. Strong typing prevents many classes of errors during refactoring, as type mismatches surface immediately when code changes. Modifying function signatures or data structures produces clear errors at call sites that need updating.
Weak typing creates more maintenance burden during refactoring, as implicit conversions may mask breaking changes. Tests become critical for catching issues that strong typing would identify automatically.
Integration Complexity affects external system communication. Strong typing requires explicit handling of data from external sources like APIs, databases, or user input. This explicit validation improves reliability but increases integration code volume.
# Strong typing integration pattern
class ExternalDataHandler
def process_webhook(payload)
# Explicit validation and conversion
validate_payload(payload)
convert_types(payload)
rescue TypeError => e
log_error("Type validation failed: #{e.message}")
raise IntegrationError, "Invalid payload structure"
end
private
def validate_payload(payload)
raise TypeError unless payload.is_a?(Hash)
raise TypeError unless payload['id'].respond_to?(:to_i)
raise TypeError unless payload['amount'].respond_to?(:to_f)
end
def convert_types(payload)
{
id: payload['id'].to_i,
amount: payload['amount'].to_f,
description: payload['description'].to_s
}
end
end
Performance Implications differ between strong and weak typing. Strong typing may require explicit conversion operations that add minimal overhead. Weak typing performs implicit conversions automatically, which can incur runtime costs for type checking and conversion logic.
However, strong typing enables better optimization opportunities since types remain consistent and predictable. Compilers or interpreters can generate more efficient code when they know types won't change unexpectedly.
Team Skill Level influences typing system effectiveness. Strong typing helps less experienced developers by catching type errors early and forcing explicit type handling. The immediate error feedback serves as a learning tool, teaching proper type management.
Weak typing requires greater understanding of implicit conversion rules and potential pitfalls. Developers must remember how different type combinations behave and anticipate coercion results, which demands more experience.
Domain Requirements shape appropriate typing choices. Financial, medical, or safety-critical applications benefit from strong typing's error prevention. Scientific computing or data analysis may favor strong typing to ensure calculation precision.
Rapid prototyping, scripting, or string-heavy text processing might benefit from weak typing's flexibility, though modern strongly typed languages provide sufficient flexibility through explicit conversion methods.
Common Patterns
Established patterns emerge for working effectively within strongly typed systems, addressing common scenarios while maintaining type safety.
Type Conversion Layer Pattern centralizes type conversion logic, providing consistent handling across an application:
# Type conversion layer for external data
module TypeConverter
def self.to_integer(value, default: 0)
return default if value.nil? || value == ""
Integer(value)
rescue ArgumentError, TypeError
default
end
def self.to_float(value, default: 0.0)
return default if value.nil? || value == ""
Float(value)
rescue ArgumentError, TypeError
default
end
def self.to_boolean(value)
return false if value.nil?
!['false', 'f', '0', '', 'no', 'n'].include?(value.to_s.downcase)
end
def self.to_string(value, default: "")
return default if value.nil?
value.to_s
end
end
# Usage in data processing
class FormProcessor
def process(params)
{
age: TypeConverter.to_integer(params[:age]),
score: TypeConverter.to_float(params[:score]),
active: TypeConverter.to_boolean(params[:active]),
name: TypeConverter.to_string(params[:name])
}
end
end
This pattern provides a single location for type conversion logic, ensuring consistent behavior and simplifying maintenance.
Type Guard Pattern validates types before operations, providing clear error messages for type mismatches:
# Type guards for method inputs
class Calculator
def self.divide(numerator, denominator)
guard_numeric!(numerator, "numerator")
guard_numeric!(denominator, "denominator")
guard_not_zero!(denominator)
numerator / denominator.to_f
end
private
def self.guard_numeric!(value, name)
return if value.is_a?(Numeric)
raise TypeError, "#{name} must be Numeric, got #{value.class}"
end
def self.guard_not_zero!(value)
raise ArgumentError, "denominator cannot be zero" if value.zero?
end
end
Type guards make type requirements explicit and provide better error messages than default type errors.
Null Object Pattern avoids type checking for nil values by providing objects that respond to the same interface:
# Null object pattern avoiding nil checks
class User
attr_reader :name, :email
def initialize(name, email)
@name = name
@email = email
end
def premium?
false
end
end
class PremiumUser < User
def premium?
true
end
end
class NullUser
def name
"Guest"
end
def email
""
end
def premium?
false
end
end
# Usage avoids type checking
def greet(user)
# No need to check if user is nil
"Welcome #{user.name}#{user.premium? ? ' (Premium)' : ''}"
end
user = User.new("Alice", "alice@example.com")
greet(user)
# => "Welcome Alice"
greet(NullUser.new)
# => "Welcome Guest"
This pattern eliminates nil checks while maintaining type consistency through polymorphism.
Type Coercion Protocol Pattern defines standard methods for type conversion in custom classes:
# Implementing coercion protocols
class Money
attr_reader :cents
def initialize(cents)
@cents = cents
end
# Explicit conversion - called by Integer(money)
def to_i
cents
end
# Implicit conversion - rarely used in Ruby
def to_int
cents
end
def to_f
cents / 100.0
end
def to_s
format("$%.2f", to_f)
end
# Coercion for arithmetic
def coerce(other)
return [Money.new(other), self] if other.is_a?(Integer)
raise TypeError, "#{other.class} can't be coerced into Money"
end
end
money = Money.new(1500)
money.to_f
# => 15.0
money.to_s
# => "$15.00"
# Explicit conversion works
Integer(money)
# => 1500
Implementing these protocols allows custom classes to integrate with Ruby's conversion system while maintaining strong typing.
Safe Navigation Pattern handles potentially nil values without extensive type checking:
# Safe navigation with explicit nil handling
class OrderProcessor
def process(order)
# Using safe navigation operator
customer_name = order&.customer&.name || "Unknown"
shipping_city = order&.shipping_address&.city || "Unknown"
# Explicit type conversion with nil handling
order_total = (order&.total || 0).to_f
item_count = (order&.items&.count || 0).to_i
{
customer: customer_name,
city: shipping_city,
total: order_total,
items: item_count
}
end
end
Safe navigation maintains type safety while gracefully handling absent data.
Builder Pattern with Type Validation constructs complex objects while ensuring type correctness:
# Builder with type validation
class QueryBuilder
def initialize
@conditions = []
@limit = nil
@offset = nil
end
def where(field, operator, value)
raise TypeError, "field must be Symbol or String" unless [Symbol, String].include?(field.class)
raise TypeError, "operator must be String" unless operator.is_a?(String)
@conditions << [field, operator, value]
self
end
def limit(value)
raise TypeError, "limit must be Integer" unless value.is_a?(Integer)
raise ArgumentError, "limit must be positive" unless value > 0
@limit = value
self
end
def offset(value)
raise TypeError, "offset must be Integer" unless value.is_a?(Integer)
raise ArgumentError, "offset must be non-negative" unless value >= 0
@offset = value
self
end
def build
{
conditions: @conditions,
limit: @limit,
offset: @offset
}
end
end
# Usage with type safety
query = QueryBuilder.new
.where(:name, "=", "Alice")
.where(:age, ">", 18)
.limit(10)
.offset(20)
.build
This pattern provides fluent interfaces while maintaining strict type checking at each step.
Common Pitfalls
Misconceptions about strong typing and mistakes in handling type conversions create common problems in strongly typed languages.
Assuming to_s Always Succeeds represents a frequent error. While most objects implement to_s, the result may not be meaningful:
# to_s succeeds but produces unhelpful results
user = { name: "Alice", age: 30 }
"User: " + user.to_s
# => "User: {:name=>\"Alice\", :age=>30}"
# Better approach with explicit formatting
"User: #{user[:name]}, Age: #{user[:age]}"
# => "User: Alice, Age: 30"
# Objects have default to_s behavior
class CustomObject
def initialize(data)
@data = data
end
end
obj = CustomObject.new([1, 2, 3])
"Data: " + obj.to_s
# => "Data: #<CustomObject:0x00007f8b8a8b3c40>"
Relying on automatic to_s conversion produces unhelpful output. Explicit formatting or custom to_s implementations provide better results.
Ignoring Conversion Failure occurs when assuming conversion methods always produce valid results:
# Conversion methods raise exceptions on failure
user_input = "not a number"
value = user_input.to_i
# => 0 - Silent failure returns 0
value = Integer(user_input)
# => ArgumentError: invalid value for Integer(): "not a number"
# Better pattern with explicit error handling
def safe_parse_integer(value)
Integer(value)
rescue ArgumentError => e
raise TypeError, "Cannot parse '#{value}' as integer: #{e.message}"
end
The difference between to_i (returns 0 on failure) and Integer() (raises exception) matters. Integer() provides better error detection for invalid input.
Mixing Numeric Types Without Understanding Precision causes calculation errors:
# Precision loss in type conversion
value = 1.9
value.to_i
# => 1 - Truncates rather than rounds
# Rounding must be explicit
value.round
# => 2
# Integer division surprises
10 / 3
# => 3
10.0 / 3
# => 3.3333333333333335
10 / 3.0
# => 3.3333333333333335
# Financial calculations require careful handling
price = 10.25
tax_rate = 0.08
total = (price * (1 + tax_rate) * 100).round / 100.0
# => 11.07
Understanding how type conversions affect numeric precision prevents calculation errors, particularly in financial applications.
Confusing String Concatenation and Interpolation leads to type errors:
# Concatenation requires strings
count = 5
"Items: " + count
# => TypeError: no implicit conversion of Integer into String
# Interpolation handles conversion
"Items: #{count}"
# => "Items: 5"
# But interpolation calls to_s, which may not be appropriate
array = [1, 2, 3]
"Array: #{array}"
# => "Array: [1, 2, 3]"
"Array: " + array
# => TypeError: no implicit conversion of Array into String
String interpolation performs automatic to_s conversion, while concatenation enforces type requirements. Knowing which context permits conversion prevents errors.
Expecting Automatic Type Promotion in Comparisons causes comparison failures:
# Comparisons don't coerce types
"10" > 5
# => ArgumentError: comparison of String with Integer failed
# Spaceship operator also fails
"10" <=> 5
# => nil
# Explicit conversion required
"10".to_i > 5
# => true
# Sorting mixed types fails
[1, "2", 3, "4"].sort
# => ArgumentError: comparison of String with Integer failed
# Explicit conversion needed
[1, "2", 3, "4"].map(&:to_i).sort
# => [1, 2, 3, 4]
Ruby refuses to compare incompatible types, preventing ambiguous comparisons. Explicit conversion ensures consistent comparison semantics.
Misunderstanding Boolean Coercion creates logic errors:
# Only nil and false are falsy
if 0
puts "This executes"
end
# => This executes
if ""
puts "This also executes"
end
# => This also executes
# No automatic boolean conversion in operations
true && 1
# => 1 - Returns last truthy value
false || 0
# => 0 - Returns last truthy value
# Explicit boolean conversion needed for consistency
!!0
# => true
!!"false"
# => true - String "false" is truthy
Ruby's boolean semantics differ from languages with automatic boolean conversion. Understanding truthiness prevents conditional logic errors.
Assuming Hash Access Returns Typed Values overlooks data source implications:
# Hash values may have unexpected types
params = { count: "5", active: "true" }
# Using values directly causes type errors
params[:count] + 10
# => TypeError: no implicit conversion of Integer into String
# Type assumptions fail
if params[:active]
# Executes because "true" is truthy, not because it's true
end
# Explicit conversion and validation needed
def parse_params(params)
{
count: Integer(params[:count]),
active: params[:active] == "true"
}
rescue ArgumentError, TypeError => e
raise "Invalid parameters: #{e.message}"
end
Hash values from external sources like HTTP parameters arrive as strings. Explicit conversion and validation prevent type-related bugs.
Reference
Type System Characteristics
| Characteristic | Strong Typing | Weak Typing |
|---|---|---|
| Implicit Conversion | Minimal or none | Extensive automatic coercion |
| Type Safety | High - catches type errors immediately | Lower - allows ambiguous operations |
| Error Detection | Immediate at operation point | May propagate through system |
| Developer Intent | Explicit through conversion code | Implicit through coercion |
| Code Verbosity | More explicit conversions required | Less conversion code needed |
| Maintenance | Type errors caught during refactoring | Tests critical for catching issues |
Ruby Type Conversion Methods
| Method | Behavior | Example |
|---|---|---|
| to_i | Returns 0 for invalid input | "abc".to_i => 0 |
| Integer() | Raises exception for invalid input | Integer("abc") => ArgumentError |
| to_f | Returns 0.0 for invalid input | "abc".to_f => 0.0 |
| Float() | Raises exception for invalid input | Float("abc") => ArgumentError |
| to_s | Always succeeds, may be unhelpful | [1,2].to_s => "[1, 2]" |
| to_a | Converts to array | 5.to_a => error, "hello".to_a => error |
| to_h | Converts to hash | [[:a, 1]].to_h => {:a=>1} |
| to_sym | Converts to symbol | "name".to_sym => :name |
Common Type Errors in Ruby
| Operation | Error | Cause |
|---|---|---|
| String + Integer | TypeError | No implicit conversion of Integer into String |
| Integer + String | TypeError | String can't be coerced into Integer |
| Array + Integer | TypeError | No implicit conversion of Integer into Array |
| String > Integer | ArgumentError | Comparison of String with Integer failed |
| Hash + Hash | TypeError | No implicit conversion to Array |
| Symbol + String | TypeError | Symbol can't be coerced into String |
Type Checking Patterns
| Pattern | Implementation | Use Case |
|---|---|---|
| is_a? check | value.is_a?(String) | Verify specific class |
| respond_to? check | value.respond_to?(:to_i) | Duck typing validation |
| kind_of? check | value.kind_of?(Numeric) | Check class hierarchy |
| instance_of? check | value.instance_of?(String) | Exact class match |
| case statement | case value when String | Multiple type handling |
| rescue TypeError | begin; op; rescue TypeError | Handle conversion failures |
Explicit Conversion Decision Matrix
| Source Type | Target Type | Method | Notes |
|---|---|---|---|
| String | Integer | to_i or Integer() | Use Integer() for validation |
| String | Float | to_f or Float() | Use Float() for validation |
| Integer | String | to_s | Always succeeds |
| Float | Integer | to_i or round | to_i truncates |
| Array | String | join | More useful than to_s |
| Hash | Array | to_a | Creates nested arrays |
| Symbol | String | to_s | Always succeeds |
| String | Symbol | to_sym or intern | Both equivalent |
Boolean Coercion Rules
| Value | Truthiness | Notes |
|---|---|---|
| nil | Falsy | Only two falsy values in Ruby |
| false | Falsy | Only two falsy values in Ruby |
| 0 | Truthy | Unlike some languages |
| "" | Truthy | Empty string is truthy |
| [] | Truthy | Empty array is truthy |
| {} | Truthy | Empty hash is truthy |
| "false" | Truthy | String "false" is not false |
Type Safety Best Practices
| Practice | Implementation | Benefit |
|---|---|---|
| Use Integer() not to_i | Integer(value) | Raises exception for invalid input |
| Validate external input | guard_type!(value) | Catches issues at boundary |
| Define custom to_s | def to_s; format; end | Meaningful string representation |
| Implement coerce | def coerce(other) | Enable arithmetic operations |
| Use safe navigation | object&.method | Handle nil gracefully |
| Type guard methods | guard_numeric!(value) | Clear error messages |
| Conversion layer | TypeConverter.to_integer | Centralized conversion logic |
| Explicit rescue | rescue TypeError | Handle conversion failures |