Every URI, however complex, decomposes into the same five components. Today you define the result type that holds all five and give the library its public Parse entry point, starting with the simplest input of all - a bare path with nothing else around it.
Define the URI struct with all five components and parse an input that is just a path.
RFC 3986 describes every URI with one generic syntax: scheme://authority/path?query#fragment. Not every URI has all five parts, but every URI is some subset of exactly these five components, always in this order. The parser’s whole job is to find the boundaries between them, so the natural shape for the result is a struct with a field per component.
A few of those components can be absent rather than merely empty, and that difference will matter later - a URI with no query is not the same as one with an empty query. So alongside the string fields you carry Has... booleans for the parts that can be present-but-empty. Define the entire struct now, even though most fields stay zero today; every later lesson fills in one more boundary. Start at the far end of the grammar: an input with no scheme, no //, no ?, and no # is nothing but a path, so Parse sets Path to the whole string and leaves the rest empty.
// the whole library grows around this one result typetype URI struct {Scheme stringAuthority stringHasAuthority boolUserinfo stringHasUserinfo boolHost, Port stringHasPort boolPath stringQuery stringHasQuery boolFragment stringHasFragment bool}func Parse(s string) *URI { return &URI{Path: s} }