CrackedRuby logo

CrackedRuby

Method Return Values

Overview

Ruby methods return values through both implicit and explicit mechanisms. Every method in Ruby returns a value - either the result of the last evaluated expression or an explicitly specified return value using the return keyword. Ruby's return value system forms the foundation for method chaining, functional programming patterns, and idiomatic Ruby code.

The return keyword allows explicit control over what a method returns and when execution stops. Without an explicit return, Ruby returns the value of the last expression evaluated in the method body. This implicit return behavior makes Ruby code concise while maintaining clarity.

def implicit_return
  "This string is returned"
end

def explicit_return
  return "Explicit return value"
  "This line never executes"
end

implicit_return  # => "This string is returned"
explicit_return  # => "Explicit return value"

Ruby methods can return any object type - strings, numbers, arrays, hashes, custom objects, or nil. Methods can also return multiple values by returning an array, which can be unpacked using parallel assignment. The return value becomes part of method contracts and influences how methods compose together.

def multiple_values
  [1, 2, 3]
end

a, b, c = multiple_values  # => a=1, b=2, c=3

Basic Usage

Ruby's implicit return mechanism evaluates the last expression in a method and returns that value. This applies to all method types - instance methods, class methods, and blocks. The implicit return creates clean, readable code without excessive return statements.

def calculate_area(length, width)
  length * width  # Implicit return
end

def format_name(first, last)
  "#{first} #{last}".strip  # Returns formatted string
end

calculate_area(5, 3)     # => 15
format_name("John", "")  # => "John"

Explicit returns using the return keyword immediately exit the method and return the specified value. Use explicit returns for early exits, guard clauses, or when the return value needs clarification. The return keyword can appear anywhere in the method body.

def validate_age(age)
  return false if age < 0
  return false if age > 150
  true
end

def find_user(id)
  return nil if id.nil?
  User.find_by(id: id)
end

Methods without explicit return values or final expressions return nil. Empty methods, methods ending with assignment operations, or methods with only side effects return nil by default.

def empty_method
end

def assignment_method
  @value = 42  # Assignment returns the assigned value, but method returns nil
end

def puts_method
  puts "Hello"  # puts returns nil
end

empty_method      # => nil
assignment_method # => 42 (assignment return value becomes method return)
puts_method       # => nil

Ruby supports multiple return values through array returns and parallel assignment. Methods can return arrays containing multiple values, which callers can unpack into separate variables or work with as a single array.

def name_parts(full_name)
  parts = full_name.split(" ")
  [parts.first, parts.last]
end

def coordinates
  x = rand(100)
  y = rand(100)
  [x, y]
end

first, last = name_parts("Jane Smith")  # => first="Jane", last="Smith"
x, y = coordinates                      # => x=42, y=73 (example values)
position = coordinates                  # => [42, 73]

Advanced Usage

Method return values enable sophisticated control flow patterns through guard clauses and early returns. Guard clauses use early explicit returns to handle edge cases and invalid inputs, keeping the main method logic clean and readable.

def process_order(order)
  return { error: "Order is nil" } if order.nil?
  return { error: "Order is empty" } if order.items.empty?
  return { error: "Invalid customer" } unless order.customer&.valid?
  
  # Main processing logic here
  {
    total: calculate_total(order),
    status: "processed",
    confirmation: generate_confirmation
  }
end

def safe_division(a, b)
  return Float::INFINITY if b == 0 && a > 0
  return -Float::INFINITY if b == 0 && a < 0
  return Float::NAN if b == 0 && a == 0
  a.to_f / b
end

Fluent interfaces and method chaining rely on methods returning objects that support additional method calls. Each method in the chain returns an object that responds to the next method call, creating readable, expressive code.

class QueryBuilder
  def initialize
    @conditions = []
    @order = nil
    @limit = nil
  end
  
  def where(condition)
    @conditions << condition
    self  # Return self for chaining
  end
  
  def order_by(field)
    @order = field
    self
  end
  
  def limit(count)
    @limit = count
    self
  end
  
  def to_sql
    sql = "SELECT * FROM table"
    sql += " WHERE #{@conditions.join(' AND ')}" unless @conditions.empty?
    sql += " ORDER BY #{@order}" if @order
    sql += " LIMIT #{@limit}" if @limit
    sql
  end
end

# Method chaining in action
query = QueryBuilder.new
  .where("age > 18")
  .where("active = true")
  .order_by("created_at DESC")
  .limit(10)
  .to_sql
# => "SELECT * FROM table WHERE age > 18 AND active = true ORDER BY created_at DESC LIMIT 10"

Conditional returns and ternary operations create concise methods that return different values based on conditions. These patterns work well for simple branching logic and calculations.

def status_message(user)
  return "Welcome back!" if user.returning?
  return "Please complete your profile" unless user.profile_complete?
  "Everything looks good!"
end

def discount_rate(customer)
  customer.premium? ? 0.15 : customer.member? ? 0.10 : 0.05
end

def safe_get(hash, key, default = nil)
  hash.key?(key) ? hash[key] : default
end

Higher-order functions and functional programming patterns use return values to compose behavior. Methods that return functions, lambdas, or procs create powerful abstraction mechanisms.

def create_validator(min_length)
  ->(value) { value.length >= min_length }
end

def create_formatter(prefix, suffix)
  ->(text) { "#{prefix}#{text}#{suffix}" }
end

def pipeline(*functions)
  ->(input) { functions.reduce(input) { |acc, func| func.call(acc) } }
end

# Usage
email_validator = create_validator(5)
html_formatter = create_formatter("<p>", "</p>")

