Goの書式指定子%qを絶対に忘れない

「海賊Gopherくん」
Powered by Gopherize.me - A Gopher pic that's as unique as you
The Gopher character is based on the Go mascot designed by Renée French

はじめに

こんにちは!!!!ライクル事業部エンジニアの黒田(@knkurokuro7)です。

今日もGoについてブログを書こうと思います!

背景

Goには「書式指定子」というものがあります。
fmt.Printf("僕は、%s", "Gopher")などの%sのように、フォーマットを指定する際に使います。
よく目にするものとしては、文字列型に対応する%s、整数型に対応する%d、error型に対応する%wなどがあります。

最近、業務で以下のようなコードを見かけました。

u, err := url.Parse(r.Image.URL)
if err != nil {
    return nil, fmt.Errorf("画像URLの形式が正しくありません: %q : %w", r.Image.URL, err)
}

ここで、 r.Image.URLは文字列型なので、「あれ、%sでもいいのではないか」と思いました。
また%qをこの時に初めて見たので、調べてみることにしました。

%qについて

まず前提として、%qについて調べてみました。

以下のfmtパッケージの公式ドキュメントに書式指定子の仕様について詳しく書かれています。

pkg.go.dev

%qについての説明は、IntegerとString and slice of bytesのセクションに書かれています。

Integer

%q     a single-quoted character literal safely escaped with Go syntax.

String and slice of bytes

%q     a double-quoted string safely escaped with Go syntax

またこういう説明もありました。

When formatting a single integer code point or a rune string (type rune) with %q, invalid Unicode code points are changed to the Unicode replacement character, U+FFFD, as in strconv.QuoteRune.

まとめると、

  • 整数型の場合は、Goシンタックスで安全にエスケープされたシングルクォートで囲まれた文字列リテラルにフォーマットする。
  • 文字列型もしくはスライス型の場合は、Goシンタックスで安全にエスケープされたダブルクォートで囲まれた文字列にフォーマットする。
  • 単一の整数コードポイントまたはルーン文字列(rune 型)の場合は、無効な Unicode コードポイントは strconv.QuoteRune のように、 Unicode 置換文字 U+FFFD に変更される。

これだけではなかなかイメージがつかなかったので、実際に試してみました。

package main

import (
    "fmt"
)

func main() {
    str := "Gopherくん"
    slice := []string{"g", "o", "h", "e", "r"}
    integer := 12450
    fmt.Printf("1:出力は %q となります。\n", str)
    fmt.Printf("2:出力は %s となります。\n", str)
    fmt.Printf("3:出力は %q となります。\n", slice)
    fmt.Printf("4:出力は %s となります。\n", slice)
    fmt.Printf("5:出力は %d となります。\n", integer)
    fmt.Printf("6:出力は %q となります。\n", integer)
}

実行してみると以下のように出力されました。

1:出力は "Gopherくん" となります。
2:出力は Gopherくん となります。
3:出力は ["g" "o" "h" "e" "r"] となります。
4:出力は [g o h e r] となります。
5:出力は 12450 となります。
6:出力は 'ア' となります。

このように、文字列型とスライス型の場合は、ダブルクォーテーション("")で囲まれて出力されます。
整数型の場合は、シングルクォート('')で囲まれた文字列リテラルにフォーマットされて出力されます。
なので、6の場合は、アのUnicode コードポイントが10進数表記で12450なので、'ア'が出力されます。

%qの使いどころ

こちらの記事を参照させていただいたのですが、

abhinavg.net

例えばエラー出力の際に、

u, err := url.Parse(r.Image.URL)
if err != nil {
    return nil, fmt.Errorf("画像URLの形式が正しくありません: %q : %w", r.Image.URL, err)
}

エラーになった場合でr.Image.URLが空の場合には、
%sを使うと出力は

画像URLの形式が正しくありません:  : hogehogeerror

のようになるのですが、%q を使うと出力は

画像URLの形式が正しくありません: "" : hogehogeerror

となり、r.Image.URLが空になっているというのを少しだけわかりやすくすることができます!

なので、文字列を出力する際に、空になる可能性があるなら%q を使うと良いということがわかりました。

終わりに

もしこの記事の内容についてご指摘等ございましたら、お気軽に教えていただけると嬉しいです!!

ここまで読んでいただいて本当にありがとうございます。

参考にさせていただいた資料

fmt package - fmt - Go Packages

Strings, bytes, runes and characters in Go - The Go Programming Language

The Go Programming Language Specification - The Go Programming Language

忘れがちなGo言語の書式指定子を例付きでまとめた - Qiita

Go errors: Use %q with fmt.Errorf