Here is my implementation of the exercise using strings.Map (the rot13 function is straight out of golang's docs). The issue is that the buffer does not seem to be modified after the Read function returns. Here is the code:
package main
import (
"io"
"os"
"strings"
"fmt"
)
type rot13Reader struct {
r io.Reader
}
func (reader *rot13Reader) Read(b []byte) (int, error) {
rot13 := func(r rune) rune {
switch {
case r >= 'A' && r <= 'Z':
return 'A' + (r-'A'+13)%26
case r >= 'a' && r <= 'z':
return 'a' + (r-'a'+13)%26
}
return r
}
n, err := reader.r.Read(b)
result := []byte(strings.Map(rot13, string(b)))
b = []byte(result)
fmt.Println(string(b))
return n, err
}
func main() {
s := strings.NewReader("Lbh penpxrq gur pbqr!")
r := rot13Reader{s}
io.Copy(os.Stdout, &r)
}
and the output:
You cracked the code!
Lbh penpxrq gur pbqr!You cracked the code!
Clearly the buffer has been modified in the Read function, but it does not seem to be the case after it returns. If I were to comment out the fmt.Println(string(b))
, the output would just be:
Lbh penpxrq gur pbqr!
Is there something quirky about Readers and Writers that I should know about?
In Go, all arguments are passed by value, as if by assignment to the parameter or receiver (a shallow copy).
In Go, a slice is implemented as
type slice struct {
array unsafe.Pointer
len int
cap int
}
When the slice is passed by value, after you return, you will not see any changes you make to the copy of the struct
fields. You will only see any changes to elements of the underlying array.
In your case, you overwrite b
(array
, cap
, len
), a copy.
b = []byte(result)
The copy is is discarded when you return.
What you want to do is change elements of b
's array
.
For example,
package main
import (
"io"
"os"
"strings"
)
func rot13(b byte) byte {
switch {
case b >= 'A' && b <= 'Z':
return 'A' + (b-'A'+13)%26
case b >= 'a' && b <= 'z':
return 'a' + (b-'a'+13)%26
}
return b
}
type rot13Reader struct {
r io.Reader
}
func (reader *rot13Reader) Read(b []byte) (int, error) {
n, err := reader.r.Read(b)
b = b[:n]
for i := range b {
b[i] = rot13(b[i])
}
return n, err
}
func main() {
s := strings.NewReader("Lbh penpxrq gur pbqr!")
r := rot13Reader{s}
io.Copy(os.Stdout, &r)
}
Playground: https://play.golang.org/p/0LDYmzrrgty
Output:
You cracked the code!
I am not too sure, so please take the below with between a few grains to several pounds of salt.
First you should add an error check as early as possible:
n, err := reader.r.Read(b)
if err != nil && err == io.EOF {
fmt.Printf("
%s, %d bytes read", err, n)
return n, err
}
With this added, the output is the one you would expect:
You cracked the code!
Lbh penpxrq gur pbqr!
EOF, 0 bytes read
The reason here is that a reader is supposed to return io.EOF in case there is nothing to read any more.
So why did you experience said strange behavior? A quick look in the source code of io.Copy
reveals that b
is allocated once and reused. But since b
was not modified (no bytes were read) and you accessed it for reading from it, it still held the same values as before. I would argue that the underlying io.Reader
should clear b
in case nothing is read, as per principle of least surprise, though.