Goにおけるmakeとnew：違いとベストプラクティス

[![表紙](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967563/9623fa04-f8d7-4f52-92f8-030c0c297a9c.png)](https://leapcell.io/?lc_t=q_gomakenew)

Go 言語のメモリ割り当てには、よく使われる方法が 2 つあります：`make` と `new` です。これらはどちらもメモリを割り当てるために使われますが、役割や利用シーンが異なります。この 2 つの違いを理解することは、効率的で保守性の高い Go コードを書くために非常に重要です。本記事では、`make` と `new` の違い、適用シーン、そしていくつかの使い方のコツを詳しく解説します。

## make と new の基本的な違い

### new：ポインタ型のゼロ値を生成

`new` は Go でメモリを割り当てるためのキーワードで、特定の型に対してメモリ領域を確保し、そのメモリへのポインタを返します。`new` で割り当てられたメモリは、その型のゼロ値で初期化されます。

例：`new` の使用

```go
package main

import "fmt"

func main() {
    var p *int = new(int)
    fmt.Println(*p) // 出力: 0、`new`によってint型のメモリがゼロ値で初期化されている
}
```

- 戻り値の型：`new` は型へのポインタを返します。
- ゼロ値での初期化：`new` は割り当てたメモリをその型のゼロ値で初期化します。例えば、`int` 型ならゼロ値は `0`、`string` 型なら空文字列 `""` です。

### make：スライス・マップ・チャネルの初期化

`make` は Go 特有の組み込み関数で、3 種類の組み込みデータ型――スライス（slice）、マップ（map）、チャネル（channel）――を初期化するために特化されています。`new` とは異なり、`make` はメモリへのポインタを返すのではなく、初期化済みのオブジェクト自体を返します。

例：`make` の使用

```go
package main

import "fmt"

func main() {
    // スライスの初期化
    s := make([]int, 5)
    fmt.Println(s) // 出力: [0 0 0 0 0]

    // マップの初期化
    m := make(map[string]int)
    m["age"] = 30
    fmt.Println(m) // 出力: map[age:30]

    // チャネルの初期化
    ch := make(chan int, 2)
    ch <- 1
    fmt.Println(<-ch) // 出力: 1
}
```

- 戻り値の型：`make` はスライス、マップ、またはチャネル型のオブジェクト自体を返し、ポインタではありません。
- メモリ割り当てと初期化：`make` は単にメモリを割り当てるだけでなく、データ構造自体の初期化も行います。たとえばスライスを初期化する際には、スライスのための基礎配列を確保し、その長さや容量を設定します。

### 主な違いのまとめ

- 用途

  - `new`：メモリを割り当て、その型へのポインタを返す
  - `make`：スライス・マップ・チャネルのオブジェクトを初期化し、返す

- 戻り値

  - `new`：型のポインタ
  - `make`：初期化済みオブジェクト

- 対応する型

  - `new`：すべての型
  - `make`：スライス、マップ、チャネル

- 初期化

  - `new`：ゼロ値を返す
  - `make`：データ構造の種類に応じて初期化

---

## make と new の使い方のコツ

### new の使い方のコツ

構造体型に適している：`new` は主に構造体にメモリを割り当て、ポインタを返す用途で使われます。注意点として、構造体ポインタの初期値は構造体のゼロ値になります。

例：`new` で構造体にメモリを割り当てる

```go
type Person struct {
    Name string
    Age  int
}

func main() {
    p := new(Person)
    fmt.Println(p)      // 出力: &{ 0}
    fmt.Println(p.Name) // 出力: 空文字列
    fmt.Println(p.Age)  // 出力: 0
}
```

- `new` で作成したポインタ：`new` は構造体へのポインタを返すため、`p.Name` や `p.Age` でフィールド値を直接操作できます。

### make の使い方のコツ

容量付きスライスの初期化：`make` は指定した長さと容量を持つスライスの初期化に使うことができます。`make` を使えば、基礎配列の割り当てとスライスの初期化が効率的に行えます。

例：容量付きスライスの初期化

```go
// 長さ5、容量10のスライスを初期化
s := make([]int, 5, 10)
fmt.Println(len(s), cap(s)) // 出力: 5 10
```

- マップ初期化時に容量を指定：`make` でマップを作成する際、初期容量を指定することができ、これはパフォーマンス最適化に役立ちます。要素追加時のメモリ再割り当て回数を減らすことができます。

例：容量付きマップの初期化

```go
m := make(map[string]int, 10) // 容量10を指定
m["age"] = 30
m["height"] = 175
fmt.Println(m) // 出力: map[age:30 height:175]
```

- バッファ付きチャネルの初期化：`make` を使えばバッファ付きチャネルを作成でき、チャネルのバッファサイズも指定可能です。これは並行処理プログラムで特に有用です。

例：バッファ付きチャネルの作成

```go
ch := make(chan int, 2)
ch <- 1
ch <- 2
fmt.Println(<-ch) // 出力: 1
```

### 適切なメモリ割り当て方法の選択

- 構造体利用シーン：構造体のポインタが必要で、初期化時に特別な要件がなければ、`new` の利用がシンプルで一般的です。
- スライス・マップ・チャネル利用シーン：スライス、マップ、チャネルの初期化が必要で、それらの内容を操作したい場合は `make` が適しています。特に容量を指定したい場合は `make` を使いましょう。

## make と new のパフォーマンスに関する考慮

- メモリ割り当てのコスト：`make` はスライス、マップ、チャネルを初期化する際、単にメモリを割り当てるだけでなく型の初期化も行うため、追加のコストが発生する場合があります。一方、`new` はメモリを割り当ててゼロ値で初期化するだけなので、コストは比較的小さいです。
- 不要なメモリ割り当てを避ける：スライス、マップ、チャネルなどの型については、`make` を使う際に適切な容量を指定することで、メモリの再割り当て回数を減らすことができます。

## よくある誤用

- 誤って `new` でスライスやマップを作成する：`new` をスライス、マップ、チャネルに使うと、その型のゼロ値を返すだけで初期化は行われません。そのため、`new` で作成したスライス、マップ、チャネルの内容に直接アクセスしようとするとランタイムエラーになります。

誤用例：`new` でマップを作成

```go
m := new(map[string]int) // 誤り：返されるのはポインタで、初期化済みのマップではない
m["age"] = 30 // ランタイムエラー：m は nil
```

正しい例：マップの初期化には `make` を使うべきです。

```go
m := make(map[string]int)
m["age"] = 30
```

## まとめ

Go 言語の `make` と `new` は、どちらもメモリ割り当てのためのキーワードですが、その機能には明確な違いがあります。
`new` は型のメモリ領域を確保し、ポインタを返すもので、ほとんどの型に利用できます。一方、`make` は主にスライス、マップ、チャネルの初期化に用いられ、より強力な初期化機能を持ちます。

- `new`：構造体やその他の基本型のポインタを生成し、メモリをゼロ値で初期化するのに適しています。
- `make`：スライス、マップ、チャネルを初期化し、容量指定や内部構造の初期化もサポートしています。

この 2 つの異なる利用シーンやパフォーマンスへの影響を理解することで、より効率的で保守性の高い Go コードを記述できるようになります。

---

### [私たちはLeapcell、Goプロジェクトのホスティングの最適解です。](https://leapcell.io/?lc_t=q_gomakenew)

[![Leapcell](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967563/c67a8901-1bf0-4eac-aa69-644b104b9db3.png)](https://leapcell.io/?lc_t=q_gomakenew)

[Leapcell](https://leapcell.io/?lc_t=q_gomakenew)は、Webホスティング、非同期タスク、Redis向けの次世代サーバーレスプラットフォームです：

**複数言語サポート**

- Node.js、Python、Go、Rustで開発できます。

**無制限のプロジェクトデプロイ**

- 使用量に応じて料金を支払い、リクエストがなければ料金は発生しません。

**比類のないコスト効率**

- 使用量に応じた支払い、アイドル時間は課金されません。
- 例: $25で6.94Mリクエスト、平均応答時間60ms。

**洗練された開発者体験**

- 直感的なUIで簡単に設定できます。
- 完全自動化されたCI/CDパイプラインとGitOps統合。
- 実行可能なインサイトのためのリアルタイムのメトリクスとログ。

**簡単なスケーラビリティと高パフォーマンス**

- 高い同時実行性を容易に処理するためのオートスケーリング。
- ゼロ運用オーバーヘッド — 構築に集中できます。

[ドキュメントで詳細を確認！](https://docs.leapcell.io/)

[![Try Leapcell](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967563/87c86679-b2ae-47e8-876c-a1eec5737958.png)](https://leapcell.io/?lc_t=q_gomakenew)

Xでフォローする：[@LeapcellHQ](https://x.com/LeapcellHQ)

---

[ブログでこの記事を読む](https://leapcell.io/blog/choosing-between-make-and-new-in-go)