immutable(?) strings in go
“ever wonder if you can modify "immutable" strings?”
Take the following bit of code.
What we've been told is that fooMessage, by being assigned, now points to an immutable structure.
But what does that mean?
Well, when we try to mutate it, we first get blocked by the compiler.
Can't let that deter us though, we know that go is "c-like", so let's take a look at some c?
Success!
Let's port this over, but first we're going to need to take a little detour through go's unsafe package to get to the underlying pointer and not let the compiler stop us.
Putting it all together:
Oh, dear. Not quite a success, but why? Well, to explain, let's take another look at our c code. The astute might have already noticed that it's not actually a straight port.
The c code uses an array of char, where the go code uses a string literal; what happens when we resolve the discrepancy?
Hey! That's the same message!
But why?
We can see a hint if we change what we're taking the address of from something the compiler knows at compile time, to something that is built up at runtime:
Notice the shorter address on the first block. The string is actually stored in a different section of memory. That explains the SIGBUS we saw before, it has something to do with the memory that we dereferenced.
There's something else that's kinda neat here, when run over and over again there's a noticeable pattern:
Let's take a peek inside that binary and see what's sitting around 2a8...
Sure enough, it's our string!
What's going on here is that the memory is being mapped into a section of the binary where the data is marked in a read-only boundary:
When the application attempts to modify that memory at runtime, the hardware reports back that something illegal is happening and kills the application.
Which makes the obvious question come up... what happens when we try to modify that memory when it's been copied out of the boundary?
Not so immutable after all 🎻
func main() {
fooMessage := "Hello, world!"
fmt.Printf("How immutable are you, '%s'?\n", fooMessage)
// How immutable are you, 'Hello, world!'?
}func main() {
fooMessage := "Hello, world!"
fooMessage[0] = 'a' // cannot assign to fooMessage[0] (neither addressable nor a map index expression)
fmt.Printf("How immutable are you, '%s'?\n", fooMessage)
}#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char** args)
{
char fooMessage[14];
strncpy(fooMessage, "Hello, world!", sizeof(fooMessage));
fooMessage[0] = 'C';
printf("How immutable are you, '%s'?\n", fooMessage);
// How immutable are you, 'Cello, world!'?
return 0;
} p := unsafe.StringData("Hello, world!")
fmt.Printf("%p\n", p)
// 0x102f418cffunc main() {
fooMessage := "Hello, world!"
p := unsafe.StringData(fooMessage)
fmt.Printf("%p\n", p)
*p = 'C'
// unexpected fault address 0x102dcd1a8
// fatal error: fault
// [signal SIGBUS: bus error code=0x1 addr=0x102dcd1a8 pc=0x102d93960]
fmt.Printf("How immutable are you, '%s'?", fooMessage)
}#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char** args)
{
char *fooMessage = "Hello, world!";
*fooMessage = 'C';
// Bus error: 10
printf("How immutable are you, '%s'?\n", fooMessage);
return 0;
}func main() {
fooMessage := "Hello, world!"
fmt.Printf("%p\n", unsafe.StringData(fooMessage))
// 0x102b119fc
f2 := fmt.Sprintf("%s", fooMessage)
fmt.Printf("%p\n", unsafe.StringData(f2))
// 0x776914ed4480
}
~/Projects/immutable-strings
$ go build -o mgo main.go
~/Projects/immutable-strings
$ ./mgo
0x1029fe2a8
0x2c7456438060
~/Projects/immutable-strings
$ ./mgo
0x102bea2a8
0x56511b740060
~/Projects/immutable-strings
$ ./mgo
0x102cbe2a8
0x991481a4020
87654321 0011 2233 4455 6677 8899 aabb ccdd eeff 000a2290: 746c 7373 6563 706d 6c6b 656d 746c 7375 tlssecpmlkemtlsu 000a22a0: 6e73 6166 6565 6b6d 4865 6c6c 6f2c 2077 nsafeekmHello, w 000a22b0: 6f72 6c64 2133 3831 3436 3937 3236 3536 orld!38146972656
00000150: 5f5f 726f 6461 7461 0000 0000 0000 0000 __rodata........ 00000160: 5f5f 5445 5854 0000 0000 0000 0000 0000 __TEXT..........
func main() {
fooMessage := "Hello, world!"
fmt.Printf("%p\n", unsafe.StringData(fooMessage))
// 0x102b119fc
f2 := fmt.Sprintf("%s", fooMessage)
ptr := unsafe.StringData(f2)
fmt.Printf("%p\n", ptr)
// 0x776914ed4480
*ptr = 'C'
fmt.Printf("How immutable are you, '%s'?", f2)
// How immutable are you, 'Cello, world!'?
}