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?
Answer
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.
No comments:
Post a Comment