• English
  • Go syscall in Production: Where Portable Go Ends and the OS Begins

    The syscall package is one of the easiest Go packages to misuse.

    It exposes low-level operating-system primitives directly: file descriptors, socket addresses, system calls, native errors, and platform-specific constants. That makes it powerful, but it also removes many of the guarantees that higher-level packages provide.

    For most application code, syscall is the wrong abstraction.

    The Go documentation says this explicitly: syscall is primarily used inside packages such as os, time, and net, and code should use those packages when possible. The documentation also points new code toward golang.org/x/sys, which provides broader system-call support.

    This does not make syscall unimportant.

    It makes the package more interesting.

    Understanding syscall explains where os.File, sockets, native handles, errno, and many of Go's operating-system abstractions actually come from. It also tells you when a production requirement has crossed the boundary from portable Go into OS-specific engineering.

    This article focuses on that boundary, using Go 1.26.

    1. The First Rule: Do Not Start With syscall

    A useful hierarchy is:

    application requirement
            |
            v
    standard library
            |
            +---- os
            +---- net
            +---- os/exec
            +---- io
            +---- io/fs
            |
            v
    golang.org/x/sys
            |
            v
    native operating-system interface

    The normal decision should be:

    Can os/net/os/exec/etc. express the requirement?
            |
           yes
            |
            v
    Use the standard library.
    
           no
            |
            v
    Does golang.org/x/sys provide the required primitive?
            |
           yes
            |
            v
    Use x/sys.
    
           no
            |
            v
    Only then consider a lower-level mechanism.

    Every step downward increases the amount of platform knowledge the application has to carry.


    2. What syscall Actually Is

    The package documentation describes syscall as an interface to low-level operating-system primitives.

    Its details vary by operating system and architecture.

    That last sentence is the important part.

    There is no single universal syscall API with identical semantics everywhere.

    For example:

    Linux
        file descriptors
        errno
        syscalls
        socket options
        pidfds
        epoll
    
    Windows
        HANDLE
        Win32 APIs
        GetLastError
        overlapped I/O
        process handles
    
    Darwin
        BSD/POSIX interfaces
        Mach-related facilities
        platform-specific constants

    Go's portable packages deliberately hide most of this.

    syscall exposes much more of it.


    3. syscall Is an OS Boundary, Not an Application Abstraction

    A function like:

    func LoadConfig(path string) ([]byte, error)

    should normally use:

    os.ReadFile

    rather than directly invoking a system call.

    The reason is not that the system call is "bad."

    The reason is that os already handles the operating-system details required to provide a portable Go abstraction.

    Conceptually:

    your code
        |
        v
    os.ReadFile
        |
        v
    os.File
        |
        v
    platform-specific implementation
        |
        v
    kernel

    Replacing the top half with:

    your code
        |
        v
    syscall
        |
        v
    kernel

    means your code now owns more of the platform contract.

    That is usually a bad trade.


    4. The Package Exists, but It Is Not the Preferred Extension Point

    The current syscall documentation contains an unusually direct warning: most new low-level code should prefer golang.org/x/sys where possible.

    The reason is historical as well as practical.

    The original syscall package became part of Go's early operating-system interface. Over time, more portable abstractions moved into packages such as os and net, while platform-specific system support evolved in golang.org/x/sys.

    The result is a deliberate split:

    portable API
        |
        v
    standard library
    
    supplemental OS API
        |
        v
    golang.org/x/sys
    
    legacy / low-level compatibility surface
        |
        v
    syscall

    Learning syscall is therefore valuable even when using it directly is uncommon.


    5. A System Call Is Not Just a Function Call

    At the application level:

    n, err := f.Read(buf)

    looks like an ordinary method call.

    At the OS boundary, the operation may involve:

    Go code
       |
       v
    runtime / standard library
       |
       v
    system-call ABI
       |
       v
    kernel
       |
       v
    device / filesystem / network stack

    The kernel may:

    • validate arguments;
    • check permissions;
    • inspect process state;
    • block;
    • return partial progress;
    • return an interrupt-related error;
    • modify kernel-managed state.

    A direct syscall therefore carries semantics that are much more complicated than its Go signature suggests.


    6. errno Becomes error

    On Unix-like systems, operating-system calls commonly communicate failure through errno.

    Go represents the numeric error as an error, typically through syscall.Errno.

    For example:

    err := syscall.Chdir("/does/not/exist")
    if err != nil {
        fmt.Println(err)
    }

    The useful property is that the error is not merely text.

    It can preserve the underlying operating-system error identity.

    At the portable application layer, however, prefer higher-level equivalents where they exist:

    if errors.Is(err, os.ErrNotExist) {
        // ...
    }

    The standard library deliberately gives application code more portable error identities.


    7. Do Not Parse System Error Strings

    This is fragile:

    if strings.Contains(err.Error(), "permission denied") {
        // ...
    }

    System error strings are for humans.

    Use error identity:

    if errors.Is(err, syscall.EACCES) {
        // ...
    }

    or, preferably at the portable layer:

    if errors.Is(err, os.ErrPermission) {
        // ...
    }

    When wrapping errors:

    return fmt.Errorf("open device: %w", err)

    preserve the original error.

    That allows callers to continue using errors.Is and errors.As.


    8. syscall.Errno Is Useful at the OS Boundary

    A low-level component may need to distinguish specific native errors:

    var errno syscall.Errno
    if errors.As(err, &errno) {
        // platform-specific decision
    }

    But do not let native error handling leak unnecessarily into the rest of the application.

    A good low-level package can translate:

    native error
        |
        v
    component-specific error
        |
        v
    application

    rather than forcing every caller to know what EINTR, EAGAIN, or a Windows native error code means.


    9. syscall.Errno Is Not a Portable Error Vocabulary

    Code such as:

    errors.Is(err, syscall.EAGAIN)

    is Unix-oriented.

    A Windows implementation has a different native error model.

    If the behavior is platform-specific, isolate it behind build-tagged files or a small platform-specific package.

    For example:

    feature.go
    feature_linux.go
    feature_windows.go

    The rest of the application can depend on:

    func configureFeature(...) error

    rather than importing Unix-specific constants everywhere.


    10. File Descriptors Are Handles, Not Files

    Unix uses integer file descriptors:

    0 -> stdin
    1 -> stdout
    2 -> stderr

    A descriptor is a process-local reference to a kernel-managed resource.

    It may refer to:

    regular file
    socket
    pipe
    terminal
    device
    event mechanism

    So:

    file descriptor != regular file

    The same integer type can identify very different resources.


    11. File Descriptor Lifetime Is a Resource Ownership Problem

    Consider:

    fd, err := syscall.Open(path, syscall.O_RDONLY, 0)
    if err != nil {
        return err
    }
    
    defer syscall.Close(fd)

    The descriptor is now owned by your code.

    If you forget to close it:

    request
       |
    open fd
       |
    return
       |
    fd remains open

    Under enough requests, the process can eventually hit file-descriptor limits.

    This is exactly why os.File is usually preferable:

    f, err := os.Open(path)
    if err != nil {
        return err
    }
    defer f.Close()

    12. The Same Integer Can Be Reused

    A file descriptor is not a permanent identity.

    For example:

    open A -> fd 7
    close A
    open B -> fd 7

    The number 7 can now refer to something completely different.

    This creates bugs when code treats:

    fd == 7

    as if it meant:

    the same underlying resource as before

    It does not.

    Resource identity comes from ownership and lifetime, not from the integer alone.


    13. syscall.Close Is Easy to Misuse

    Do not mix ownership:

    f, err := os.Open(path)
    if err != nil {
        return err
    }
    
    defer f.Close()
    
    syscall.Close(int(f.Fd()))

    Now two layers believe they own the same resource.

    The *os.File still exists, but its underlying descriptor has been closed behind its back. Later operations may fail, and descriptor reuse can make the failure especially confusing.

    The rule is:

    The layer that owns a resource should be responsible for closing it.

    If *os.File owns the descriptor, use:

    f.Close()

    not:

    syscall.Close(int(f.Fd()))

    14. File.Fd() Is an Escape Hatch

    When code has:

    f *os.File

    it can obtain the underlying descriptor:

    fd := f.Fd()

    This is useful when integrating with a native API.

    It is not an invitation to manage the descriptor manually.

    A healthy pattern is:

    os.File owns lifetime
            |
            v
    Fd() exposes native reference
            |
            v
    platform-specific operation

    An unhealthy pattern is:

    os.File
       |
    Fd()
       |
    manual close
       |
    os.File still alive

    15. RawConn Often Gives You a Better Boundary

    For network operations, the standard library can expose controlled access to an underlying descriptor through syscall.RawConn.

    For example, a net.Conn can expose it through SyscallConn.

    The important idea is:

    net.Conn
       |
       v
    standard library owns descriptor
       |
       v
    SyscallConn
       |
       v
    your native operation

    This is preferable to extracting an integer descriptor and treating it as an independently owned resource.


    16. Do Not Assume a Descriptor Is Safe to Use Forever

    Even if you have:

    fd := int(f.Fd())

    the descriptor's validity is tied to the lifetime of the underlying resource.

    If another goroutine closes the file:

    goroutine A
        |
        | fd = 7
        v
    goroutine B
        |
        | Close()
        v
    fd 7 released
        |
        v
    another resource may receive fd 7

    Now goroutine A may accidentally operate on a different resource.

    Low-level descriptor use therefore requires explicit lifetime coordination.


    17. Direct syscall.Read and syscall.Write Are Not io.Reader and io.Writer

    A direct call might look like:

    n, err := syscall.Read(fd, buf)

    This exposes native semantics directly.

    By contrast:

    n, err := r.Read(buf)

    works through the io.Reader contract.

    The higher-level abstraction can provide:

    portable semantics
    resource ownership
    buffering
    deadline integration
    runtime integration
    specialized optimizations

    Direct system calls bypass some of those layers.

    Use them when those layers are precisely what you need to bypass.


    18. Do Not Build a Faster Read by Default

    A tempting optimization is:

    io.Reader
       |
    remove abstraction
       |
    syscall.Read
       |
    faster?

    There is no reason to assume this will be faster.

    The standard library already integrates I/O with the Go runtime and, for many paths, contains specialized implementations.

    Replacing a high-level operation with a direct syscall can remove useful runtime integration rather than improving performance.

    Benchmark the complete workload before crossing the abstraction boundary for performance reasons.


    19. Blocking Is a Runtime Concern

    A system call can block.

    That matters enormously in Go.

    The runtime needs to know when an operation may block so that it can schedule other goroutines appropriately.

    This is one reason os, net, and other standard-library packages do not simply expose raw kernel calls as their primary API.

    A direct low-level call can interact differently with:

    goroutine scheduling
    network polling
    thread parking
    interrupt handling
    deadlines
    cancellation

    The right question is not:

    Can I invoke the syscall?

    It is:

    What runtime and resource semantics do I lose or change by invoking it directly?


    20. RawSyscall Is Not a General Optimization

    The package contains low-level syscall entry points, including variants intended for special runtime-facing situations.

    These are not general-purpose faster versions of ordinary syscalls.

    A low-level primitive that bypasses expected runtime integration can be correct only under very specific conditions.

    For ordinary application code:

    do not choose RawSyscall because it looks lower-level or faster

    If code genuinely requires it, the runtime and platform contract—not a microbenchmark alone—must justify the choice.


    21. EINTR Is an OS-Level Semantic, Not a Generic Go Retry Rule

    Unix system calls may return:

    EINTR

    when interrupted by a signal.

    It is tempting to write:

    for {
        n, err := syscall.Read(fd, buf)
        if err != syscall.EINTR {
            return n, err
        }
    }

    But blindly retrying every EINTR is not a universal rule.

    Before implementing a retry loop around a direct syscall, understand:

    which syscall
    which OS
    which operation
    whether partial progress is possible
    whether the wrapper already retries

    Higher-level Go packages often already handle relevant details.

    Do not copy a generic "retry EINTR" snippet into unrelated code.


    22. Nonblocking I/O Is More Than O_NONBLOCK

    A low-level example may set:

    syscall.O_NONBLOCK

    and then expect:

    Read
      |
      +-- data
      +-- EAGAIN

    But integrating nonblocking I/O into a Go service is a much larger problem.

    You also need:

    readiness notification
    runtime poller
    deadlines
    goroutine scheduling
    partial reads/writes
    backpressure
    shutdown

    This is exactly why net.Conn is a better abstraction for ordinary network programming.

    The native flag is one piece of a complete I/O model.


    23. The Standard Library Already Uses Low-Level Primitives

    A useful way to understand syscall is to inspect what higher-level packages do with it.

    Conceptually:

    os
     |
     +-- platform-specific syscall layer
     |
     +-- runtime integration
     |
     +-- resource management
     |
     +-- portable API

    Likewise:

    net
     |
     +-- socket primitives
     |
     +-- poller
     |
     +-- deadlines
     |
     +-- connection semantics
     |
     +-- portable API

    The standard library is not avoiding system calls.

    It is containing them.

    That is a much better model for production engineering.


    24. golang.org/x/sys Is the Preferred Low-Level Extension Point

    When the standard library does not expose a required operating-system feature, the usual next step is:

    import "golang.org/x/sys/unix"

    on Unix-like systems, or:

    import "golang.org/x/sys/windows"

    on Windows.

    The x/sys repository explicitly describes itself as providing supplemental Go packages for low-level OS interaction, including unix and windows.

    It is maintained as part of the Go project ecosystem and is designed specifically for this layer.

    That is why modern low-level Go code usually looks more like:

    os
      |
      v
    x/sys
      |
      v
    kernel

    than:

    application
      |
      v
    syscall
      |
      v
    kernel

    25. syscall and x/sys Are Not Equivalent Choices

    It is tempting to say:

    x/sys is just the newer name for syscall.

    That is too simplistic.

    The distinction is architectural.

    syscall is part of the standard library and has a long compatibility history.

    x/sys is an external module specifically intended to provide broader and more current low-level OS support.

    The official syscall documentation explicitly recommends x/sys for most new low-level code.

    So for a new Linux-specific feature:

    Need native Linux primitive
            |
            v
    Check os/net/etc.
            |
            v
    Check x/sys/unix
            |
            v
    Only use syscall if there is a concrete reason.

    26. Build Tags Are Part of Low-Level API Design

    Platform-specific system calls should not be scattered throughout portable files.

    Prefer:

    feature.go
    feature_linux.go
    feature_windows.go
    feature_darwin.go

    For example:

    //go:build linux
    
    package feature
    
    func enableFeature(fd int) error {
        // Linux-specific implementation.
        return nil
    }

    and:

    //go:build windows
    
    package feature
    
    func enableFeature(handle uintptr) error {
        // Windows-specific implementation.
        return nil
    }

    The portable package can expose:

    func Enable(...) error

    while platform-specific implementation remains isolated.


    27. Cross-Compilation Is Where Weak Abstractions Break

    A package that assumes Unix-specific constants may compile perfectly on Linux and fail immediately on Windows.

    That is not a compiler nuisance.

    It is the compiler correctly exposing that the API contract was platform-specific.

    For production libraries, test the supported matrix when the package claims portability.

    Low-level code should make platform boundaries explicit rather than discovering them through downstream build failures.


    28. Native Structures Are ABI Contracts

    System calls often exchange structures whose layout is defined by the operating system.

    A Go representation is not merely a data model.

    It is part of an ABI contract involving:

    field size
    alignment
    padding
    integer width
    pointer width
    architecture
    OS version

    A structure that happens to work on:

    linux/amd64

    may be wrong on:

    linux/arm64

    or another OS.

    This is one reason to use established platform bindings rather than hand-writing native structures.


    29. unsafe Usually Appears Near syscall

    Low-level interfaces sometimes require:

    unsafe.Pointer

    because the kernel expects a pointer to native memory.

    That makes lifetime rules stricter.

    If a pointer is passed to a native operation, the referenced memory must remain valid for the operation's required lifetime.

    This is another reason to prefer:

    well-tested x/sys wrapper

    over:

    hand-written unsafe syscall

    The latter makes your package responsible for the ABI and memory contract.


    30. C Strings Are Another Boundary

    Native APIs often expect NUL-terminated strings.

    The syscall package provides helpers such as:

    p, err := syscall.BytePtrFromString(s)

    because a Go string is not automatically a C string.

    The conversion has a semantic constraint:

    Go string
        |
        v
    NUL-terminated native representation

    If the input contains an embedded NUL, the conversion can fail because the native API would otherwise see a truncated string.

    This is a small example of a broader rule:

    Native APIs have different data representations. Crossing the boundary requires explicit conversion.


    31. Socket Addresses Are a Good Example of Why net Exists

    Low-level socket programming involves:

    socket
    bind
    listen
    accept
    connect
    setsockopt
    sockaddr
    address-family constants

    A direct syscall implementation quickly becomes platform-specific.

    Go's net package turns that into:

    ln, err := net.Listen("tcp", addr)

    and:

    conn, err := net.Dial("tcp", addr)

    That buys you:

    portable address handling
    runtime poller integration
    deadlines
    connection lifecycle
    error handling

    If the requirement is simply "serve TCP traffic," a syscall-level implementation is solving the wrong problem.


    32. When Direct Socket Operations Are Justified

    There are legitimate reasons to go below net:

    special socket options
    interface binding
    Linux-specific socket features
    packet sockets
    advanced routing
    custom kernel facilities

    Even then, the preferred design is often:

    net.Conn / net.Listener
            |
            v
    SyscallConn / native handle
            |
            v
    x/sys

    when the standard library provides the appropriate escape hatch.

    That lets the standard library continue to own the resource and runtime integration.


    33. File Flags Are Part of the OS Contract

    Low-level code often manipulates flags such as:

    O_RDONLY
    O_WRONLY
    O_RDWR
    O_CREAT
    O_EXCL
    O_TRUNC
    O_APPEND
    O_NONBLOCK

    These are not portable business-level concepts.

    For example:

    fd, err := syscall.Open(
        path,
        syscall.O_WRONLY|syscall.O_CREAT|syscall.O_EXCL,
        0600,
    )

    expresses an operating-system operation directly.

    At this level, you need to understand:

    flag interaction
    permission semantics
    umask
    symlink behavior
    race behavior
    platform differences

    That is precisely why os.OpenFile should be preferred for ordinary file operations.


    34. Atomic Flags Are Valuable Because They Move Decisions Into the Kernel

    One of the strongest reasons to use a native flag is to avoid a user-space race.

    For example:

    bad:
        Stat
          |
        absent?
          |
        Create
    
    good:
        OpenFile(O_CREATE | O_EXCL)

    The second form asks the operating system to perform the relevant condition and action as one operation.

    This is a recurring systems-programming principle:

    Prefer an OS primitive that expresses the invariant directly over a sequence of observations followed by assumptions.


    35. Direct syscall Does Not Automatically Mean "Zero Copy"

    A common misconception is:

    syscall = zero copy

    No.

    A system call is simply a transition into the kernel.

    Whether data is copied depends on the particular operation and OS mechanism.

    Examples may involve:

    user buffer -> kernel buffer
    kernel buffer -> device
    kernel-to-kernel transfer
    DMA
    page mapping
    sendfile-like operations
    splice-like operations

    If the goal is zero-copy file-to-network transfer, the right abstraction may be:

    io.Copy
        |
        v
    WriterTo / ReaderFrom
        |
        v
    OS-specific optimization

    rather than hand-writing a raw syscall.

    This is one of the strongest arguments for understanding the standard library before bypassing it.


    36. The Best Low-Level Code Is Often Very Small

    A good OS-specific wrapper might look like:

    func setNativeOption(fd int, value int) error {
        if err := unix.SomeOperation(fd, value); err != nil {
            return fmt.Errorf("set native option: %w", err)
        }
        return nil
    }

    The rest of the application never sees:

    fd
    errno
    native constants
    unsafe pointers
    GOOS-specific types

    This gives you a narrow containment boundary.

    The lower the abstraction level, the smaller the surface area should usually be.


    37. Do Not Export Native Handles Unless You Mean To

    Avoid APIs such as:

    func (c *Client) FD() int

    unless exposing the native descriptor is itself part of the library's contract.

    Once an API exports a raw descriptor:

    caller can close it
    caller can change flags
    caller can duplicate it
    caller can pass it elsewhere
    caller can race with owner

    The library loses control of the resource.

    A better API is often a controlled native operation or a SyscallConn-style boundary where ownership remains explicit.


    38. Native Handles and PID Recycling

    The same principle applies to processes.

    An integer PID is a process namespace identifier.

    It can be recycled.

    A native process handle can represent a particular process resource.

    Go 1.26's Process.WithHandle exists for this class of low-level integration. It provides access to the native process reference while preserving the lifetime relationship with the os.Process object.

    Conceptually:

    Process
       |
       +-- Pid: namespace identifier
       |
       +-- WithHandle: native process reference

    The distinction is:

    PID
        "which number currently names this process?"
    
    native handle
        "which OS process resource does this reference?"

    That difference matters for process supervision and other OS-level operations.


    A useful mental model is:

                        Application
                             |
              +--------------+--------------+
              |                             |
           io.Reader                    fs.FS
              |                             |
              v                             v
             os -------------------------- io/fs
              |
              v
        operating-system
           resources
              |
         +----+-----+
         |          |
      runtime     x/sys
         |          |
         +----+-----+
              |
            kernel

    syscall historically sits close to the bottom of this stack.

    Modern Go deliberately puts most application-facing operating-system functionality above it.

    The result is that:

    os.File
    net.Conn
    os.Process
    fs.FS

    are usually better interfaces than raw descriptors and native calls.


    40. A Production Decision Table

    RequirementPreferred APILow-level fallback
    Read/write a fileos.Filex/sys / syscall only for native feature
    Open with creation flagsos.OpenFilenative open syscall
    Root-confined path accessos.Rootplatform-specific APIs only if required
    TCP/UDP networkingnetx/sys for special socket features
    Process executionos/execos.Process / native API
    Stable native process referenceProcess.WithHandleplatform-specific APIs
    Environmentosrarely a syscall concern
    Native Linux featurex/sys/unix
    Native Windows featurex/sys/windows
    OS-specific descriptor operationSyscallConn + x/sys where available
    Raw kernel primitive unavailable elsewhereisolated syscall / native implementation

    The important column is the first one.

    Start there.


    41. Production Checklist for Low-Level OS Code

    API choice

    • Does os, net, or another standard package already expose the operation?
    • Does golang.org/x/sys provide the required primitive?
    • Am I bypassing an abstraction only because I have not found the existing API?

    Resource ownership

    • Who owns the descriptor or handle?
    • Who closes it?
    • Can another goroutine close it?
    • Can the integer handle be reused?

    Runtime integration

    • Can the operation block?
    • Does it interact with Go's network poller?
    • Does it interact with deadlines?
    • Does it require special scheduler treatment?

    Errors

    • Am I checking error identity rather than error strings?
    • Is the error platform-specific?
    • Should the low-level error be translated before reaching application code?

    ABI

    • Are structure layouts architecture-dependent?
    • Are pointer sizes correct?
    • Are native constants OS-specific?
    • Does the code depend on undocumented kernel behavior?

    Portability

    • Which GOOS/GOARCH combinations are supported?
    • Are build tags isolating native code?
    • Does the package compile on every supported platform?

    Security

    • Can a native API bypass higher-level path or permission guarantees?
    • Can a raw handle be confused with another resource after reuse?
    • Are user-controlled values passed directly to the kernel?

    Performance

    • Is the low-level path actually faster?
    • Did it remove runtime integration?
    • Is the bottleneck really the syscall boundary?
    • Have you benchmarked the complete workload?

    Maintenance

    • Is the native operation documented?
    • Is there a kernel/OS version dependency?
    • Is there a fallback?
    • Is the platform-specific code contained in one small package?

    42. The Most Common syscall Mistakes

    Mistake 1: Using syscall because it looks "faster"

    Low-level does not imply faster.

    Mistake 2: Using a raw descriptor after its owner closes it

    Descriptor numbers can be reused.

    Mistake 3: Closing an os.File through syscall.Close

    This breaks resource ownership.

    Mistake 4: Parsing err.Error()

    Use errors.Is and errors.As.

    Mistake 5: Assuming errno is portable

    Native error models differ.

    Mistake 6: Writing one file for all operating systems

    Use build tags and platform-specific implementations.

    Mistake 7: Hand-writing native structure layouts

    ABI details are architecture-dependent.

    Mistake 8: Assuming a syscall is equivalent to a complete I/O model

    A syscall does not provide buffering, polling, deadlines, cancellation, or resource ownership by itself.

    Mistake 9: Exporting raw handles casually

    You are exporting control over the resource.

    Mistake 10: Reimplementing what os or net already solved

    You inherit the complexity without gaining a real capability.


    43. A Small and Reasonable x/sys Wrapper

    Suppose a Linux-specific feature is genuinely required.

    Keep the native code isolated:

    //go:build linux
    
    package nativefeature
    
    import (
        "fmt"
    
        "golang.org/x/sys/unix"
    )
    
    func configure(fd int) error {
        if err := unix.SomeNativeOperation(fd); err != nil {
            return fmt.Errorf("configure native feature: %w", err)
        }
    
        return nil
    }

    The application should depend on:

    func Configure(...) error

    not directly on the native operation.

    This is the same design principle used by the standard library itself:

    native complexity
            |
            v
    small wrapper
            |
            v
    portable application-facing behavior

    44. Test the Boundary, Not Just the Wrapper

    Low-level code can compile while still being wrong.

    Tests should cover:

    success
    invalid arguments
    permission failures
    partial progress
    interrupted operations
    platform-specific errors
    resource lifetime
    concurrent use
    cleanup after failure

    For filesystem operations, test actual filesystem semantics.

    For process operations, test process lifetime and races.

    For native handles, test what happens when the resource exits or is closed.

    A unit test that merely verifies:

    err == nil

    does not prove that the native contract is correct.


    45. Keep Platform-Specific Knowledge at the Edge

    A strong architecture often looks like:

                    application
                         |
                         v
                  portable interface
                         |
                 +-------+-------+
                 |               |
              linux           windows
                 |               |
              x/sys/unix    x/sys/windows
                 |               |
                 +-------+-------+
                         |
                      kernel

    The application should not contain dozens of conditions like:

    if runtime.GOOS == "linux" {
        ...
    } else if runtime.GOOS == "windows" {
        ...
    }

    Use build constraints when the implementation itself is platform-specific.

    This keeps compilation, testing, and review much cleaner.


    46. When syscall Is the Right Tool

    There are still cases where direct use is justified:

    maintaining existing low-level code
    compatibility with a historical syscall interface
    very specific platform integration
    a primitive not available through x/sys
    research / experimental kernel integration

    Even then, the design should be conservative.

    A good low-level package has:

    small API
    small native surface
    explicit ownership
    platform-specific files
    documented OS assumptions
    tests for native semantics

    The fact that a syscall is available is not itself a reason to expose it.


    47. The Right Way to Think About syscall

    There are three levels of abstraction:

    Level 1: application semantics
    
        "read this configuration"
        "send this request"
        "start this process"
    
    
    Level 2: Go resource semantics
    
        io.Reader
        io.Writer
        os.File
        net.Conn
        os.Process
    
    
    Level 3: operating-system semantics
    
        fd
        HANDLE
        errno
        sockaddr
        native syscall

    Production Go should stay at Level 1 or Level 2 whenever possible.

    Level 3 exists for requirements that genuinely depend on the operating system.

    The mistake is not using Level 3.

    The mistake is making Level 3 the default.


    48. Final Perspective

    syscall is valuable precisely because it exposes facts that higher-level Go APIs intentionally hide:

    resources have ownership
    handles have lifetimes
    integers can be reused
    errors have native identity
    structures have ABI layouts
    syscalls can block
    OS semantics differ
    kernel interfaces evolve

    Those facts explain many of the design decisions in os, net, os/exec, and the runtime.

    The modern production path is usually:

    need an OS operation
            |
            v
    use the standard library
            |
            | not enough?
            v
    use golang.org/x/sys
            |
            | still not enough?
            v
    use a narrowly isolated native implementation
            |
            v
    document the OS contract

    And the most important rule is this:

    Do not go lower merely because you can. Go lower when the operating system itself is part of the requirement.

    That is the right place for syscall: not as a shortcut around Go's standard library, but as a boundary for the small amount of production code that genuinely needs to speak directly in operating-system terms.


    API Selection Summary

    If you need to...Prefer
    Work with filesos
    Work with streamsio
    Work with filesystem abstractionsio/fs
    Work with socketsnet
    Run processesos/exec
    Access a native process handle in Go 1.26os.Process.WithHandle
    Use a supplemental OS primitivegolang.org/x/sys
    Access a raw connection safelySyscallConn where available
    Call a truly unsupported native primitiveisolated syscall / native implementation
    Share low-level code across platformsbuild tags + small platform wrappers

    The package hierarchy is not accidental:

    io
     |
     +---- data movement
    
    io/fs
     |
     +---- filesystem abstraction
    
    os
     |
     +---- operating-system resources
    
    net
     |
     +---- network resources
    
    os/exec
     |
     +---- process execution
    
    x/sys
     |
     +---- supplemental native interfaces
    
    syscall
     |
     +---- low-level / historical OS boundary

    The closer code gets to syscall, the more carefully its ownership, portability, ABI, runtime, and failure semantics need to be designed.