Goのエラー解決:なぜ []string を []interface{} として渡せないのか

intermediate🔷 Go2026-06-10| Go (Golang) 1.0+, ジェネリクスには1.18+が必要

Error Message

cannot use data (type []string) as type []interface {} in argument to process
#go#slice#interface#型変換#ジェネリクス

問題の発生

これはよくある罠です。[]string のスライスがあり、[]interface{} を受け取る関数があるとします。単一の stringinterface{} に完全に適合するため、スライス版も同様に動作すると想定しがちです。しかし、Go コンパイラはこれを必ず拒否します。

データベースドライバ、JSON エンコーダ、またはカスタムログ用ラッパーにデータを渡す際に、この壁に突き当たることがあります。以下は、この不満を引き起こす典型的なコードスニペットです:

package main

import "fmt"

func process(items []interface{}) {
    for _, item := range items {
        fmt.Println(item)
    }
}

func main() {
    data := []string{"apple", "banana", "cherry"}
    
    // ここでエラーが発生します:
    // cannot use data (type []string) as type []interface {} in argument to process
    process(data) 
}

なぜ Go はこれを拒否するのか

Go は「魔法を使わない(no magic)」という哲学で有名です。stringinterface{} を満たしますが、[]string のメモリレイアウトは []interface{} とは根本的に異なります。

このように考えてみてください。64ビットシステムでは、string ヘッダーは16バイト(データへのポインタと長さ)です。interface{} も16バイトですが、型ポインタとデータポインタで構成されています。内部構造が異なるため、Go は一方をもう一方として単純に扱うことができません。

もし Go がこの変換を自動的に行った場合、スライス全体をループして各要素を再割り当てする必要があります。これは O(n) の操作です。パフォーマンスを予測可能に保つため、Go は単純な代入の裏にこのコストを隠すのではなく、コード上でこのコストを明示的にさせるようにしています。

解決策1:「for ループ」による方法(標準的)

最も直接的な修正方法は、手動で新しいスライスを割り当てて要素をコピーすることです。これにより、新しいメモリの割り当てと CPU サイクルの消費というパフォーマンス上のコストが、コードを読む誰にとっても明白になります。

func main() {
    data := []string{"apple", "banana", "cherry"}

    // 正しい型の新しいスライスを割り当てる
    interfaceSlice := make([]interface{}, len(data))

    for i, v := range data {
        interfaceSlice[i] = v
    }

    process(interfaceSlice)
}

解決策2:可変長引数

関数のシグネチャを変更できる場合は、可変長引数(...interface{})を使用します。これにより、要素を個別に渡すことができます。変換自体を回避できるわけではありませんが、少量のデータを扱う場合、呼び出し側のコードがすっきりすることがよくあります。

// []interface{} の代わりに ...interface{} を使用する
func process(items ...interface{}) {
    for _, item := range items {
        fmt.Println(item)
    }
}

func main() {
    data := []string{"apple", "banana", "cherry"}

    // 引き続きスライスの手動変換は必要
    vals := make([]interface{}, len(data))
    for i, v := range data {
        vals[i] = v
    }

    // スプレッド演算子を使用して要素を渡す
    process(vals...)
}

解決策3:モダンな方法(ジェネリクス)

Go 1.18 以降を使用している場合、このために []interface{} を使用するのはやめましょう。ジェネリクスが優れた選択肢です。データを汎用的なインターフェースコンテナに強制的に入れる代わりに、関数が任意の型 T のスライスを受け取れるようにします。

// 任意の型 T のスライスを受け取るジェネリック関数
func process[T any](items []T) {
    for _, item := range items {
        fmt.Println(item)
    }
}

func main() {
    data := []string{"apple", "banana", "cherry"}
    nums := []int{10, 20, 30}

    // 手動変換は不要!
    process(data)
    process(nums)
}

検証とパフォーマンス

- **コンパイルチェック:** `go build` を実行します。エラーが消えれば、型が正しく調整されています。
- **メモリチェック:** 1,000,000個の文字列を含むスライスを変換する場合、解決策1ではインターフェーススライスのために約16MBの新しいメモリが割り当てられます。高頻度のループ内でこれが発生する場合は、ベンチマークを行ってください。
- **型安全性:** 解決策3(ジェネリクス)が最も安全です。元の型情報を保持しますが、`interface{}` では型アサーションを行うまでその情報は失われます。

重要なまとめ

- スライスは共変(covariant)ではありません。`T` が何であれ、`[]T` は決して `[]interface{}` ではありません。
- 明示的な変換は Go の機能であり、バグではありません。これにより、隠れた O(n) のパフォーマンス低下を防いでいます。
- モダンな Go プロジェクトで柔軟な関数を作成する場合、ジェネリクス(`[T any]`)を第一の選択肢にすべきです。

Related Error Notes