io.Reader & io.Writer
Part of the Object Oriented Programming section of Coddy's GO journey. Lesson 73 of 107.
Go's standard library showcases interface-based design through two fundamental interfaces: io.Reader and io.Writer. These simple interfaces power everything from file operations to network communication.
The io.Reader interface requires just one method:
type Reader interface {
Read(p []byte) (n int, err error)
}It reads up to len(p) bytes into the slice and returns how many bytes were read. Similarly, io.Writer defines:
type Writer interface {
Write(p []byte) (n int, err error)
}Any type implementing these methods works with the entire I/O ecosystem.
Here's a custom type that implements io.Reader:
type RepeatReader struct {
Char byte
Count int
pos int
}
func (r *RepeatReader) Read(p []byte) (int, error) {
if r.pos >= r.Count {
return 0, io.EOF
}
n := 0
for n < len(p) && r.pos < r.Count {
p[n] = r.Char
n++
r.pos++
}
return n, nil
}Now this custom reader works with any function expecting an io.Reader:
reader := &RepeatReader{Char: 'A', Count: 5}
data, _ := io.ReadAll(reader)
fmt.Println(string(data)) // AAAAAThis design lets you write functions that accept io.Reader or io.Writer, making them work with files, network connections, buffers, or any custom implementation.
Challenge
EasyLet's build a custom text stream processor that implements the io.Reader and io.Writer interfaces! You'll create types that can transform text as it flows through them, demonstrating how these fundamental interfaces enable powerful I/O composition.
You'll organize your code across two files:
streams.go: Define your custom reader and writer types.Create a
CountingReaderstruct that wraps a string and tracks how many bytes have been read. It should have fields for the source string and a position tracker. Implement theRead(p []byte) (int, error)method that reads bytes from the source into the provided slice, updates the position, and returnsio.EOFwhen the source is exhausted.Create a
UppercaseWriterstruct that collects written data and converts it to uppercase. It should store the accumulated result internally. Implement theWrite(p []byte) (int, error)method that converts incoming bytes to uppercase and stores them. Add aResult() stringmethod to retrieve the accumulated uppercase text.Create constructor functions
NewCountingReader(source string) *CountingReaderandNewUppercaseWriter() *UppercaseWriterto initialize your types properly.main.go: Demonstrate your custom I/O types working with the standard library.Read a string input, then use your
CountingReaderwithio.ReadAllto read all the data. Pass that data through yourUppercaseWriterusing itsWritemethod.Print the results in this format:
Original: [input] Uppercase: [result from writer] Bytes read: [total bytes]
The following input will be provided:
- A single line of text
For example, given:
Hello WorldYour output should be:
Original: Hello World
Uppercase: HELLO WORLD
Bytes read: 11And given:
Go interfaces are powerfulYour output should be:
Original: Go interfaces are powerful
Uppercase: GO INTERFACES ARE POWERFUL
Bytes read: 26The key insight is that your CountingReader works seamlessly with io.ReadAll because it implements io.Reader. Any function in Go's ecosystem that accepts an io.Reader will work with your custom type: that's the power of interface-based design.
Try it yourself
package main
import (
"bufio"
"fmt"
"io"
"os"
)
func main() {
// Read input
reader := bufio.NewReader(os.Stdin)
input, _ := reader.ReadString('\n')
// Remove trailing newline if present
if len(input) > 0 && input[len(input)-1] == '\n' {
input = input[:len(input)-1]
}
// TODO: Create a CountingReader with the input string
// countingReader := NewCountingReader(input)
// TODO: Use io.ReadAll to read all data from your CountingReader
// data, err := io.ReadAll(countingReader)
// TODO: Create an UppercaseWriter
// upperWriter := NewUppercaseWriter()
// TODO: Write the data to your UppercaseWriter
// upperWriter.Write(data)
// TODO: Print the results in the required format:
// Original: [input]
// Uppercase: [result from writer]
// Bytes read: [total bytes]
// Placeholder to avoid unused import error - remove when implementing
_ = io.EOF
fmt.Println("Original:", input)
// fmt.Println("Uppercase:", upperWriter.Result())
// fmt.Println("Bytes read:", countingReader.Position)
}
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Fundamentals of Go OOP
External FilesGo Workspace & ModulesPackages & ImportsExported vs Unexported NamesIntroduction to OOP in GoStructs as ClassesDefining Methods on StructsPointer vs Value ReceiversStruct InitializationConstructor FunctionsRecap - Simple Calculator4Interfaces
Introduction to InterfacesImplicit ImplementationInterface as ContractEmpty Interface (any)Type AssertionType SwitchInterface CompositionStringer & Error InterfacesRecap - Shape Calculator7Encapsulation
Exported vs Unexported FieldsPackage-Level EncapsulationGetter & Setter MethodsInformation Hiding in GoRecap - Student Records10Generics (Go 1.18+)
Introduction to GenericsType ParametersType ConstraintsGeneric StructsGeneric Methods WorkaroundRecap - Generic Collection2Types & Structs Deep Dive
Basic & Composite TypesCustom Type DefinitionsStruct TagsAnonymous StructsNested StructsZero Values & DefaultsRecap - Contact Book5Composition Over Inheritance
Why Go Has No InheritanceStruct Embedding BasicsMethod PromotionEmbedding Multiple StructsEmbedding vs AggregationShadowing Embedded MethodsRecap - Employee Hierarchy8Error Handling & OOP
The error InterfaceCustom Error TypesError Wrapping (fmt.Errorf)Sentinel Errorserrors.Is() and errors.As()Panic, Defer, and RecoverRecap - File Parser11Standard Library & OOP
io.Reader & io.Writersort.Interfacefmt.Stringer Interfaceencoding/json with Structshttp.Handler InterfaceRecap - REST API Models3Pointers & Memory
Pointer Basics in GoPointers to StructsPass by Value vs ReferenceThe new() FunctionGarbage Collection in GoRecap - Linked List Builder6Polymorphism in Go
Polymorphism via InterfacesDuck Typing in GoInterface Satisfaction RulesPolymorphic CollectionsDependency InjectionRecap - Payment Processor9Concurrency & OOP
Goroutines BasicsChannels & CommunicationBuffered vs Unbuffered ChanSelect Statementsync.Mutex & sync.RWMutexsync.WaitGroupThread-Safe Struct DesignRecap - Worker PoolPractice on your own: Online Go compiler