Go言語(Golang)は、そのシンプルさと高速なコンパイル性能から、クラウドネイティブな開発やマイクロサービスにおいて不可欠な言語となりました。

その開発体験を支える重要な要素の一つが、コマンドラインツールの使い勝手の良さです。

特に go run コマンド は、コードを書いてから実行するまでのサイクルを劇的に短縮してくれる、開発者にとって最も身近なコマンドの一つと言えるでしょう。

しかし、単純にプログラムを動かすだけのコマンドだと捉えていては、Go言語の持つポテンシャルを十分に引き出すことはできません。

go run が内部でどのような挙動をし、どのようなシーンで活用すべきなのかを深く理解することは、効率的な開発フローを構築する上で極めて重要です。

本記事では、基本的な使い方から、実務で役立つ応用テクニック、さらには内部的なコンパイルの仕組みまでを詳しく解説します。

go run コマンドの基本概念

go run は、Goのソースコードをコンパイルし、生成されたバイナリを即座に実行するためのコマンドです。

通常、C言語やRustなどのコンパイル言語では「コンパイル(ビルド)」と「実行」という2つのステップを踏みますが、go run を使用することで、あたかもスクリプト言語(PythonやRubyなど)を扱っているかのような感覚で開発を進めることができます。

コマンドの基本的な構文

最もシンプルな使い方は、実行したいメインパッケージが含まれるファイルを指定する方法です。

go
// main.go
package main

import "fmt"

func main() {
    fmt.Println("Hello, Go world!")
}

このファイルを保存し、ターミナルで以下のコマンドを実行します。

Shell
go run main.go
実行結果
Hello, Go world!

このように、実行ファイル(バイナリ)を明示的に作成することなく、プログラムの結果を即座に確認できます。

パッケージ単位での実行

実務的な開発では、1つのファイルだけで完結することは稀です。

複数のファイルで構成される main パッケージを実行する場合は、ファイル名を個別に並べるのではなく、ディレクトリ(カレントディレクトリ)を指定する形式が推奨されます。

Shell
# カレントディレクトリの main パッケージを実行
go run .

この方法を用いることで、ディレクトリ内のすべてのソースコードがコンパイル対象となり、ファイル間の依存関係を意識せずに実行が可能になります。

go run の内部メカニズム

go run が「スクリプトのように動く」からといって、Goがインタプリタとして動作しているわけではありません。

その裏側では、非常に高速なコンパイル処理が行われています。

一時ディレクトリへのビルド

go run を実行した際、Goツールチェーンは以下の手順を自動的に踏んでいます。

  1. ソースコードのスキャンと依存関係の解析
  2. OSの一時ディレクトリ(Tempフォルダ)内への実行バイナリ作成
  3. 作成されたバイナリの実行
  4. (実行終了後)バイナリの保持、またはクリーンアップ

ユーザーの作業ディレクトリにバイナリを残さないため、プロジェクトフォルダを汚さずに動作確認ができるというメリットがあります。

ビルドキャッシュの活用

「毎回コンパイルしていたら遅いのではないか?」という疑問が生じるかもしれませんが、Goには強力な ビルドキャッシュ機能 が備わっています。

一度コンパイルされたパッケージや依存ライブラリは、変更がない限りキャッシュから再利用されます。

そのため、2回目以降の go run は、ソースコードの変更分だけを処理し、極めて短時間で起動します。

現在のキャッシュの状態を確認したい場合は、以下のコマンドを使用します。

Shell
go env GOCACHE

実務で役立つ go run の活用法

単なる動作確認以外にも、go run は実務のさまざまな場面で威力を発揮します。

引数の渡し方

開発中のプログラムにコマンドライン引数(フラグ)を渡したい場合があります。

このとき、go run 自体への引数と、実行されるプログラムへの引数を区別する必要があります。

区切り記号として -- (ダブルハイフン)を使用するのが一般的です。

go
// args.go
package main

import (
    "fmt"
    "os"
)

