DEV Community

Cover image for Learning Go as a Ruby Developer #5: Strings and formatting
Shrouk Abozeid
Shrouk Abozeid

Posted on

Learning Go as a Ruby Developer #5: Strings and formatting

A Go string is essentially a sequence of bytes.

That sounds like a small implementation detail, but once you start dealing with UTF-8 and Unicode, it becomes important to understand what's actually happening.


Strings in Go

A basic Go string looks familiar:

message := "Hello World"

fmt.Println(message)
Enter fullscreen mode Exit fullscreen mode

Strings can use double quotes:

message := "Hello \t World"
Enter fullscreen mode Exit fullscreen mode

Or backticks:

message := `Hello \t World`
Enter fullscreen mode Exit fullscreen mode

And these two behave differently.

With double quotes, escape sequences are interpreted:

fmt.Println("Hello \t World")
Enter fullscreen mode Exit fullscreen mode

The \t becomes a tab.

With backticks, the contents are treated as a raw string literal:

fmt.Println(`Hello \t World`)
Enter fullscreen mode Exit fullscreen mode

Here, \t remains part of the string.

This is useful when you want the contents of a string to be interpreted literally.


A String Is a Sequence of Bytes

Consider:

str := "Hello You!"
Enter fullscreen mode Exit fullscreen mode

We can convert the string to a byte slice:

bytes := []byte(str)
Enter fullscreen mode Exit fullscreen mode

Then print both:

fmt.Printf("%v\n", str)
fmt.Printf("%v\n", bytes)
Enter fullscreen mode Exit fullscreen mode

The output looks like:

Hello You!
[72 101 108 108 111 32 89 111 117 33]
Enter fullscreen mode Exit fullscreen mode

Those numbers represent the individual bytes that make up the string.

For example, the first character:

H
Enter fullscreen mode Exit fullscreen mode

is represented by the byte:

72
Enter fullscreen mode Exit fullscreen mode

So when Go says a string is a sequence of bytes, this isn't just an abstract definition.

We can actually see those bytes.


So Where Does UTF-8 Come In?

Computers ultimately store data as binary.

That means we need a way to represent text as numbers that computers can store.

That's where character encodings come in.

UTF-8 stands for:

Unicode Transformation Format – 8-bit

It provides a way to encode Unicode characters using between 1 and 4 bytes per character.

This allows the same encoding to represent characters from many different writing systems.

For example, simple ASCII characters such as:

A
B
C
Enter fullscreen mode Exit fullscreen mode

can be represented using a single byte.

Other Unicode characters can require multiple bytes.

And this is where the distinction between characters and bytes becomes important.


Bytes Are Not Characters

Consider this:

str := "Hello"
Enter fullscreen mode Exit fullscreen mode

Each character in this particular string can be represented by one byte.

But that doesn't mean every character is one byte.

For example, Unicode contains characters that require multiple bytes when encoded as UTF-8.

So if we're dealing with text outside the ASCII range, we can't simply assume:

1 character = 1 byte
Enter fullscreen mode Exit fullscreen mode

This is one of those details I rarely think about when writing normal Ruby application code.

Go makes the underlying representation much more visible.


Ruby Comparison

Ruby also lets us inspect the bytes of a string.

str = "Hello You!"

str.bytes
Enter fullscreen mode Exit fullscreen mode

This gives us the byte values making up the string.

So the underlying concept isn't completely foreign to Ruby.

The difference is that Go's type system and standard library make this distinction much more explicit.

That mental model becomes particularly useful when working with files, network protocols, encodings, and other low-level operations.


Formatting with fmt

Another part of my notes that I found useful was Go's fmt package.

It provides formatted output similar to Ruby's printf.

For example:

name := "Alice"
age := 30

fmt.Printf("Name: %s, Age: %d\n", name, age)
Enter fullscreen mode Exit fullscreen mode

The % symbols are called format verbs.

They tell fmt how to format the value.


%v — Print the Value

When you're not particularly concerned about the exact formatting, %v is useful.

name := "Alice"

fmt.Printf("%v\n", name)
Enter fullscreen mode Exit fullscreen mode

It prints the value using its default format.

This is one of the formatting verbs I expect to use frequently.


%T — Print the Type

One I particularly like while learning Go is %T.

age := 30

fmt.Printf("%T\n", age)
Enter fullscreen mode Exit fullscreen mode

This prints the type of the value.

For example:

int
Enter fullscreen mode Exit fullscreen mode

Coming from Ruby, this reminds me of:

puts age.class
Enter fullscreen mode Exit fullscreen mode

Both are useful when you're trying to understand what you're actually working with.


Formatting Numbers

Go also provides formatting verbs for different number representations.

For example:

number := 42

fmt.Printf("%d\n", number)
fmt.Printf("%b\n", number)
fmt.Printf("%o\n", number)
fmt.Printf("%x\n", number)
fmt.Printf("%X\n", number)
Enter fullscreen mode Exit fullscreen mode

These represent the number in:

  • %d — decimal
  • %b — binary
  • %o — octal
  • %x — hexadecimal using lowercase letters
  • %X — hexadecimal using uppercase letters

So:

42
Enter fullscreen mode Exit fullscreen mode

can be represented as:

101010
Enter fullscreen mode Exit fullscreen mode

in binary.


Characters and Unicode Formatting

Go also provides formatting verbs for characters and Unicode values.

For example:

fmt.Printf("%c\n", 65)
Enter fullscreen mode Exit fullscreen mode

prints:

A
Enter fullscreen mode Exit fullscreen mode

because 65 corresponds to the Unicode code point for A.

There's also %q for quoted character/string representations.

And %#U can be used to display a Unicode code point in Go syntax.

These formatting options become particularly useful when you're debugging text and encoding-related issues.


Go vs Ruby

Here's how some of these concepts compare.

Concept Go Ruby
String Sequence of bytes String object
String literal "Hello" "Hello"
Raw string `Hello` %q{Hello} / other forms
Inspect bytes []byte(str) str.bytes
Print fmt.Println puts
Formatted output fmt.Printf printf
Print type %T .class
Default formatting %v puts / interpolation
Decimal %d printf("%d", n)
Binary %b printf("%b", n)
Hexadecimal %x / %X printf("%x", n)
Character %c chr / formatting

When you're working with files, network communication, binary data, or encoding problems, understanding that you're ultimately dealing with bytes becomes extremely valuable.

And this is another recurring theme in my Go journey:

Ruby often lets me forget what's happening underneath.

Go occasionally makes me look underneath the hood.

And sometimes, that's exactly what I need to become a better developer.

Top comments (0)