bufferReadCloser

package
v1.19.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 24, 2026 License: MIT Imports: 3 Imported by: 0

README

BufferReadCloser Package

Go Version Coverage

Lightweight I/O wrappers that add io.Closer support to standard Go buffer types with automatic resource cleanup and custom close callbacks.


Table of Contents


Overview

Design Philosophy

This package extends Go's standard buffered I/O types (bytes.Buffer, bufio.Reader, bufio.Writer, bufio.ReadWriter) by adding io.Closer support, enabling automatic resource cleanup and custom close callbacks.

Core Principles:

  1. Minimal Overhead: Thin wrappers with zero-copy passthrough to underlying buffers
  2. Lifecycle Management: Automatic reset and cleanup on close
  3. Flexibility: Optional custom close functions for additional cleanup logic
  4. Standard Compatibility: Implements all relevant io.* interfaces
  5. Defensive Programming: Handles nil parameters gracefully with sensible defaults
Key Features
  • Buffer Wrapper: bytes.Buffer + io.Closer with automatic reset
  • Reader Wrapper: bufio.Reader + io.Closer with resource release
  • Writer Wrapper: bufio.Writer + io.Closer with auto-flush and error propagation
  • ReadWriter Wrapper: bufio.ReadWriter + io.Closer for bidirectional I/O
  • Custom Close Callbacks: Optional FuncClose for chaining cleanup operations
  • Full Interface Support: All standard io.* interfaces preserved
  • 100% Test Coverage: 69 specs + 23 benchmarks + 13 examples
  • Zero Dependencies: Only standard library
  • Thread-Safe Patterns: Documented concurrent usage with external synchronization

Architecture

Component Diagram
┌─────────────────────────────────────────────────┐
│          bufferReadCloser Package               │
└─────────────────┬───────────────────────────────┘
                  │
     ┌────────────┼────────────┬─────────────┐
     │            │            │             │
┌────▼─────┐ ┌───▼────┐ ┌────▼─────┐ ┌─────▼────────┐
│  Buffer  │ │ Reader │ │  Writer  │ │ ReadWriter   │
├──────────┤ ├────────┤ ├──────────┤ ├──────────────┤
│bytes.    │ │bufio.  │ │bufio.    │ │bufio.        │
│Buffer    │ │Reader  │ │Writer    │ │ReadWriter    │
│    +     │ │   +    │ │    +     │ │      +       │
│io.Closer │ │io.     │ │io.Closer │ │io.Closer     │
│          │ │Closer  │ │          │ │              │
└──────────┘ └────────┘ └──────────┘ └──────────────┘
Wrapper Behavior
Wrapper Underlying Type On Close Nil Handling
Buffer bytes.Buffer Reset + custom close Creates empty buffer
Reader bufio.Reader Reset + custom close Returns EOF immediately
Writer bufio.Writer Flush + Reset + custom close Writes to io.Discard
ReadWriter bufio.ReadWriter Flush + custom close (no reset*) Empty source + io.Discard

* ReadWriter cannot call Reset() due to ambiguous methods in bufio.ReadWriter

Data Flow
User Code
    ↓
Wrapper (Buffer/Reader/Writer/ReadWriter)
    ↓
Underlying stdlib type (bytes.Buffer/bufio.*)
    ↓
Actual I/O destination/source

On Close():
    1. Flush buffered data (Writer/ReadWriter)
    2. Reset buffer (Buffer/Reader/Writer only)
    3. Execute custom FuncClose if provided
    4. Return any error
Important Considerations

Writer Flush Error Handling: Since the correction of the flush error handling bug, Writer.Close() and ReadWriter.Close() now properly return flush errors. Always check the error:

writer := bufferReadCloser.NewWriter(bw, nil)
if err := writer.Close(); err != nil {
    // Handle flush error - data may not have been written
    log.Printf("flush failed: %v", err)
}

Thread Safety: Like stdlib buffers, these wrappers are NOT thread-safe. Use external synchronization (mutex, channels) for concurrent access.

ReadWriter Limitation: Cannot reset on close due to ambiguous Reset() methods in bufio.ReadWriter. Only flush is performed.


Performance

Benchmark Results

Measured on AMD Ryzen 9 7900X3D with Go 1.25:

Operation Wrapper stdlib Overhead Allocations
Buffer.Read 31.53 ns/op 27.74 ns/op +14% 0 B/op
Buffer.Write 29.69 ns/op 29.47 ns/op +1% 0 B/op
Reader.Read 1013 ns/op 1026 ns/op -1% 4144 B/op
Writer.Write 1204 ns/op 1263 ns/op -5% 5168 B/op
Close (no func) 2.47 ns/op N/A N/A 0 B/op
Close (with func) 6.90 ns/op N/A N/A 0 B/op
Memory Usage
  • Wrapper Overhead: 24 bytes per instance (2 pointers)
  • Zero Additional Buffering: Uses existing stdlib buffers
  • Allocation Pattern: Single allocation for wrapper struct
  • GC Pressure: Minimal - only wrapper allocation
Throughput

Large data transfers (1MB):

  • Buffer: 2,598 MB/s
  • Reader/Writer: ~2,500 MB/s
  • Identical to stdlib for practical purposes
Scalability
  • O(1) memory overhead regardless of data size
  • Linear performance with data size
  • No contention (not thread-safe by design)
  • Suitable for high-throughput applications

Use Cases

1. File Processing with Automatic Cleanup

Scenario: Reading configuration files where you want both the buffer and file handle closed together.

Advantages:

  • Single defer statement handles both buffer and file cleanup
  • No risk of forgetting to close the file
  • Automatic buffer reset prevents memory leaks

How it's suited: The custom close function chains file closure with buffer cleanup, ensuring proper resource management even on error paths.

2. Network Protocol Implementation

Scenario: Implementing request/response protocols over TCP connections with buffered I/O.

Advantages:

  • Automatic flush on close ensures all data is sent
  • Connection tracking via custom close callbacks
  • Simplified error handling with single close point

How it's suited: ReadWriter wrapper provides bidirectional buffering with guaranteed flush, perfect for protocols requiring both reading and writing.

3. Buffer Pool Management

Scenario: High-performance applications using sync.Pool for buffer reuse.

Advantages:

  • Automatic reset before returning to pool
  • Prevents buffer state leakage between uses
  • Simplified pool integration

How it's suited: Custom close function returns buffer to pool after reset, eliminating manual cleanup code.

4. Testing and Mocking

Scenario: Unit tests requiring lifecycle tracking of I/O operations.

Advantages:

  • Easy verification of close calls
  • Trackable resource cleanup
  • Simplified test setup

How it's suited: Custom close callbacks enable precise tracking of when and how resources are released.

5. Middleware and Logging

Scenario: Adding cross-cutting concerns (logging, metrics, tracing) to I/O operations.

Advantages:

  • Composable wrappers
  • Non-invasive instrumentation
  • Centralized cleanup logic

How it's suited: Custom close functions provide hook points for logging and metrics without modifying core logic.

6. Temporary Data Processing

Scenario: Processing data streams where intermediate buffers should be automatically cleaned up.

Advantages:

  • Guaranteed cleanup even on panic (with defer)
  • Memory efficiency through automatic reset
  • Simplified error paths

How it's suited: Automatic reset on close prevents memory accumulation in long-running processes.


Quick Start

Installation
go get github.com/nabbar/golib/ioutils/bufferReadCloser
Basic Usage
package main

import (
    "bytes"
    "fmt"
    "github.com/nabbar/golib/ioutils/bufferReadCloser"
)

func main() {
    // Create a closeable buffer
    buf := bytes.NewBufferString("Hello, World!")
    wrapped := bufferReadCloser.NewBuffer(buf, nil)
    defer wrapped.Close() // Automatic cleanup
    
    // Use like normal buffer
    data := make([]byte, 5)
    n, _ := wrapped.Read(data)
    fmt.Printf("Read %d bytes: %s\n", n, string(data))
}
File Reading with Automatic Cleanup
package main

import (
    "bufio"
    "fmt"
    "os"
    "github.com/nabbar/golib/ioutils/bufferReadCloser"
)

func readFile(path string) error {
    file, err := os.Open(path)
    if err != nil {
        return err
    }
    
    // Wrap with custom close that closes the file
    reader := bufferReadCloser.NewReader(
        bufio.NewReader(file),
        file.Close, // Chains file close
    )
    defer reader.Close() // Closes both reader and file
    
    // Read data
    data := make([]byte, 1024)
    n, _ := reader.Read(data)
    fmt.Printf("Read %d bytes\n", n)
    
    return nil
}
Writer with Auto-Flush
package main

import (
    "bufio"
    "bytes"
    "fmt"
    "github.com/nabbar/golib/ioutils/bufferReadCloser"
)

func main() {
    dest := &bytes.Buffer{}
    bw := bufio.NewWriter(dest)
    
    writer := bufferReadCloser.NewWriter(bw, nil)
    defer func() {
        if err := writer.Close(); err != nil {
            fmt.Printf("Flush error: %v\n", err)
        }
    }()
    
    // Write data (buffered)
    writer.WriteString("buffered data")
    // Data not yet in dest
    
    // Close flushes automatically
    writer.Close()
    fmt.Println(dest.String()) // "buffered data"
}
Network Connection Management
package main

import (
    "bufio"
    "net"
    "sync/atomic"
    "github.com/nabbar/golib/ioutils/bufferReadCloser"
)

var activeConnections int64

func handleConnection(conn net.Conn) {
    rw := bufio.NewReadWriter(
        bufio.NewReader(conn),
        bufio.NewWriter(conn),
    )
    
    atomic.AddInt64(&activeConnections, 1)
    
    wrapper := bufferReadCloser.NewReadWriter(rw, func() error {
        atomic.AddInt64(&activeConnections, -1)
        return conn.Close()
    })
    defer wrapper.Close() // Auto-flush, metrics, close
    
    // Handle protocol
    // ...
}
Buffer Pool Integration
package main

import (
    "bytes"
    "sync"
    "github.com/nabbar/golib/ioutils/bufferReadCloser"
)

