skip to content

What does Go's unsafe.Sizeof(x) return, and is it computed at run time or at compile time?

level: juniorimportance: should knowfreq 30%

answer

  1. the compiler already knows the answer
  2. no function actually runs for it
  3. legal where a constant is required
  4. a uintptr constant, not an int

basics

~20 s

unsafe.Sizeof(x) gives the size in bytes of x's own type representation. The compiler replaces the call with a uintptr constant, so no code runs for it and the result never depends on the contents of x.

solid answer

~50 s

`unsafe.Sizeof(x)` reports how many bytes a variable of x's type occupies, as if you had written `var v = x` and measured v. It is not an ordinary function call: the compiler folds it into a typed `uintptr` constant, so it costs nothing at run time and may appear anywhere a constant is required, such as an array length or a `const` declaration. Its two siblings work the same way — `unsafe.Alignof(x)` gives the required alignment of that type, and `unsafe.Offsetof(s.f)` gives the byte offset of field `f` from the start of struct `s`. Because the answer is fixed at compile time it measures only the value's own representation and never follows a pointer to count what is on the other end. The one exception to constancy is generic code, where an argument whose type is a type parameter has no compile-time size.

code

go · 11 lines
go
type point struct {
	x, y int32
}

var p point

const size = unsafe.Sizeof(p) // a uintptr constant: 8
var _ [unsafe.Sizeof(p)]byte  // legal: array lengths must be constant

fmt.Println(unsafe.Sizeof(p))        // 8
fmt.Println(unsafe.Sizeof(int64(0))) // 8

go deeper

for a junior

Be ready to say in one sentence that it reports the byte size of a type and that the compiler works it out, so nothing happens at run time. Knowing that the same number comes back regardless of the value is enough here.

for a middle

An interviewer expects the mechanics: a typed uintptr constant, usable as an array length or const initialiser, not usable as a function value, and only non-constant when the argument's type is a type parameter.

for a senior

Show that you know what the number excludes and would never hand it to someone as a memory figure without saying so. Be ready to explain why a struct's size can exceed the sum of its fields.

for a principal

Own the position on whether layout facts belong in shared code at all: a number baked into a build is an assumption about the target, and it needs an enforcement mechanism and a review policy rather than a comment.

## The three functions The `unsafe` package exposes three measuring operations. Despite their spelling they are not ordinary functions — the compiler recognises the call and substitutes a value: - `unsafe.Sizeof(x) uintptr` — the number of bytes a variable of x's type occupies. - `unsafe.Alignof(x) uintptr` — the required alignment of that type: the largest m such that the address of such a variable is always 0 mod m. - `unsafe.Offsetof(s.f) uintptr` — the byte offset of field `f` measured from the address of the struct denoted by `s`. The language specification defines Sizeof and Alignof in terms of a hypothetical variable: the result is the size or alignment of `v` as if you had written `var v = x`. The value of x is irrelevant; only its type matters. `unsafe.Sizeof("")` and `unsafe.Sizeof("a very long string")` produce the same number, because both are `string` values. ## Compile time, not run time The specification says the results are Go constants of type `uintptr` whenever the argument's type has constant size. That has several practical consequences: 1. **No code executes.** There is no call in the compiled binary, no reflection, no lookup of a type descriptor. The compiler writes a literal into the instruction stream, or more often folds it away entirely. 2. **They work in constant contexts.** `const headerSize = unsafe.Sizeof(x)` is legal, and so is an array type such as `[unsafe.Sizeof(x)]byte`. Ordinary function results cannot be used in either position. This is what makes compile-time size assertions possible. 3. **They cannot be used as values.** `f := unsafe.Sizeof` does not compile; the builtin must be called in place. 4. **The type is `uintptr`, not `int`.** Because the constant is typed, `var n int = unsafe.Sizeof(x)` is rejected — write `int(unsafe.Sizeof(x))`. Functions that accept any integer type, such as `make`, take it directly. The only case where the result is not a constant is a type of variable size, which in practice means a type parameter in generic code (or a type built from one). Inside `func f[T any](v T)`, `unsafe.Sizeof(v)` still yields the right number for each instantiation, but it is not a constant and cannot be an array length. ## What it does and does not measure Sizeof measures the value's own representation — the bytes the variable itself occupies in a struct field, a local slot, or an array element. It never dereferences. For a pointer it reports the size of the pointer, not of the pointee. For types that are internally a pointer plus some bookkeeping words, it reports exactly those words. That is the single most common surprise for people arriving from a language where a size query walks the object graph, and it is why the number is useless as a memory budget without extra work. Also note that the size of a struct is not necessarily the sum of the sizes of its fields: the compiler may insert padding so each field lands on a suitable boundary, and may pad the end of the struct as well. Sizeof reports the real, padded size — the size the compiler will actually reserve. ## Importing unsafe Using these three operations does require importing `unsafe`, which carries a reputation and, in many codebases, a review policy. It is worth knowing that Sizeof, Alignof and Offsetof do not by themselves break any safety guarantee: they produce a number, they never produce a pointer, and they cannot corrupt memory. They are the tamest members of the package, though a build that depends on a particular number is still a build that has made an assumption about its target platform.

  • What do unsafe.Alignof and unsafe.Offsetof return, and are they constants too?
    `unsafe.Alignof(x)` returns the required alignment in bytes of a value of x's type — the largest m for which its address is always 0 mod m. `unsafe.Offsetof(s.f)` takes a selector and returns the byte offset of field `f` from the start of the struct `s` names. Both fold to `uintptr` constants under the same rule as Sizeof.
  • Can you store unsafe.Sizeof in a variable and call it later?
    No. It is a compiler builtin rather than an ordinary function, so `f := unsafe.Sizeof` does not compile — the operation must appear as a complete call, which the compiler then replaces with a constant. The same is true of Alignof and Offsetof.
  • Is unsafe.Sizeof ever not a compile-time constant?
    Yes, when the argument's type has variable size — in practice, a type parameter or a type containing one. Inside a generic function, `unsafe.Sizeof(v)` for a value of type-parameter type still yields the correct size for each instantiation, but it is not a constant and cannot be used as an array length.

It is a measurement taken from the blueprint, not from the finished building: the compiler reads the type and writes down a number, and nobody visits the value.

saying these in an interview costs you the question

  • Thinks it is a runtime call that inspects the value
  • Expects the result to change with the value's contents
  • Says it counts all memory the value can reach
  • Assumes the result is an int rather than a uintptr
  • Believes it needs reflection or a type descriptor to work