'How to coerce _string-type_ to _fixed-length string-type_ in Go?

I have a simple program to open a text file with two space-separated elements per line (first name and last name). Then, I put first-name and last-name in to a struct (T), which goes in a slice of such structs ([ ]T).

There is a catch, first-name and last-name must be limited by 20 characters. When reading the text-file, I'm unable to coerce the text to be of that fixed-length. How can I coerce a general string into a "maximum-20char-string"?

package main

import (
    "bufio"
    "fmt"
    "os"
    "strings"
)

type T struct {
    fname [20]string
    lname [20]string
}

func main() {
    // Ask for file name (fn)
    fmt.Println("Enter file name, e.g.: 'names.txt'")
    var fn string
    _, _ = fmt.Scan(&fn)

    // Open file
    file, err := os.Open(fn)
    if err != nil {
        fmt.Println(err)
    }
    defer file.Close()

    // Create a slice, in which the T constructs will be populated.
    var slice []T

    // Counter for each line
    i := 0

    // Read each line and iterate on it
    scanner := bufio.NewScanner(file)
    for scanner.Scan() {
        fmt.Println(scanner.Text())
        strline := scanner.Text()
        // words := strings.Split(strline, " ")
        // var words [20]string
        words := strings.Fields(strline)
        // fmt.Println(words, len(words))
        slice[i] = T{fname: words[0], lname: words[1]}
        i = i + 1
    }

    if err := scanner.Err(); err != nil {
        fmt.Println(err)
    }

    // return slice with T-type elements
    fmt.Println(slice)
}

In the terminal,

go build read.go
# command-line-arguments
./read.go:43:23: cannot use words[0] (variable of type string) as type [20]string in struct literal
./read.go:43:40: cannot use words[1] (variable of type string) as type [20]string in struct literal


Solution 1:[1]

You are going to need to make various decisions:

  • Do you want 20 bytes, or 20 runes?
  • What do you want to do with shorter strings?
  • What do you want to do with longer strings?

Once you've made some of these decisions, it is time to write some Go code:

func to20bytes(s string) [20]byte {
    ... some code ...
}

or:

func to20runes(s string) ([20]rune, error) {
    ... some code ...
}

for instance.

Since an array of 20 bytes or runes always contains exactly 20 bytes or runes, these arrays cannot contain shorter "strings" (I put the word "strings" in quotes here as an array of bytes or runes is not a Go string). In ancient times, people often space-padded arrays of bytes, so that the first function might read:

func to20bytes(s string) (ret [20]byte) {
    for i, c := range s {
        if c > 127 {
            panic("non-ASCII-character")
        }
        ret[i] = byte(c) // panic if i >= 20
    }
    for i := len(s); i < 20; i++ {
        ret[i] = ' '
    }
    return
}

This function is terrible; see the playground example to see why. Having an error return, or truncating, is probably better, but only you can say; using runes is probably better, but only you can say.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 torek