var bufferPool = sync.Pool{
    New: func() interface{} {
        return bytes.NewBuffer(make([]byte, 0, 4096))
    },
}

func processData(data []byte) {
    // Get buffer from pool
    buf := bufferPool.Get().(*bytes.Buffer)
    
    // Wrap with custom close that returns to pool
    wrapped := bufferReadCloser.NewBuffer(buf, func() error {
        bufferPool.Put(buf)
        return nil
    })
    defer wrapped.Close() // Resets and returns to pool
    
    // Use buffer
    wrapped.Write(data)
    // Process...
}

Best Practices

1. Always Check Close Errors

Since flush errors are now properly returned, always check the error from Close():

writer := bufferReadCloser.NewWriter(bw, nil)
if err := writer.Close(); err != nil {
    return fmt.Errorf("failed to flush: %w", err)
}
2. Use defer for Guaranteed Cleanup
reader := bufferReadCloser.NewReader(br, file.Close)
defer reader.Close() // Always executes, even on panic
3. Chain Resource Cleanup
file, _ := os.Open("data.txt")
reader := bufferReadCloser.NewReader(
    bufio.NewReader(file),
    file.Close, // Chains cleanup
)
defer reader.Close() // Single defer handles both
4. Handle Nil Parameters Intentionally

The package handles nil gracefully, but be aware of the behavior:

  • NewBuffer(nil, nil) creates an empty buffer
  • NewReader(nil, nil) returns EOF immediately
  • NewWriter(nil, nil) writes to io.Discard
5. Use External Synchronization for Concurrent Access
var mu sync.Mutex
buf := bufferReadCloser.NewBuffer(bytes.NewBuffer(nil), nil)

// Concurrent access
go func() {
    mu.Lock()
    defer mu.Unlock()
    buf.WriteString("data")
}()
Testing

The package includes comprehensive tests with 100% code coverage:

  • 69 Ginkgo specs: Unit tests covering all functionality
  • 23 benchmarks: Performance validation against stdlib
  • 13 examples: Executable documentation
  • Concurrency tests: Thread-safety patterns
  • Race detection: All tests pass with -race

For detailed testing information, see TESTING.md.

Run tests:

go test -v -cover ./...
go test -race ./...
go test -bench=. -benchmem

API Reference

Interfaces
Buffer
type Buffer interface {
    io.Reader
    io.ReaderFrom
    io.ByteReader
    io.RuneReader
    io.Writer
    io.WriterTo
    io.ByteWriter
    io.StringWriter
    io.Closer
}

Wraps bytes.Buffer with automatic reset on close.

Reader
type Reader interface {
    io.Reader
    io.WriterTo
    io.Closer
}

Wraps bufio.Reader with automatic reset on close.

Writer
type Writer interface {
    io.Writer
    io.StringWriter
    io.ReaderFrom
    io.Closer
}

Wraps bufio.Writer with automatic flush and reset on close.

ReadWriter
type ReadWriter interface {
    Reader
    Writer
}

Wraps bufio.ReadWriter with automatic flush on close (no reset due to API limitation).

Constructors
NewBuffer
func NewBuffer(b *bytes.Buffer, fct FuncClose) Buffer

Creates a Buffer wrapper. If b is nil, creates an empty buffer.

NewReader
func NewReader(b *bufio.Reader, fct FuncClose) Reader

Creates a Reader wrapper. If b is nil, creates a reader from empty source.

NewWriter
func NewWriter(b *bufio.Writer, fct FuncClose) Writer

Creates a Writer wrapper. If b is nil, creates a writer to io.Discard.

NewReadWriter
func NewReadWriter(b *bufio.ReadWriter, fct FuncClose) ReadWriter

Creates a ReadWriter wrapper. If b is nil, creates a readwriter with empty source and io.Discard destination.

Configuration
FuncClose
type FuncClose func() error

Optional custom close function called after wrapper's internal cleanup. Use for:

  • Closing underlying resources (files, connections)
  • Returning buffers to pools
  • Updating metrics or logging
  • Releasing external resources
Error Handling

Close Errors: Close() returns errors from:

  1. Flush operations (Writer, ReadWriter)
  2. Custom close functions (FuncClose)

Best Practice: Always check and handle close errors:

if err := wrapper.Close(); err != nil {
    // Handle error - data may not have been written/flushed
}

Nil Parameters: Handled gracefully with sensible defaults (no errors).

Monitoring

Track wrapper usage with custom close functions:

var (
    activeWrappers int64
    totalClosed    int64
)

wrapper := bufferReadCloser.NewBuffer(buf, func() error {
    atomic.AddInt64(&totalClosed, 1)
    atomic.AddInt64(&activeWrappers, -1)
    return nil
})
atomic.AddInt64(&activeWrappers, 1)

Contributing

Contributions are welcome! Please follow these guidelines:

Code Contributions

AI Usage Policy: AI assistance should be limited to:

  • ✅ Testing (writing tests, test cases, test data)
  • ✅ Debugging (identifying bugs, suggesting fixes)
  • ✅ Documentation (comments, README, examples)

AI must NEVER be used to:

  • ❌ Generate package implementation code
  • ❌ Design core functionality
  • ❌ Make architectural decisions

All core package code must be human-designed and validated.

Development Guidelines
  • Maintain 100% test coverage
  • All tests must pass with -race
  • Follow existing code style
  • Add GoDoc comments for all public elements
  • Update documentation for new features
  • Include examples for new functionality
Pull Request Process
  1. Fork the repository
  2. Create a feature branch
  3. Make your changes
  4. Run tests: go test -v -race -cover ./...
  5. Update documentation
  6. Submit pull request with clear description

Resources

Documentation
Community

AI Transparency

In compliance with EU AI Act Article 50.4: AI assistance was used for testing, documentation, and bug resolution under human supervision. All core functionality is human-designed and validated.


License

MIT License - See LICENSE file for details.

Copyright (c) 2020 Nicolas JUHEL


Maintained by: Nicolas JUHEL
Package: github.com/nabbar/golib/ioutils/bufferReadCloser
Minimum Go Version: 1.18+

Documentation

Overview

Package bufferReadCloser provides lightweight wrappers around Go's standard buffered I/O types (bytes.Buffer, bufio.Reader, bufio.Writer, bufio.ReadWriter) that add io.Closer support with automatic resource cleanup and custom close callbacks.

Design Philosophy

The package follows these core principles:

  1. Minimal Overhead: Thin wrappers with zero-copy passthrough to underlying buffers
  2. Lifecycle Management: Automatic reset and cleanup on close
  3. Flexibility: Optional custom close functions for additional cleanup logic
  4. Standard Compatibility: Implements all relevant io.* interfaces
  5. Defensive Programming: Provides sensible defaults when nil parameters are passed

Architecture

The package provides four main wrapper types:

┌─────────────────────────────────────────────────┐
│          bufferReadCloser Package               │
└─────────────────┬───────────────────────────────┘
                  │
     ┌────────────┼────────────┬─────────────┐
     │            │            │             │
┌────▼─────┐ ┌───▼────┐ ┌────▼─────┐ ┌─────▼────────┐
│  Buffer  │ │ Reader │ │  Writer  │ │ ReadWriter   │
├──────────┤ ├────────┤ ├──────────┤ ├──────────────┤
│bytes.    │ │bufio.  │ │bufio.    │ │bufio.        │
│Buffer    │ │Reader  │ │Writer    │ │ReadWriter    │
│    +     │ │   +    │ │    +     │ │      +       │
│io.Closer │ │io.     │ │io.Closer │ │io.Closer     │
│          │ │Closer  │ │          │ │              │
└──────────┘ └────────┘ └──────────┘ └──────────────┘

Each wrapper delegates all I/O operations directly to the underlying buffer type, ensuring zero performance overhead. The Close() method performs cleanup specific to each type and optionally calls a custom close function.

Wrapper Behavior

Buffer (bytes.Buffer wrapper):

  • On Close: Resets buffer (clears all data) + calls custom close
  • Nil handling: Creates empty buffer
  • Use case: In-memory read/write with lifecycle management

Reader (bufio.Reader wrapper):

  • On Close: Resets reader (releases resources) + calls custom close
  • Nil handling: Creates reader from empty source (returns EOF)
  • Use case: Buffered reading with automatic cleanup

Writer (bufio.Writer wrapper):

  • On Close: Flushes buffered data + resets writer + calls custom close
  • Nil handling: Creates writer to io.Discard
  • Use case: Buffered writing with guaranteed flush

ReadWriter (bufio.ReadWriter wrapper):

  • On Close: Flushes buffered data + calls custom close (no reset due to API limitation)
  • Nil handling: Creates readwriter with empty source and io.Discard destination
  • Use case: Bidirectional buffered I/O
  • Limitation: Cannot call Reset() due to ambiguous methods in bufio.ReadWriter

Advantages

  • Single defer statement handles both buffer cleanup and resource closing
  • Prevents resource leaks by ensuring cleanup always occurs
  • Composable: Custom close functions enable chaining of cleanup operations
  • Type-safe: Preserves all standard io.* interfaces
  • Zero dependencies: Only uses standard library
  • Defensive: Handles nil parameters gracefully with sensible defaults

Disadvantages and Limitations

  • Not thread-safe: Like stdlib buffers, requires external synchronization
  • ReadWriter limitation: Cannot reset on close due to ambiguous Reset methods
  • Memory overhead: 24 bytes per wrapper (pointer + function pointer)
  • Nil parameter handling: Creates default instances which may not be desired behavior

Performance Characteristics

  • Zero-copy operations: All I/O delegates directly to underlying buffers
  • Minimal allocation: Single wrapper struct per buffer
  • No additional buffering: Uses existing bufio buffers
  • Constant memory: O(1) overhead regardless of data size
  • Inline-friendly: Method calls are often inlined by compiler

Typical Use Cases

File Processing with Automatic Cleanup:

file, _ := os.Open("data.txt")
reader := bufferReadCloser.NewReader(bufio.NewReader(file), file.Close)
defer reader.Close() // Closes both reader and file