email_validator.call("test@example.com")  # => true
html_formatter.call("Hello World")        # => "<p>Hello World</p>"

# Pipeline composition
text_processor = pipeline(
  ->(text) { text.strip },
  ->(text) { text.downcase },
  create_formatter("[", "]")
)
text_processor.call("  HELLO WORLD  ")  # => "[hello world]"

Common Pitfalls

Implicit nil returns occur when methods end with operations that return nil, such as assignment to instance variables, puts calls, or empty conditional blocks. These situations can cause unexpected behavior when methods are expected to return meaningful values.

# Problematic: method returns nil instead of the assigned value
def set_name(name)
  @name = name  # Returns name, but often misunderstood
end

# Problematic: puts returns nil
def display_result(value)
  puts "Result: #{value}"  # Method returns nil
end

# Better: explicit return of meaningful value
def set_name(name)
  @name = name
  self  # Return self for chaining
end

def display_result(value)
  puts "Result: #{value}"
  value  # Return the value itself
end

Early return placement can create unreachable code and logical errors. Code after a return statement never executes, which can hide bugs and create maintenance problems.

# Problematic: unreachable code
def calculate_score(points)
  return 0 if points < 0
  bonus = points * 0.1
  return points + bonus
  # This line never executes!
  log_calculation(points, bonus)  # Dead code
end

# Better: structure to avoid unreachable code
def calculate_score(points)
  return 0 if points < 0
  
  bonus = points * 0.1
  result = points + bonus
  log_calculation(points, bonus)
  result
end

Multiple return value unpacking can fail silently when the number of variables doesn't match the number of returned values. Extra variables become nil, and extra values are ignored without warnings.

def get_user_info
  ["John", "Doe", 30, "Engineer"]  # Returns 4 values
end

# Problematic: mismatched unpacking
first, last = get_user_info  # age and job are lost
# => first="John", last="Doe"

first, last, age, job, extra = get_user_info  # extra becomes nil
# => first="John", last="Doe", age=30, job="Engineer", extra=nil

# Better: explicit handling of expected values
def get_basic_info
  user_data = get_user_info
  [user_data[0], user_data[1]]  # Explicitly return only what's needed
end

# Or use splat operator for remaining values
first, last, *other_info = get_user_info
# => first="John", last="Doe", other_info=[30, "Engineer"]

Return value mutability can lead to unexpected side effects when callers modify returned objects. Returning mutable objects like arrays or hashes allows external code to change internal state.

class DataStore
  def initialize
    @items = ["item1", "item2", "item3"]
  end
  
  # Problematic: returns mutable reference
  def items
    @items  # Caller can modify internal state
  end
  
  # Better: return frozen copy or duplicate
  def items
    @items.dup.freeze
  end
  
  # Or return immutable view
  def item_count
    @items.length  # Returns immutable integer
  end
end

store = DataStore.new
items = store.items
items << "item4"      # Modifies internal state if not protected
items.delete("item1") # Also modifies internal state

Boolean return consistency problems arise when methods sometimes return boolean values and sometimes return other types. This inconsistency makes conditional logic unreliable.

# Problematic: inconsistent return types
def find_user(id)
  user = User.find_by(id: id)
  return false if user.nil?
  return user if user.active?
  nil  # Inconsistent with false return above
end

# Better: consistent boolean returns
def user_active?(id)
  user = User.find_by(id: id)
  user&.active? || false
end

# Or consistent object returns with nil
def find_active_user(id)
  user = User.find_by(id: id)
  user&.active? ? user : nil
end

Reference

Return Value Mechanisms

Mechanism Description Example Return Value
Implicit return Last expression value def test; 42; end 42
Explicit return return keyword def test; return 42; end 42
Empty method No expressions def test; end nil
Assignment return Assignment operation def test; @x = 42; end 42

Common Return Patterns

Pattern Use Case Example Notes
Guard clause Early validation return nil unless valid? Explicit return for clarity
Ternary return Simple conditions valid? ? result : nil Concise conditional return
Multiple values Complex results [status, data, errors] Array unpacking supported
Fluent interface Method chaining return self Enables .method1.method2
Nil object pattern Safe defaults return NullUser.new Avoids nil checks

Return Value Types by Context

Context Typical Return Example Purpose
Predicate methods Boolean user.valid? True/false questions
Factory methods Object instance User.create(attrs) Object construction
Query methods Object or nil User.find_by(id) Optional object retrieval
Transform methods Modified data text.upcase Data transformation
Side-effect methods Self or nil array.sort! Enable chaining or indicate action

Multiple Return Values

Pattern Syntax Unpacking Use Case
Array return [a, b, c] x, y, z = method Fixed number of values
Hash return {key: val} result[:key] Named return values
Splat unpacking [a, *rest] first, *others = method Variable number of values
Selective unpacking [a, b, c] x, _, z = method Ignore some values

Error and Edge Case Returns

Scenario Pattern Example Rationale
Invalid input Return nil return nil if input.nil? Fail fast with nil
Error condition Return error object {error: "message"} Structured error info
Empty collection Return empty array [] Consistent collection type
Boolean with info Return array [true, additional_info] Boolean plus metadata
Operation status Return hash {success: true, data: result} Status and data together

Method Chain Return Requirements

Chain Type Return Requirement Example
Builder pattern Return self builder.add(x).add(y).build
Data pipeline Return processed data data.filter(f).map(m).sort
Configuration Return self config.set(k,v).enable(f)
Query building Return query object query.where(x).limit(n)