• English
  • Example: io.MultiWriter

    io.MultiWriter makes multiple io.Writer values behave like one writer.

    Each call to Write is sent to the writers in order.

    The important boundary is:

    io.MultiWriter duplicates writes synchronously. It does not provide buffering, concurrency, retry, or rollback.

    Quick Example

    package main
    
    import (
    	"io"
    	"log"
    	"os"
    )
    
    func main() {
    	file, err := os.OpenFile(
    		"application.log",
    		os.O_CREATE|os.O_WRONLY|os.O_APPEND,
    		0644,
    	)
    	if err != nil {
    		log.Fatal(err)
    	}
    	defer file.Close()
    
    	w := io.MultiWriter(os.Stdout, file)
    
    	if _, err := w.Write([]byte("server started\n")); err != nil {
    		log.Fatal(err)
    	}
    }

    The same bytes are written to stdout and the file.

    MultiWriter does not create a second copy of the data or buffer it internally. It simply forwards each Write call to the underlying writers.

    How It Behaves

    Given:

    w := io.MultiWriter(w1, w2, w3)

    a write behaves conceptually like:

    Write(p)
       |
       +--> w1.Write(p)
       |
       +--> w2.Write(p)
       |
       +--> w3.Write(p)

    The writers are processed sequentially and in the order provided.

    If a writer returns an error, MultiWriter stops and does not call the remaining writers.

    A writer that returns a short write without an error causes io.ErrShortWrite.

    This makes MultiWriter suitable for straightforward fan-out, but not for independent delivery guarantees.

    Common Use

    Logging to stdout and a file

    logger := log.New(
    	io.MultiWriter(os.Stdout, file),
    	"",
    	log.LstdFlags,
    )

    Writing data while calculating a hash

    A hash implementation such as sha256.Hash is an io.Writer, so it can receive the same stream while the primary destination is being written:

    hasher := sha256.New()
    
    w := io.MultiWriter(file, hasher)
    
    if _, err := io.Copy(w, src); err != nil {
    	return err
    }
    
    sum := hasher.Sum(nil)

    This avoids reading the source twice.

    Other useful combinations include:

    • writing a generated stream to two files
    • saving a local copy while sending data elsewhere
    • recording a stream while passing it to its primary destination

    Common Mistakes

    1. Assuming writes are concurrent

    They are not.

    If w2 is slow, the caller waits for w2 before w3 is attempted.

    If destinations have independent latency or failure requirements, use separate asynchronous pipelines instead of putting them behind one MultiWriter.

    2. Treating a failure as transactional

    MultiWriter does not roll back successful writes.

    For example:

    w1: accepts the data
    w2: fails
    w3: never called

    The caller gets an error, but w1 has already received the data.

    This matters when retrying the operation. A retry may send the same bytes to w1 again.

    MultiWriter therefore works best when duplicate delivery or partial delivery is acceptable, or when the application has an explicit recovery strategy.

    3. Assuming n describes every destination

    The returned byte count is not a commit record for the entire fan-out.

    If an earlier writer succeeds and a later writer fails, n reflects the write that produced the returned result; it does not tell you how much data earlier writers have already accepted.

    Do not use n == 0 as proof that no destination received data.

    4. Forgetting resource ownership

    MultiWriter only implements io.Writer. It does not close its underlying writers.

    If you pass an io.WriteCloser, the caller still owns its lifecycle:

    file, err := os.Create("output.log")
    if err != nil {
    	return err
    }
    defer file.Close()
    
    w := io.MultiWriter(file, os.Stdout)

    API Selection

    NeedUse
    Send the same bytes to multiple writersio.MultiWriter
    Combine multiple readers sequentiallyio.MultiReader
    Copy one stream to one destinationio.Copy
    Observe a stream while passing it onwardio.TeeReader
    Send data independently to slow destinationsSeparate asynchronous pipelines

    Rule of Thumb

    Use io.MultiWriter when several destinations should receive the same stream synchronously.

    It is a writer composition primitive, not a replication, buffering, or delivery system.