Skip to content

proposal: unicode/utf8: add LastPartialRuneLen #73149

Description

@rogpeppe

Proposal Details

There are some situations where it's desirable to split a string at some known size but avoid splitting UTF-8 sequences. Examples include truncating a string to a limited length when logging, and doing character-oriented manipulation within an io.Reader.

Currently the easiest way to do that is by traversing from the start of the string, decoding runes until the desired split point is reached, but this is unnecessarily inefficient.

Getting this right is surprisingly tricky.

I propose we add two new functions to unicode/utf8:

// LastPartialRuneLen returns the number of bytes at the
// end of p that might be the start of a valid UTF-8 byte
// sequence.
func LastPartialRuneLen(p []byte) int

// LastPartialRuneLenInString is like LastPartialRuneLen
// but for strings.
func LastPartialRuneLenInString(s string) int

Here's a possible implementation, very lightly tested as yet:

func LastPartialRune[T ~[]byte|~string](p T) int {
	end := len(p)
	if end == 0 {
		return 0
	}
	start := end - 1
	lim := max(0, end-utf8.UTFMax)
	for ; start >= lim; start-- {
		r := p[start]
		if r < utf8.RuneSelf {
			return 0
		}
		if r&0b1100_0000 == 0b1000_0000 {
			// continuation byte.
			continue
		}
		if r, size := utf8.DecodeRune(p[start:]); r != utf8.RuneError || size > 1 {
			return 0
		}
		return end - start
	}
	// It's all continuation characters up to here.
	// They can't _all_ be continuation characters:
	// the last one definitely isn't, so we can't consider
	// it a partial rune.
	return 0
}

It might be nice to add it as a generic function that works on []byte and string, but that would need a generic version of DecodeRune too.

Metadata

Metadata

Assignees

No one assigned

    Labels

    LibraryProposalIssues describing a requested change to the Go standard library or x/ libraries, but not to a toolProposal

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions