-
Notifications
You must be signed in to change notification settings - Fork 18.4k
Description
Inspired by #40135, I have another proposal for an addition to the strings package (and by extension, I suppose, the bytes package).
The strings functions I most frequently wish for are variants of TrimPrefix and TrimSuffix which report whether they did any trimming.
// TrimmedPrefix returns s without the provided leading prefix string
// and reports whether it found the prefix.
// If s doesn't start with prefix, TrimmedPrefix returns s, false.
// If prefix is the empty string, TrimmedPrefix returns s, true.
func TrimmedPrefix(s, prefix string) (trimmed string, ok bool)
Lacking these functions, Go authors resort to fragile code, often writing a prefix/suffix literal twice or hard-coding its length:
if strings.HasPrefix(s, "myprefix") {
s = strings.TrimPrefix(s, "myprefix")
...
}
if strings.HasPrefix(s, "myprefix") {
s = s[len("myprefix"):]
...
}
if strings.HasPrefix(s, "myprefix") {
s = s[8:]
...
}
if t := strings.TrimPrefix(s, "myprefix"); s != t {
// had prefix
s = t
...
}
Of course, a function like TrimmedPrefix is easy to write and can exist outside of the standard library. At my company we have these functions in an internal string helper package and they see regular use. But certainly I wouldn't pull in a dependency for such a tiny helper function and so when I'm working on my own projects I generally just use the above workarounds.
Here are some examples from the Go source tree along with how they could be altered to use TrimmedPrefix/TrimmedSuffix:
-
src/cmd/go/internal/modload/load.go:if strings.HasPrefix(suffix, "/vendor/") { return strings.TrimPrefix(suffix, "/vendor/") }becomes
if v, ok := strings.TrimmedPrefix(suffix, "/vendor/"); ok { return v } -
src/cmd/go/proxy_test.go:if !strings.HasSuffix(name, ".txt") { continue } name = strings.TrimSuffix(name, ".txt")becomes
name, ok := strings.TrimmedSuffix(name, ".txt") if !ok { continue } -
src/testing/benchmark.go:if strings.HasSuffix(s, "x") { n, err := strconv.ParseInt(s[:len(s)-1], 10, 0) ... }becomes
if s, ok := strings.TrimmedSuffix(s, "x"); ok { n, err := strconv.ParseInt(s, 10, 0) ... } -
src/testing/fstest/mapfs.go:if strings.HasPrefix(fname, prefix) { felem := fname[len(prefix):] ... }becomes
if felem, ok := strings.TrimmedPrefix(fname, prefix); ok { ... } -
src/mime/mediatype.go:if !strings.HasPrefix(rest, ";") { return "", "", v } rest = rest[1:] // consume semicolonbecomes
rest, ok := strings.TrimmedPrefix(rest, ";") if !ok { return "", "", v } -
test/run.go:if strings.HasPrefix(line, "//") { line = line[2:] } else { continue }becomes
line, ok := strings.TrimmedPrefix(line, "//") if !ok { continue } -
test/run.go:if strings.HasPrefix(m, "LINE+") { delta, _ := strconv.Atoi(m[5:]) n += delta ... }becomes
if d, ok := strings.TrimmedPrefix(m, "LINE+"); ok { delta, _ := strconv.Atoi(d) n += delta ... } -
src/runtime/testdata/testprog/traceback_ancestors.go:if strings.HasPrefix(tb[pos:], "goroutine ") { id := tb[pos+len("goroutine "):] ... }becomes
if id, ok := strings.TrimmedPrefix(tb[pos:], "goroutine "); ok { ... }
Update 2022-03-26: Changed proposed names to TrimmedPrefix/TrimmedSuffix (per @ianlancetaylor's suggestion).