Network Connection Management:

conn, _ := net.Dial("tcp", "example.com:80")
rw := bufio.NewReadWriter(bufio.NewReader(conn), bufio.NewWriter(conn))
wrapper := bufferReadCloser.NewReadWriter(rw, conn.Close)
defer wrapper.Close() // Flushes and closes connection

Buffer Pool Integration:

buf := bufferPool.Get().(*bytes.Buffer)
wrapped := bufferReadCloser.NewBuffer(buf, func() error {
    bufferPool.Put(buf)
    return nil
})
defer wrapped.Close() // Resets and returns to pool

Testing with Lifecycle Tracking:

tracker := &TestTracker{}
buf := bufferReadCloser.NewBuffer(bytes.NewBuffer(nil), tracker.OnClose)
defer buf.Close()
// Test code...
// tracker.Closed will be true after Close()

Error Handling

Close operations may return errors from:

  • Flush operations (Writer, ReadWriter): If buffered data cannot be written
  • Custom close functions: Any error returned by the FuncClose callback

The package follows Go conventions: errors are returned, never panicked. When nil parameters are provided, sensible defaults are created instead of panicking.

Thread Safety

Like the underlying stdlib types, these wrappers are NOT thread-safe. Concurrent access requires external synchronization (e.g., sync.Mutex).

Minimum Go Version

This package requires Go 1.18 or later. All functions used are from the standard library and have been stable since Go 1.0-1.2.

Example (Basic)

Example_basic demonstrates the simplest use case: wrapping a bytes.Buffer with automatic cleanup on close.

package main

import (
	"bytes"
	"fmt"

	"github.com/nabbar/golib/ioutils/bufferReadCloser"
)

func main() {
	// Create a buffer with some data
	buf := bytes.NewBufferString("Hello, World!")

	// Wrap it with Close support
	wrapped := bufferReadCloser.NewBuffer(buf, nil)
	defer wrapped.Close() // Automatically resets buffer

	// Read the data
	data := make([]byte, 5)
	n, _ := wrapped.Read(data)
	fmt.Printf("Read %d bytes: %s\n", n, string(data))

}
Output:

Read 5 bytes: Hello
Example (BufferPool)

Example_bufferPool demonstrates integration with sync.Pool for efficient buffer reuse with automatic reset and return to pool.

package main

import (
	"bytes"
	"fmt"

	"github.com/nabbar/golib/ioutils/bufferReadCloser"
)

func main() {
	// Simulate a buffer pool (in real code, use sync.Pool)
	pool := &simplePool{buf: bytes.NewBuffer(make([]byte, 0, 1024))}

	// Get buffer from pool
	buf := pool.Get()

	// Wrap with custom close that returns to pool
	wrapped := bufferReadCloser.NewBuffer(buf, func() error {
		pool.Put(buf)
		return nil
	})

	// Use buffer
	wrapped.WriteString("temporary data")

	// Close resets and returns to pool
	wrapped.Close()

	// Verify buffer was reset
	fmt.Printf("Buffer length after close: %d\n", buf.Len())

}

// simplePool is a simplified pool for demonstration
type simplePool struct {
	buf *bytes.Buffer
}

func (p *simplePool) Get() *bytes.Buffer {
	return p.buf
}

func (p *simplePool) Put(b *bytes.Buffer) {

}
Output:

Buffer length after close: 0
Example (ChainingCleanup)

Example_chainingCleanup shows how to chain multiple cleanup operations using custom close functions.

package main

import (
	"bytes"
	"fmt"

	"github.com/nabbar/golib/ioutils/bufferReadCloser"
)

func main() {
	var cleanupOrder []string

	// Create nested cleanup chain
	buf := bytes.NewBuffer(nil)
	wrapped := bufferReadCloser.NewBuffer(buf, func() error {
		cleanupOrder = append(cleanupOrder, "buffer cleanup")
		return nil
	})

	// Simulate additional resource
	resource := &mockResource{
		onClose: func() error {
			cleanupOrder = append(cleanupOrder, "resource cleanup")
			return nil
		},
	}

	// Use both
	wrapped.WriteString("data")

	// Close in order
	wrapped.Close()
	resource.Close()

	fmt.Printf("Cleanup order: %v\n", cleanupOrder)

}

// mockResource simulates an external resource with cleanup
type mockResource struct {
	onClose func() error
}

func (r *mockResource) Close() error {
	if r.onClose != nil {
		return r.onClose()
	}
	return nil
}
Output:

Cleanup order: [buffer cleanup resource cleanup]
Example (CustomClose)

Example_customClose demonstrates using a custom close function to perform additional cleanup beyond the default reset behavior.

package main

import (
	"bytes"
	"fmt"

	"github.com/nabbar/golib/ioutils/bufferReadCloser"
)

func main() {
	closed := false

	// Create buffer with custom close function
	buf := bytes.NewBuffer(nil)
	wrapped := bufferReadCloser.NewBuffer(buf, func() error {
		closed = true
		fmt.Println("Custom cleanup executed")
		return nil
	})

	wrapped.WriteString("data")
	wrapped.Close()

	fmt.Printf("Closed: %v\n", closed)

}
Output:

Custom cleanup executed
Closed: true
Example (ErrorPropagation)

Example_errorPropagation demonstrates how errors from custom close functions are properly propagated to the caller.

package main

import (
	"bytes"
	"fmt"

	"github.com/nabbar/golib/ioutils/bufferReadCloser"
)

func main() {
	// Create buffer with failing close function
	buf := bytes.NewBuffer(nil)
	wrapped := bufferReadCloser.NewBuffer(buf, func() error {
		return fmt.Errorf("cleanup failed")
	})

	wrapped.WriteString("data")

	// Error is returned from Close()
	err := wrapped.Close()
	if err != nil {
		fmt.Println("Error:", err)
	}

}
Output:

Error: cleanup failed
Example (FileProcessing)

Example_fileProcessing shows a realistic use case: reading a file with automatic cleanup of both the buffer and file handle.

package main

import (
	"bufio"
	"fmt"
	"io"
	"strings"

	"github.com/nabbar/golib/ioutils/bufferReadCloser"
)

func main() {
	// Simulate file content
	fileContent := strings.NewReader("File content here")

	// In real code, this would be: file, _ := os.Open("data.txt")
	// We simulate with a ReadCloser
	file := io.NopCloser(fileContent)

	// Create buffered reader with file close chained
	br := bufio.NewReader(file)
	reader := bufferReadCloser.NewReader(br, file.Close)
	defer reader.Close() // Closes both reader and file

	// Read data
	data := make([]byte, 12)
	n, _ := reader.Read(data)
	fmt.Printf("Read: %s\n", string(data[:n]))

}
Output:

Read: File content
Example (NilHandling)

Example_nilHandling shows how the package handles nil parameters gracefully by providing sensible defaults instead of panicking.

package main

import (
	"fmt"

	"github.com/nabbar/golib/ioutils/bufferReadCloser"
)

func main() {
	// Passing nil creates an empty buffer
	buf := bufferReadCloser.NewBuffer(nil, nil)
	defer buf.Close()

	// Can write to it normally
	buf.WriteString("test")

	// And read back
	data := make([]byte, 4)
	buf.Read(data)
	fmt.Println(string(data))

}
Output:

test
Example (ReadWriter)

Example_readWriter demonstrates bidirectional buffered I/O with automatic flush on close. Useful for network protocols or duplex communication.

package main

import (
	"bufio"
	"bytes"
	"fmt"

	"github.com/nabbar/golib/ioutils/bufferReadCloser"
)

func main() {
	// Create separate buffers for reading and writing to simulate duplex I/O
	readBuf := bytes.NewBufferString("Initial data")
	writeBuf := &bytes.Buffer{}

	// Create ReadWriter with separate read/write buffers
	rw := bufio.NewReadWriter(bufio.NewReader(readBuf), bufio.NewWriter(writeBuf))
	wrapped := bufferReadCloser.NewReadWriter(rw, nil)
	defer wrapped.Close() // Flushes writes

	// Read from input
	data := make([]byte, 7)
	wrapped.Read(data)
	fmt.Printf("Read: %s\n", string(data))

	// Write to output (buffered)
	wrapped.WriteString("Response data")
	wrapped.Close() // Flush to output buffer

	fmt.Printf("Written: %s\n", writeBuf.String())

}
Output:

Read: Initial
Written: Response data
Example (Reader)

Example_reader shows buffered reading with automatic resource cleanup. This is useful when reading from files or network connections.

package main

import (
	"bufio"
	"fmt"
	"strings"

	"github.com/nabbar/golib/ioutils/bufferReadCloser"
)

func main() {
	// Simulate a data source
	source := strings.NewReader("Line 1\nLine 2\nLine 3")
	br := bufio.NewReader(source)

	// Wrap with close support
	reader := bufferReadCloser.NewReader(br, nil)
	defer reader.Close() // Automatically resets reader

	// Read some data
	data := make([]byte, 6)
	n, _ := reader.Read(data)
	fmt.Printf("Read: %s\n", string(data[:n]))

}
Output:

Read: Line 1
Example (ReaderEOF)

Example_readerEOF shows how a nil reader creates a reader that immediately returns EOF, useful for testing or empty data scenarios.

package main

import (
	"fmt"
	"io"

	"github.com/nabbar/golib/ioutils/bufferReadCloser"
)

func main() {
	// Passing nil creates reader from empty source
	reader := bufferReadCloser.NewReader(nil, nil)
	defer reader.Close()

	// Read immediately returns EOF
	data := make([]byte, 10)
	n, err := reader.Read(data)
	fmt.Printf("Read %d bytes, EOF: %v\n", n, err == io.EOF)

}
Output:

Read 0 bytes, EOF: true
Example (Writer)

Example_writer demonstrates buffered writing with automatic flush on close. This ensures all buffered data is written before cleanup.

package main

import (
	"bufio"
	"bytes"
	"fmt"

	"github.com/nabbar/golib/ioutils/bufferReadCloser"
)