func main() {
    if len(os.Args) > 1 {
        fmt.Printf("引数: %s\n", os.Args[1])
    }
}
Shell
go run args.go -- hello
実行結果
引数: hello

リモートパッケージの直接実行

Go 1.17 以降、go run はリモートにあるパッケージをインストールせずに直接実行する機能を強化しました。

これは、開発ツールやLinterなどを実行する際に非常に便利です。

Shell
# 特定のバージョンのツールを直接実行
go run golang.org/x/tools/cmd/goimports@latest -w .

このコマンドは、ローカルの GOBIN を汚染することなく、最新の goimports を取得してカレントディレクトリのコードを整形します。

プロジェクトごとに必要なツールのバージョンが異なる場合でも、グローバルな環境に影響を与えず使い分けられるのが大きな利点です。

ツール管理としての go.mod 利用

2025年以降のGo開発(Go 1.24以降の標準)では、go.mod ファイル内に tool ディレクティブを含めることで、プロジェクトで使用する開発ツールを管理する手法が一般的になりました。

これにより、以下のコマンドだけでチーム全員が同じバージョンのツールを go run 経由で利用できるようになります。

Shell
# プロジェクトで定義されたツールを実行
go run tool-name

go run と go build の使い分け

開発において、go rungo build のどちらを使うべきか迷うことがあるかもしれません。

以下の表に主要な違いをまとめました。

特徴go rungo build
主な用途開発中の素早い試行錯誤、スクリプト実行本番環境用バイナリ作成、配布
実行バイナリ一時ディレクトリに作成(自動削除)カレントディレクトリ等に作成・保持
実行速度コンパイル時間を含めて実行ビルド済みのため最速で起動
依存関係実行のたびに依存関係を確認ビルド時にすべての依存をバイナリに封入

結論として、ローカル開発環境での「書き直しと確認」のループでは go run を使い、デプロイや配布、あるいは厳密なパフォーマンス測定を行う際には go build を使用するのが正解です。

注意点とトラブルシューティング

非常に便利な go run ですが、いくつかの落とし穴も存在します。

1. 複数の main パッケージ問題

同じディレクトリ内に package main を持つファイルが複数存在し、それぞれが独立した main() 関数を持っている場合、ディレクトリ指定(go run .)はエラーになります。

text
main redeclared in this block

この場合は、実行したいファイルを個別に指定するか、ディレクトリ構造を整理して main パッケージを分離する必要があります。

2. コンパイルオーバーヘッド

非常に大規模なプロジェクトで、かつ依存関係が複雑な場合、ビルドキャッシュが効いていても go run の起動に数秒かかることがあります。

このようなケースでは、一度 go build して生成されたバイナリを使い回す方がストレスを軽減できる場合があります。

3. 本番環境での使用禁止

本番環境(サーバー上)で go run を使用してアプリケーションを起動するのは絶対に避けてください。 理由は以下の通りです。

  • 実行のたびにコンパイルが発生し、起動が遅れる。
  • Goツールチェーン(SDK)を本番環境にインストールしておく必要があり、セキュリティリスクが高まる。
  • コンパイルエラーが発生した場合、プロセスが起動せず原因究明が困難になる。

本番環境では、必ずビルド済みのバイナリをデプロイするようにしましょう。

まとめ

go run コマンドは、Go言語の「高速なコンパイル」という強みを最大限に活かした強力なツールです。

単にプログラムを実行するだけでなく、ビルドキャッシュの仕組みやリモートパッケージの直接実行といった特性を理解することで、開発効率はさらに向上します。

  • 日常的なコーディングの確認には go run . を活用する。
  • 引数を渡す際は -- を活用して正しくフラグを分離する。
  • ツール類の実行にはバージョン指定付きの go run pkg@version を活用し、環境をクリーンに保つ。
  • 本番環境では必ず go build したバイナリを使用する。

これらのルールを意識することで、Go言語による開発スピードはさらに加速するはずです。

シンプルながら奥が深い go run を使いこなし、洗練された開発フローを構築していきましょう。