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:
- Minimal Overhead: Thin wrappers with zero-copy passthrough to underlying buffers
- Lifecycle Management: Automatic reset and cleanup on close
- Flexibility: Optional custom close functions for additional cleanup logic
- Standard Compatibility: Implements all relevant io.* interfaces
- 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 NewBuffer ¶ added in v1.17.2
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
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
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)
}
}()