func main() {
	// Create destination buffer
	dest := &bytes.Buffer{}
	bw := bufio.NewWriter(dest)

	// Wrap with close support
	writer := bufferReadCloser.NewWriter(bw, nil)

	// Write data (buffered, not yet visible in dest)
	writer.WriteString("Hello")
	writer.WriteString(" ")
	writer.WriteString("World")

	// Close flushes automatically
	writer.Close()

	fmt.Println(dest.String())

}
Output:

Hello World
Example (WriterFlushError)

Example_writerFlushError shows how flush errors are properly returned from the Close() method, allowing proper error handling.

package main

import (
	"bufio"
	"fmt"

	"github.com/nabbar/golib/ioutils/bufferReadCloser"
)

func main() {
	// Create a writer that will fail on flush
	failWriter := &failingWriter{}
	bw := bufio.NewWriter(failWriter)

	writer := bufferReadCloser.NewWriter(bw, nil)
	writer.WriteString("data")

	// Close returns the flush error
	err := writer.Close()
	if err != nil {
		fmt.Println("Flush error:", err)
	}

}

// failingWriter is a helper type for demonstrating error handling
type failingWriter struct{}

func (w *failingWriter) Write(p []byte) (n int, err error) {
	return 0, fmt.Errorf("write failed")
}
Output:

Flush error: write failed
Example (WriterToDiscard)

Example_writerToDiscard shows how a nil writer creates a writer to io.Discard, useful for testing or when output needs to be silently discarded.

package main

import (
	"fmt"

	"github.com/nabbar/golib/ioutils/bufferReadCloser"
)

func main() {
	// Passing nil creates writer to io.Discard
	writer := bufferReadCloser.NewWriter(nil, nil)
	defer writer.Close()

	// Writes succeed but data is discarded
	n, err := writer.WriteString("discarded data")
	fmt.Printf("Wrote %d bytes, error: %v\n", n, err)

}
Output:

Wrote 14 bytes, error: <nil>

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Buffer

type Buffer interface {
	io.Reader       // Read reads data from the buffer
	io.ReaderFrom   // ReadFrom reads data from a reader into the buffer
	io.ByteReader   // ReadByte reads a single byte
	io.RuneReader   // ReadRune reads a single UTF-8 encoded rune
	io.Writer       // Write writes data to the buffer
	io.WriterTo     // WriteTo writes buffer data to a writer
	io.ByteWriter   // WriteByte writes a single byte
	io.StringWriter // WriteString writes a string
	io.Closer       // Close resets the buffer and calls custom close function
}

Buffer is a wrapper around bytes.Buffer that implements io.Closer. It provides all the standard buffer interfaces with automatic reset on close.

The Buffer interface combines reading and writing capabilities with lifecycle management. When Close() is called, the underlying buffer is reset (all data cleared) and any custom close function is executed.

All I/O operations are delegated directly to the underlying bytes.Buffer with zero overhead. The wrapper only adds the Close() method for lifecycle management.

Thread safety: Not thread-safe. Concurrent access requires external synchronization.

func New deprecated

func New(b *bytes.Buffer) Buffer

New creates a new Buffer from a bytes.Buffer without a custom close function.

Deprecated: use NewBuffer instead of New. This function is maintained for backward compatibility but NewBuffer provides more flexibility with the optional FuncClose parameter.

func NewBuffer added in v1.17.2

func NewBuffer(b *bytes.Buffer, fct FuncClose) Buffer

NewBuffer creates a new Buffer from a bytes.Buffer and an optional FuncClose.

Parameters:

  • b: The underlying bytes.Buffer to wrap. If nil, a new empty buffer is created.
  • fct: Optional custom close function. If not nil, called after buffer reset.

The returned Buffer delegates all I/O operations to the underlying bytes.Buffer. On Close(), the buffer is reset (cleared) and then fct is called if provided.

Nil handling: Passing nil for b creates a new empty buffer, allowing immediate use without additional initialization. This is useful for testing or when a buffer is conditionally needed.

Example:

buf := NewBuffer(bytes.NewBuffer(nil), nil)
defer buf.Close()
buf.WriteString("data")

type FuncClose added in v1.17.2

type FuncClose func() error

FuncClose is an optional custom close function that is called when a wrapper is closed. It allows for additional cleanup logic beyond the default reset behavior.

The function is called after the wrapper's internal cleanup (flush, reset) but before returning from Close(). Any error returned by FuncClose is propagated to the caller.

Common use cases:

  • Closing underlying file handles or network connections
  • Returning buffers to sync.Pool
  • Updating metrics or logging
  • Releasing external resources

Example:

file, _ := os.Open("data.txt")
reader := NewReader(bufio.NewReader(file), file.Close)
defer reader.Close() // Closes both reader and file

type ReadWriter added in v1.17.2

type ReadWriter interface {
	Reader // Provides Read and WriteTo operations
	Writer // Provides Write, WriteString, and ReadFrom operations
}

ReadWriter is a wrapper around bufio.ReadWriter that implements io.Closer. It combines Reader and Writer interfaces with automatic flush on close.

The ReadWriter interface provides bidirectional buffered I/O with lifecycle management. When Close() is called, buffered write data is flushed and any custom close function is executed.

Limitation: Unlike Reader and Writer, ReadWriter cannot call Reset() on close because bufio.ReadWriter embeds both *Reader and *Writer, each with their own Reset() method, creating an ambiguous method call. This means the underlying readers/writers are not reset on close, only flushed.

Typical use case: Network protocols or duplex communication channels where both reading and writing are needed with guaranteed flush on close.

Thread safety: Not thread-safe. Concurrent access requires external synchronization.

func NewReadWriter added in v1.17.2

func NewReadWriter(b *bufio.ReadWriter, fct FuncClose) ReadWriter

NewReadWriter creates a new ReadWriter from a bufio.ReadWriter and an optional FuncClose.

Parameters:

  • b: The underlying bufio.ReadWriter to wrap. If nil, creates a readwriter with empty source (reads return EOF) and io.Discard destination (writes are discarded).
  • fct: Optional custom close function. If not nil, called after flush.

The returned ReadWriter delegates all I/O operations to the underlying bufio.ReadWriter. On Close(), buffered write data is flushed (errors returned) and then fct is called if provided.

Limitation: Reset() is NOT called on close due to ambiguous methods in bufio.ReadWriter. This means the underlying readers/writers retain their state after close.

Nil handling: Passing nil for b creates a readwriter where reads immediately return io.EOF and writes are silently discarded. This is useful for testing or placeholder scenarios.

Common pattern for network connections:

conn, _ := net.Dial("tcp", "example.com:80")
rw := bufio.NewReadWriter(bufio.NewReader(conn), bufio.NewWriter(conn))
wrapper := NewReadWriter(rw, conn.Close)
defer wrapper.Close() // Flushes and closes connection

type Reader added in v1.17.2

type Reader interface {
	io.Reader   // Read reads data from the buffered reader
	io.WriterTo // WriteTo writes buffered data to a writer
	io.Closer   // Close resets the reader and calls custom close function
}

Reader is a wrapper around bufio.Reader that implements io.Closer. It provides read operations with automatic reset on close.

The Reader interface provides buffered reading with lifecycle management. When Close() is called, the underlying reader is reset (buffered data released) and any custom close function is executed.

Typical use case: Reading from files or network connections where you want to ensure both the buffer and the underlying resource are properly cleaned up.

Thread safety: Not thread-safe. Concurrent access requires external synchronization.

func NewReader added in v1.17.2

func NewReader(b *bufio.Reader, fct FuncClose) Reader

NewReader creates a new Reader from a bufio.Reader and an optional FuncClose.

Parameters:

  • b: The underlying bufio.Reader to wrap. If nil, creates a reader from an empty source.
  • fct: Optional custom close function. If not nil, called after reader reset.

The returned Reader delegates all read operations to the underlying bufio.Reader. On Close(), the reader is reset (buffered data released) and then fct is called if provided.

Nil handling: Passing nil for b creates a reader from an empty source that immediately returns io.EOF on any read operation. This is useful for testing or placeholder scenarios.

Common pattern for file reading:

file, _ := os.Open("data.txt")
reader := NewReader(bufio.NewReader(file), file.Close)
defer reader.Close() // Closes both reader and file

type Writer added in v1.17.2

type Writer interface {
	io.Writer       // Write writes data to the buffered writer
	io.StringWriter // WriteString writes a string to the buffered writer
	io.ReaderFrom   // ReadFrom reads from a reader and writes to the buffered writer
	io.Closer       // Close flushes, resets the writer, and calls custom close function
}

Writer is a wrapper around bufio.Writer that implements io.Closer. It provides write operations with automatic flush and reset on close.

The Writer interface provides buffered writing with guaranteed flush on close. When Close() is called, buffered data is flushed, the writer is reset, and any custom close function is executed.

Important: Data written to a Writer is buffered and may not be visible in the destination until Close() is called or the buffer is manually flushed.

Typical use case: Writing to files or network connections where you want to ensure all buffered data is written and resources are properly cleaned up.

Thread safety: Not thread-safe. Concurrent access requires external synchronization.

func NewWriter added in v1.17.2

func NewWriter(b *bufio.Writer, fct FuncClose) Writer

NewWriter creates a new Writer from a bufio.Writer and an optional FuncClose.

Parameters:

  • b: The underlying bufio.Writer to wrap. If nil, creates a writer to io.Discard.
  • fct: Optional custom close function. If not nil, called after flush and reset.

The returned Writer delegates all write operations to the underlying bufio.Writer. On Close(), buffered data is flushed (errors returned), the writer is reset, and then fct is called if provided.

Nil handling: Passing nil for b creates a writer to io.Discard that accepts all writes without error but discards the data. This is useful for testing or when output needs to be silently ignored.

Important: Close() now returns flush errors. Always check the error:

writer := NewWriter(bw, nil)
defer func() {
    if err := writer.Close(); err != nil {
        log.Printf("flush failed: %v", err)
    }
}()

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL