SoFunction
Updated on 2025-04-07

The essential difference between new and make functions in Golang

In Go language development, new() and make() are two built-in functions that are easy to confuse developers. Although they are all used for memory allocation, there are essential differences in their design purposes, applicable scenarios, and underlying implementations. This article will implement three dimensions through type system, memory model and compiler to deeply analyze the essential differences between these two functions.

1. The philosophical division of type systems

1.1 Generalization design of new()

new(T) is a general memory allocator designed for all types, with a highly uniform behavior pattern:

// Allocate zero-value memory for int typepInt := new(int)  // *int type// Allocate memory for custom structurestype MyStruct struct { a int }
pStruct := new(MyStruct) // *MyStruct type

Its core features:

  • The return type is always *T
  • The allocated memory is initialized to type zero value
  • Suitable for any type (including basic types, structures, arrays, etc.)

1.2 The specialized mission of make()

make() is a constructor designed by Go for specific reference types:

// Create slices := make([]int, 5, 10) 
// Initialize mapm := make(map[string]int)
// Create channelch := make(chan int, 5)

Key limitations:

  • Only available for slice, map and channel types
  • Returns an initialized instance of type (non-pointer)
  • Supports type-specific initialization parameters

2. Differences in the implementation of memory models

2.1 The underlying mechanism of new()

When the compiler encounters new(T):
1. Calculate the type size: size = (T{})
2. Call allocate memory
3. Perform memory clearing operation (corresponding to zero value initialization)
4. Return a pointer to this memory

The following pseudo-code schematically illustrates its process:

func new(T) *T {
    ptr := malloc(sizeof(T))
    *ptr = T{}  // Zero value initialization    return ptr
}

2.2 Syntax parsing of the compiler front-end

// Original code snippettype MyStruct struct { a int }
p := new(MyStruct)
// Convert to intermediate representation (IR)ptr := ((&MyStruct{}))

The compiler will replace new(T) with a direct call to , passing type meta information as a parameter.

2.3 Memory allocation to enter the runtime system

It is the core entrance of new(), defined in runtime/:

func newobject(typ *_type)  {
    return mallocgc(, typ, true)
}

Key parameter explanation

  • : The size of the target type (calculated statically by the compiler)
  • typ: Pointer to type metadata (describes memory layout)
  • true: Indicates whether a clear operation is required (corresponding to zero value initialization)

2.4 In-depth memory allocation process of mallocgc

mallocgc is a general memory allocation function, responsible for selecting different allocation strategies according to the object size:

Tiny Allocator
For objects less than 16 bytes:

if size <= maxSmallSize {
    if noscan && size < maxTinySize {
        // Use per-P's tiny allocator        off := 
        if off+size <= maxTinySize &&  != 0 {
            x = ( + off)
             = off + size
            return x
        }
        // ...
    }
}
  • Use thread-local cache (mcache) to improve the allocation speed of small objects
  • Merge multiple micro-objects into a memory block to reduce fragmentation

Regular object assignment
For larger objects, go to the standard allocation path:

var span *mspan
systemstack(func() {
    span = largeAlloc(size, needzero, noscan)
})
x = (())
  • Request a new memory page through mheap Global Heap Manager
  • Involving complex idle list search and page segmentation algorithms

2.5 Type specialization processing of make()

The compiler converts make to different runtime function calls:

type Internal functions Key parameters
slice Element type, length, capacity
map Initial bucket number
channel Buffer size

The underlying processing flow taking slice as an example:

// The compiler converts make([]int, 5, 10) toptr, len, cap := ((int(0)), 5, 10)
return Slice{ptr: ptr, len: 5, cap: 10}

3. Zero value vs ready state

3.1 Implementation details of new() zero value initialization

The memory pointed to by the returned pointer will be automatically zeroed:

if needzero {
    memclrNoHeapPointers(x, size)
}
  • memclrNoHeapPointers is a quick zeroing routine written in assembly
  • Optimize clearing speed using SIMD instructions for different memory blocks

3.2 The zero-value dilemma of new()

Although new() can complete basic memory allocation, it may produce unexpected results for complex types:

// Create slice pointersp := new([]int)
*sp = append(*sp, 1)  // Legal but unconventional usage(*sp)[0] = 1          // Runtime panic(Index crosses boundary)

Although the slice header structure (ptr/len/cap) is assigned at this time, however:

  • The underlying array pointer is nil
  • length and capacity are both 0

3.3 Initialization guarantee of make()

make() ensures that the returned object is immediately available:

s := make([]int, 5)
s[0] = 1          // Safe operationch := make(chan int, 5)
ch <- 1           // It won't blockm := make(map[string]int)
m["key"] = 1      // Won't panic

The initialization process includes:

  • Assign the underlying array to slice
  • Initialize the hash bucket of map
  • Create a ring buffer for channel

4. Compiler optimization strategy

4.1 Difference processing of escape analysis

The assigned objects in new() may be assigned to the stack:

func localAlloc() *int {
    return new(int)  // Stack allocation may be performed}

The compiler will decide whether the object needs to be allocated to the heap during compilation:

// If an escape occurs, generate a callif  {
    call = mkcall("newobject", ...)
} else {
    // Allocation of space directly on the stack}
  • Use -gcflags="-m" to view specific escape decisions
  • Stack allocation completely bypass mallocgc, significantly improving performance

And the objects created by make always escape to the heap:

func createSlice() []int {
    return make([]int, 10)  // Heap allocation must be}

4.2 Initialization optimization

The compiler will optimize the immediate assignment after new():

p := new(int)
*p = 42
// Optimized to directly allocate initialized memory

5. Assembly output verification of typical platforms

Taking the AMD64 platform as an example, observe the generated machine code:

//go tool compile -S 
MOVQ    $(SB), AX  ;; Load type metadata
CALL    (SB)   ;; Calling the allocation function
  • Type metadata is stored in read-only segments to ensure security of multi-coroutine access
  • The final calling convention follows Go's unique ABI specification

6. Practical suggestions and model selection

6.1 Select a decision tree

Whether to create a reference type?
├─ yes → Must use make()
└─ no → yesno需要指针?
       ├─ yes → use new()
       └─ no → use字面量初始化

6.2 Performance considerations

For structure initialization, it is recommended to use the value type directly:

// Better than new(MyStruct)var s MyStruct

Use new() when explicit pointer semantics are required

6.3 Special usage mode

Combined use to implement delayed initialization:

type LazyContainer struct {
    data *[]string
}
func (lc *LazyContainer) Get() []string {
    if  == nil {
         = new([]string)
        * = make([]string, 0, 10)
    }
    return *
}

7. Performance optimization enlightenment

1. Try to keep small structures on the stack

  • Control the size of the structure to avoid unconscious escape

2. Beware of GC pressure caused by large objects

  • Objects over 32KB are allocated directly from the heap

3. Batch initialization replaces multiple times new()

  • Reduce overhead using object pools or slice preallocation

8. Understand differences from design philosophy

The Go language embodies the design philosophy of its type system through the separation of new and make:

  • Clarity: Force developers to explicitly handle special initialization requirements for reference types
  • Security: Avoid runtime errors caused by uninitialized reference types
  • Orthogonality: Keeping the basic type system isolated from the reference type system

Although this design increases learning costs for beginners, it provides better maintainability and runtime security for large projects.

Through the analysis of memory allocation mechanism, compiler optimization strategies and language design philosophy, we can clearly realize that new() is a general memory allocation primitive, and make() is a type-aware constructor for reference types. Understanding this difference will help developers write elegant code that is more in line with Go's design ideas.

This is the end of this article about the new() and make() functions in Golang. For more related contents of Golang new() and make() functions, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!