unknwon/the-way-to-go_ZH_CN · error

pkg: %v

Error message

pkg: %v

What it means

Safety net inside Parse in section 13.4: a deferred recover() catches any panic from fields2numbers; if the recovered value already implements error it is used as-is, otherwise it is wrapped as fmt.Errorf("pkg: %v", r) so Parse always returns an error-typed value. This message therefore means: a helper panicked with a non-error value — in this listing the string panic("no words to parse") on empty input. Note that as printed ParseError implements String() (older idiom), not Error(), so even typed panics take the "pkg: %v" wrap.

Source

Thrown at eBook/13.4.md:46

type ParseError struct {
    Index int      // The index into the space-separated list of words.
    Word  string   // The word that generated the parse error.
    Err error // The raw error that precipitated this error, if any.
}

// String returns a human-readable error message.
func (e *ParseError) String() string {
    return fmt.Sprintf("pkg parse: error parsing %q as int", e.Word)
}

// Parse parses the space-separated words in in put as integers.
func Parse(input string) (numbers []int, err error) {
    defer func() {
        if r := recover(); r != nil {
            var ok bool
            err, ok = r.(error)
            if !ok {
                err = fmt.Errorf("pkg: %v", r)
            }
        }
    }()

    fields := strings.Fields(input)
    numbers = fields2numbers(fields)
    return
}

func fields2numbers(fields []string) (numbers []int) {
    if len(fields) == 0 {
        panic("no words to parse")
    }
    for idx, field := range fields {
        num, err := strconv.Atoi(field)
        if err != nil {
            panic(&ParseError{idx, field, err})
        }

View on GitHub (pinned to 7a54d34d36)

Solutions

  1. Handle empty input at the source without panicking: if len(fields) == 0 { return nil, errors.New("pkg: no words to parse") } — keep panic/recover for truly exceptional paths
  2. Panic with error values, not strings: panic(fmt.Errorf(...)) or panic(&ParseError{...}) — and give ParseError an Error() string method so the r.(error) branch preserves its type
  3. On the caller side, unwrap to classify: var pe *ParseError; if errors.As(err, &pe) { use pe.Word } — the 'pkg: %v' text only carries a string
  4. Preserve cause when wrapping: fmt.Errorf("pkg: %w", e) in the error branch so errors.Is/As chain through the wrapper

Example fix

// before
panic("no words to parse") // recovered → err = "pkg: no words to parse" (untyped string)

// after
if len(fields) == 0 {
	return nil, errors.New("pkg: no words to parse")
}
// and panic only with typed errors:
panic(&ParseError{Index: idx, Word: s, Err: err})
Defensive patterns

Strategy: try-catch

Validate before calling

fields := strings.Fields(input)
if len(fields) == 0 {
	return nil, errors.New("pkg: no words to parse") // pre-check: skip the panic path
}
return Parse(input)

Try / catch

defer func() {
	if r := recover(); r != nil {
		if e, ok := r.(error); ok {
			err = e
		} else {
			err = fmt.Errorf("pkg: %v", r)
		}
	}
}()

Prevention

When it happens

Trigger: Parse("") → fields is empty → fields2numbers panics "no words to parse" (a string) → the r.(error) assertion fails → err becomes 'pkg: no words to parse'. Likewise any panic carrying a non-error value (int, plain string) from deeper helpers; Parse("1 2 three") panics with a *ParseError which is stringified through the same wrap in this version.

Common situations: Converting panic-style internal code to error-returning APIs at a package boundary; batch processors where one bad record must not kill the run; maintainers panicking with strings; debugging why the returned error is an opaque 'pkg: ...' wrapper instead of a typed value that carries Word/Index fields.

Related errors


AI-assisted analysis of unknwon/the-way-to-go_ZH_CN@7a54d34d36 (2026-08-15). Data as JSON: /api/errors/a19b8fa100912a18. Report an issue: GitHub.