build-a-protobuf-decoder / lesson-01.md
Lesson 01 · Varints, the core

A byte cursor

Every protobuf message is a flat run of bytes read strictly left to right, so the first thing we need is a cursor that hands out the next byte and remembers where it is. Today you build that reader, the spine the whole decoder threads through.

The goal

Build a reader over a byte slice that returns the next byte and advances its position.

Start here - the target
TO DO
Scenario: A reader walks a byte slice one byte at a time
Givena reader created over the bytes 0x08, 0x96, 0x01
WhenReadByte is called three times in a row
Thenit returns 0x08, then 0x96, then 0x01, and the position advances 0 to 1 to 2 to 3
AndAtEnd reports true once all three bytes have been read
Background

A protobuf message has no framing, no delimiters, and no length at the front - it is just a sequence of bytes that you interpret in order. Because of that, almost every operation in this project is “read the next byte (or few), decide what it means, advance.” A small cursor that owns the buffer and a position is the one abstraction every later lesson leans on.

Today is deliberately tiny: wrap a byte slice, hand back bytes one at a time, and keep track of how far you have read. The pos field is the whole point - it is what lets tomorrow’s varint reader consume a variable number of bytes and leave the cursor sitting exactly on the next field. Getting AtEnd right matters too: the top-level decoder will loop “until the reader is at the end” to know when a message is fully consumed.

Make it work
// the whole decoder reads through one of these
type Reader struct {
buf []byte
pos int
}
func NewReader(b []byte) *Reader { return &Reader{buf: b} }
func (r *Reader) ReadByte() byte { b := r.buf[r.pos]; r.pos++; return b }
func (r *Reader) AtEnd() bool { return r.pos >= len(r.buf) }
CheckpointDONE
You have a cursor that reads bytes in order and tracks its position. Commit and stop here.