3

I was watching this talk given at FOSDEM '17 about implementing "tail -f" in Go => https://youtu.be/lLDWF59aZAo

In the author's initial example program, he creates a Reader using a file handle, and then uses the ReadString method with delimiter '\n' to read the file line by line and print its contents. I usually use Scanner, so this was new to me.

Program below | Go Playground Link

package main

import (
    "bufio"
    "fmt"
    "log"
    "os"
)

func main() {
    fileHandle, err := os.Open("someFile.log")
    if err != nil {
        log.Fatalln(err)
        return
    }
    defer fileHandle.Close()

    reader := bufio.NewReader(fileHandle)

    for {
        line, err := reader.ReadString('\n')
        if err != nil {
            log.Fatalln(err)
            break
        }
        fmt.Print(line)
    }

}

Now, ReadString takes a byte as its delimiter argument[https://golang.org/pkg/bufio/#Reader.ReadString]

So my question is, how in the world did '\n', which is a rune, get converted into a byte? I am not able to get my head around this. Especially since byte is an alias for uint8, and rune is an alias for int32.

I asked the same question in Gophers slack, and was told that '\n' is not a rune, but an untyped constant. If we actually created a rune using '\n' and passed it in, the compilation would fail. This actually confused me a bit more.

I was also given a link to a section of the Go spec regarding Type Identity => https://golang.org/ref/spec#Type_identity

If the program is not supposed to compile if it were an actual rune, why does the compiler allow an untyped constant to go through? Isn't this unsafe behaviour?

My guess is that this works due to a rule in the Assignability section in the Go spec, which says

x is an untyped constant representable by a value of type T.

Since '\n' can indeed be assigned to a variable of type byte, it is therefore converted.

Is my reasoning correct?

kkaosninja
  • 1,301
  • 11
  • 21
  • Be sure to check the official blog post regarding constants: https://blog.golang.org/constants – Agis Mar 12 '17 at 08:46

2 Answers2

5

TL;DR Yes you are correct but there's something more.

'\n' is an untyped rune constant. It doesn't have a type but a default type which is int32 (rune is an alias for int32). It holds a single byte representing the literal "\n", which is the numeric value 10:

package main

import (
    "fmt"
)

func main() {
    fmt.Printf("%T %v %c\n", '\n', '\n', '\n') // int32 10 (newline)
}

https://play.golang.org/p/lMjrTFDZUM

The part of the spec that answers your question lies in the § Calls (emphasis mine):

Given an expression f of function type F,

f(a1, a2, … an)

calls f with arguments a1, a2, … an. Except for one special case, arguments must be single-valued expressions assignable to the parameter types of F and are evaluated before the function is called.

"assignable" is the key term here and the part of the spec you quoted explains what it means. As you correctly guessed, among the various rules of assignability, the one that applies here is the following:

x is an untyped constant representable by a value of type T.

In our case this translates to:

'\n' is an untyped (rune) constant representable by a value of type byte

The fact that '\n' is actually converted to a byte when calling ReadString() is more apparent if we try passing an untyped rune constant wider than 1 byte, to a function that expects a byte:

package main

func main() {
    foo('α')
}

func foo(b byte) {}

https://play.golang.org/p/W0EUZppWHH

The code above fails with:

tmp/sandbox120896917/main.go:9: constant 945 overflows byte

That's because 'α' is actually 2 bytes, which means it cannot be converted to a value of type byte (the maximum integer a byte can hold is 255 while 'α' is actually 945).

All this is explained in the official blog post, Constants.

Agis
  • 32,639
  • 3
  • 73
  • 81
2

Yes, your reading is correct. Spec: Assignability section applies here as the value you want to pass must be assignable to the type of the parameter.

When you pass the value '\n', that is an untyped constant specified by a rune literal. It represents a number equal to the Unicode code of the '\n' character (which is 10 by the way). The rule you quoted applies here:

x is an untyped constant representable by a value of type T.

Constants have a default type, which will be used when a type is "missing" from the context where the value is used. Such an example is the short variable declaration:

r := '\n'
fmt.Printf("%T", r)

The default type of a rune literal is that: rune. The above code prints int32 because the rune type is an alias for int32 (they are "identical", interchangable). Try it on the Go Playground.

Now if you try to pass the variable r to a function which expects a value of type byte, it is a compile time error, because this case matches none of the assignability rules. You need explicit type conversion to make such a case work:

r := '\n'
line, err := reader.ReadString(byte(r))

See related blog posts and questions:

Spec: Constants

The Go Blog: Constants

Defining a variable in Go programming language

Custom type passed to function as a parameter

Why do these two float64s have different values?

Does go compiler's evaluation differ for constant expression and other expression

Community
  • 1
  • 1
icza
  • 389,944
  • 63
  • 907
  • 827