DEV Community

Takahiro Kudo
Takahiro Kudo

Posted on

Go - Safe truncate string

#go
package main

import (
    "fmt"
)

// Truncate string.
func TruncateString(str string, length int) string {
    if length <= 0 {
        return ""
    }

    // This code cannot support Japanese
    // orgLen := len(str)
    // if orgLen <= length {
    //     return str
    // }
    // return str[:length]

    // Support Japanese
    // Ref: Range loops https://blog.golang.org/strings
    truncated := ""
    count := 0
    for _, char := range str {
    truncated += string(char)
        count++
        if count >= length {
            break
        }
    }
    return truncated
}

// Main
func main() {
    dataList := [][]interface{} {
        {"drink", 3, "dri"},
        {"drink", 6, "drink"},
        {"drink", 0, ""},
        {"drink", -1, ""},
        {"drink", 100, "drink"},
        {"pub", 100, "pub"},
        {"こんにちは", 3, "こんに"},
    }
    for _, dl := range dataList {
        r := TruncateString(dl[0].(string), dl[1].(int))
        if r != dl[2].(string) {
            fmt.Printf("ERROR: got=%s, want=%s", r, dl[2].(string))
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

https://play.golang.org/p/QHFLlXr8v8i
https://play.golang.org/p/iakC8xxTlFI

Reference
https://play.golang.org/p/EzvhWMljku

The below code raises an error of slice.

package main

import (
    "fmt"
)

func main() {
    s := "drink"
    r := s[:20] // error
    fmt.Println(r)
}
Enter fullscreen mode Exit fullscreen mode

https://play.golang.org/p/7IfclYXADoW

Discussion (6)

Collapse
huahuayu profile image
Shiming

if you test your code, there's a bug, your will get length + 1 substring not length

I modified it to

func truncate(str string, length int) (truncated string) {
    if length <= 0 {
        return
    }
    for i, char := range str {
        if i >= length {
            break
        }
        truncated += string(char)
    }
    return
}
Enter fullscreen mode Exit fullscreen mode
Collapse
takakd profile image
Takahiro Kudo Author

Thank you for your feedback. I cannot notice my mistake. Could you give me any test cases?

Collapse
jgreen01su profile image
jgreen01su

Hi Takahiro. Shiming isn't exactly right. I don't see any bugs with your code vs his. But he's still half right, I'll explain why.

    ...
    count := 0
    for _, char := range str {
    truncated += string(char)
        count++  // <-- shiming is talking about this
        if count >= length {
            break
        }
        /* Someone may mistakenly put code here that uses
         * count forgetting that count was incremented for
         * the next loop iteration. */
    }
    ...
Enter fullscreen mode Exit fullscreen mode

While your code works, in general it's not good practice to increment your counter before the end of the loop. It would be very easy for someone to make a mistake modifying this code.

Better would be this:

    ...
    count := 0
    for _, char := range str {
        if count >= length {
            break
        }
        truncated += string(char)
        count++
    }
    ...
Enter fullscreen mode Exit fullscreen mode

But best is exactly the code @huahuayu wrote because he's using the built-in for loop counter. He doesn't even need to worry about initializing and incrementing the counter because the for loop does all the hard work for him.

In general, the less code the better.

Thread Thread
takakd profile image
Takahiro Kudo Author

Thank you for your explaining. I have made sense. 😆

Collapse
derfenix profile image
Sergey Kostyuchenko
func TruncateString(str string, length int) string {
    if length <= 0 {
        return ""
    }

    if utf8.RuneCountInString(str) < length {
        return str
    }

    return string([]rune(str)[:length])
}
Enter fullscreen mode Exit fullscreen mode
Collapse
takakd profile image
Takahiro Kudo Author

Thanks! I didn't know utf8.RuneCountInString method. I have to learn more, haha😅