That first approach is also how Rust may be doing it; it just so happens that the compiler / linker may be laying out all these strings together. In C:
static char const * const msgs[] = {
[ERR1] = "long message for err1",
[ERR2] = "but message for err2 can be longer",
[ERR3] = "or message for err3 can be short"
};
is the same as (modulo string ordering):
static char const msg1[] = "long message for err1" /* + \0 */;
static char const msg2[] = "but message for err2 can be longer";
static char const * const msgs[] = {
[ERR1] = &msg1,
[ERR2] = &msg2,
…
};
That means that msg1
and msg2
(and so on) are perfectly allowed to to be laid out contiguously in static / global memory:
// by the linker:
static uint8_t const GLOBAL_RO_MEMORY[] = {
…
'l', 'o', 'n', 'g', ' ', 'm', 'e', 's', 's', 'a', 'g', 'e',
' ', 'f', 'o', 'r', ' ', 'e', 'r', 'r', '1', '\0',
'b', 'u', 't', ' ', 'm', 'e', 's', 's', 'a', 'g', 'e', ' ',
'f', 'o', 'r', ' ', 'e', 'r', 'r', '2', ' ', 'c', 'a', 'n', ' ',
'b', 'e', ' ', 'l', 'o', 'n', 'g', 'e', 'r', '\0',
…
};
And it turns out Rust may do the same thing, but with the big difference that Rust's strings are not null-terminated, so a C-String "scanning" that would start at long message…
would simply not stop until reaching a null byte arbitrarily far away in memory, hence "exposing" other officially-unrelated-even-if-laid-out-contiguously-by-the-linker read-only memory, such as the other language strings.
To further prove my point: