← Back to Blog

Deep Dive into Ruby's puts and p: System Call Analysis

Published: June 29, 2025
Tags: ruby

Introduction

Ruby's 'puts' and 'p' methods are commonly used for debugging and output. For example, both return "hello", but what happens internally?

$ puts "hello"
hello
$ p "hello"
"hello"

This article explores the internals of 'puts' and 'p'. The investigation reveals that both functions call the write system call, but in different ways. Despite Ruby being written in C, it doesn't use printf for its standard output functionality.

System Specifications

MacBook Air M2 arm64
Running Ubuntu 24.04 on Docker

Quick Ubuntu Virtual Environment Setup from Mac

# Create Dockerfile and build
docker build -t ruby-syscall - <<'EOF'
FROM ubuntu:24.04
RUN apt-get update && \
    apt-get install -y --no-install-recommends \
        ruby-full gcc strace linux-tools-common && \
    rm -rf /var/lib/apt/lists/*
WORKDIR /ws
EOF

# Enter container
echo 'puts "hello from puts"; p "hello from p"' > test.rb && \
docker run --privileged -it --rm -v "$PWD/test.rb":/ws/test.rb ruby-syscall

Background Knowledge

System Calls

This article deals with system calls - mechanisms for invoking OS functionality. When high-level languages output text to stdout or control file I/O, system calls are invoked deep in the code.

The simple conceptual model shows that application code doesn't directly access kernel space, but interacts with the OS and hardware through system calls, acting as an interface layer.

System call diagram

Investigation Process

Using strace to Monitor System Calls

We can use strace to monitor what system calls are made when Ruby executes puts and p:

strace -e write ruby test.rb

Key Findings

puts vs p System Call Patterns

Both puts and p ultimately use the write system call, but they differ in their approach:

puts behavior:

p behavior:

Ruby's Output Implementation

Interesting discoveries about Ruby's implementation:

System Call Sequence Analysis

When examining the system call traces, we can observe:

  1. File descriptor setup: Ruby sets up stdout (fd=1)
  2. String processing: Different processing for puts vs p
  3. Write operations: Direct write system calls to stdout
  4. Buffer management: Ruby manages its own output buffering

Performance Implications

Understanding these internals helps with performance considerations:

Conclusion

This exploration reveals that Ruby's output methods are more sophisticated than they appear on the surface. By implementing custom output handling rather than relying on C's printf, Ruby maintains better control over object representation and output formatting. Understanding these system-level details helps developers write more efficient Ruby code and debug output-related issues more effectively.