29 Apr 2024 ~ 9 min read

GoLang Strings for TypeScript Developers

A quick guide to GoLang strings for TypeScript developers.

Listen to something with me 😍

Want to listen somewhere else? 🎧

Contents

Introduction

GoLang is a statically typed, compiled language that is syntactically similar to C, with the added benefit of memory safety, garbage collection, structural typing, and CSP-style concurrency. It is a great language for building web servers, APIs, and CLI tools. If you are coming from a TypeScript background, you might find GoLang’s string handling a bit different. I hope I can help you understand a bit more of GoLang strings with this article, outlining the differences I found confusing when I started learning GoLang.

Note: This article assumes you have some knowledge of TypeScript, and when refering to TypeScript Strings we’re referring to JavaScript strings.

[Top]

Comparing Strings in GoLang & TypeScript

A strings is a sequence of characters. Both in GoLang and TypeScript, are a representation of a sequence of characters. However, GoLang strings are literally a slice of bytes.

Slice(GoLang): A slice is a reference to a contiguous segment of an array. Slices are dynamic, they can grow and shrink as needed.

In TypeScript, a string is just a sequence of characters. It is a primitive data type and is immutable. There is no distinction between a character and a string in TypeScript. Thus, when you’re manipulating strings in a TypeScript application you’re always going to be working with a string. In GoLang however, you’re going to encounter different data types depending on the operation you’re performing.

Let’s look at a simple TypeScript example:

let str = "Hello, World!";

console.log(str[0], typeof str[0]); // output: H string
console.log(str.slice(1, 4), typeof str.slice(1, 4)); // output: ell string
console.log(str, typeof str); // output: Hello, World! string

Notice how each time we check the typeof the result, it’s always a string. This is because TypeScript strings are always strings. Nothing special about them. However, in GoLang…

var cp []byte

str := "Hello, World!"
c := str[0]
  partOfStr := str[0:5]
cp = []byte(str[0:5])

fmt.Println(reflect.TypeOf(str))
fmt.Println(reflect.TypeOf(partOfStr).Kind())
fmt.Println(reflect.TypeOf(c))
fmt.Println(reflect.TypeOf(cp))

This is however the first part of the revelation. There is another angle to this, strings in GoLang what you see is not what you get. Let me explain, using examples

TypeScript Example

let newline = "\n"
let newline_char = "\\n"
let literal_nl = `\n`
let literal_nl_character = `\\n`
let str_char = '\n'
let str_char_literal = '\\n'

console.log("Newline: ", newline)
console.log("Newline char: ", newline_char)
console.log("Literal String, newline", literal_nl)
console.log("Literal String, newline character", literal_nl_character)
console.log("Character, newline", str_char)
console.log("Character, newline character", str_char_literal)
// Output
Newline:  

Newline char:  \n
Literal String, newline: 
↩︎
Literal String, newline character \n
Character, newline: 
↩︎
Character, newline character: \n

GoLang Example

newline := "\n"
newline_escaped := "\\n"
literal_nl_character := `\n`
literal_not_nl := `\\n`
literal_nl := `

`
newline_char := '\n'
// newline_char_error := '\\n' // Error: illegal rune literal

println("Newline: ", newline) 
println("Newline Character: ", newline_escaped)
println("Literal String, newline character: ", literal_nl_character)
println("Literal String, not newline char", literal_not_nl)
println("Literal String, actial newline", literal_nl)
println("Character, newline", newline_char)
// Output
Newline:
↩︎
Newline Character: \n
Literal Newline: \n
Literal Not Newline: \\n
Literal Newline:
↩︎
Character, newline: 10

Note: the newline_char_error line will throw an error because the value \\n is not a valid rune. while \\ is a valid rune, \\n is not a valid rune since \\ and n are two separate characters.

The newline character is represented by the ASCII value 10. This is why the output of newline_char is 10.

Notice the difference? when you’re using strings, you might be getting a different result depending on the syntax you’re using to represent the string.

Type of String Representations in GoLang

A String in GoLang is a single data type, but

Rune

Let’s start with runes, A rune is a single code unit that represents a single character in GoLang. It is also an alias for int32 and is used to represent a Unicode code point.

A code point, just means a number that represents a character. For example, the code point for the letter a is 97. apparently, character is ambiguous, so we use code point to refer to the number that represents a character.

rune := 'a'
fmt.Println(reflect.TypeOf(rune)) // output: int32

String

A string in GoLang is a sequence of bytes. It is a slice of bytes, also known as int8.

str := "Hello, World"
fmt.Println(reflect.TypeOf(str)) // output: string

String Literal

A string literal is the exact UTF-8 representation of the input string. What you see is what you get, meaning what you write in your editor is what you are storing in memory. This works because the GoLang compiler only accepts UTF-8 encoded source code, so it can safely store the string literal as is. Just dump what you’re writing in the string literal into memory.

str_literal := `⚡︎`
fmt.Println(reflect.TypeOf(str_literal)) // output: string
fmt.Println(str_literal) // output: ⚡︎

Note: I just placed a lightning bolt emoji in the string literal, and it was printed as is on the output. This is because the GoLang compiler only accepts UTF-8 encoded source code. Thus, can safely store the string literal as is.

[Top]

String Operations

Let’s compare string operations between TypeScript and GoLang. I’ll demonstrate using simple examples

String Concatenation

TypeScript

const oldstr = "old1", oldstr2 = "old2"
const newstr = oldstr1 + oldstr2

GoLang

oldstr1 := "old1"
oldstr2 := "old2"
newstr := oldstr1 + oldstr2
newstr2 := strings.Join([]string{oldstr1, oldstr2}, "")

println("1", newstr)
println("2", newstr2)

String Length

TypeScript

const str = "Hello, World!"
console.log(str.length) // 13 

GoLang

str := "Hello, World!"
println(len(str)) // 13

String Indexing, Slicing and Searching

Let’s see how typescript & golang handle string indexing, slicing and searching operations.

TypeScript

const str = "Hello, World!";

// Indexing
console.log(str.at(0)) // H
console.log(str[0]) // H
console.log(str[14]) // undefined

// Slicing
console.log(str.slice(-6)) // World!
console.log(str.substring(0, 5)) // Hello

// Searching
console.log(str.indexOf("World")) // 7
console.log(str.lastIndexOf("o")) // 8
console.log(str.includes("World")) // true
console.log(str.includes("something")) // false
console.log(str.endsWith("!")) // true
console.log(str.startsWith("a")) // false

// Matching
const str2 = "food, good, mood, wood, rude, dude, water, boot, foot"
console.log(str2.match(/.?ood/g)) // [ 'food', 'good', 'mood', 'wood' ]
console.log(str2.match(/.?oot/g)) // [ 'boot', 'foot' ]

GoLang

package main

import (
	"fmt"
	"regexp"
	"strings"
)

func main() {
	str := "Hello, World!"

	// Indexing
	println(str[0])
	if len(str) >= 14 {
		// panics without the size check
		println(str[14])
	} else {
		println("str doesn't contain 14 bytes")
	}

	// Slicing
	println(str[0:5]) // Hello
	// println(str[-4]) // ERROR: must be uint, non-negative integer
	println(str[7:]) // World!

	// Searching
	println(strings.Index(str, "World")) // 7
	println(strings.LastIndex(str, "o")) // 8
	println(strings.Contains(str, "World")) // true
	println(strings.HasSuffix(str, "!")) // true
	println(strings.HasPrefix(str, "a")) // false

	// Matching
	str2 := "find, feed, found, food, frown"
	re := regexp.MustCompile(`f.?.?d`)
	fmt.Printf("%q", re.FindAll([]byte(str2), -1))
}

Advanced String Operations

String Formatting

String formatting is important in GoLang for multiple reasons, one of which is for converting the string into different types of values. Let’s see how we can format strings in GoLang.

package main

import (
  "fmt"
  "strconv"
)

func main() {
  str := "Hello, World!"
  num := 10
  floatNum := 10.5

  // String to Int
  numStr := "10"
  numInt, _ := strconv.Atoi(numStr)
  fmt.Println(numInt)

  // Int to String
  numStr2 := strconv.Itoa(num)
  fmt.Println(numStr2)

  // Float to String
  floatStr := strconv.FormatFloat(floatNum, 'f', 2, 64)
  fmt.Println(floatStr)

  // String to Float
  floatStr2 := "10.5"
  floatNum2, _ := strconv.ParseFloat(floatStr2, 64)
  fmt.Println(floatNum2)
}

More importantly formatting can be useful when you want to print out a string in a specific format. For example, you might want to print out a string with a specific number of decimal places. This is where the fmt package comes in

package main

import "fmt"

func main() {
	str := "hello"
	bty := []byte(str)
	uniStr := `⚡︎`

	fmt.Printf("TypeOf: %v %T\n", string(str), str)       // TypeOf: h uint8
	fmt.Printf("TypeOf: %v %T\n", string(str[0]), str[0]) // TypeOf: h uint8
	fmt.Printf("TypeOf: %v %T\n", bty, bty)               // TypeOf: [104 101 108 108 111] []uint8
	fmt.Printf("TypeOf: %v %T\n", uniStr, uniStr)         // TypeOf: ⚡︎ string
	fmt.Printf("TypeOf: %x %T\n", uniStr, uniStr)         // TypeOf: e29aa1efb88e string

	// We can also return the string instead of sending it to an IO stream
	// with the fmt.Sprintf function
	str2 := fmt.Sprintf("TypeOf (Returned): %v %T\n", string(str), str)
	println(str2) // TypeOf (Returned): h uint8
}

As I’ve tried to demonstrate the fmt package is very helpful package and important if you’re looking for a way to output string data.

String Splitting

Splitting strings can be done using two types methods, strings.Split and strings.Cut. There is a slight difference between the two, strings.Split does one thing it takes a separator and returns a []string extracted from the input string. While strings.Cut returns string before and after the separator, similar to slicing the input string in two.

package main

import "strings"

String Replacing

Conclusion

I’ve tried to put together all I have experienced about string processing in golang, keep in mind I am primarily a JavaScript developer. So if you find something interesting that I didn’t point out or a suggestion on improvements from the techniques from this article please add a comment to the article, much appreciated. Thank you for reading this article.

References

  1. GoLang Strings, go.dev
  2. GoLang Slice, go.dev
  3. JavaScript Strings, **MDN
  4. GoLang RegExp Web Docs**

Headshot of Maxi Ferreira

Hi, I'm Zablon. I'm a software engineer, mostly working in the web space, based in Ethiopia. You can follow me on Twitter, see some of my work on GitHub, or read more about Qebero.dev on here.