Skip to content

Fixed-size array struct field has no valid backing storage when the struct is declared uninitialized (segfaults on every read/write) #162

Description

@manit2004

Environment

  • Rux 0.3.1 (build 2026-06-03)
  • Installed via Fedora Copr zapaxe/Rux-Lang
  • Fedora 43, x86_64, Linux target

Summary

When a struct contains a fixed-size array field (T[N]) and the struct variable is declared without an initializer (var b: Box;), the array field's underlying pointer is invalid (null or garbage) rather than pointing to allocated backing storage. Any access to that field — read or write, at any index — segfaults. This is unconditional: tested across every (index, read/write) combination for a 4-element array with no exceptions.

A plain standalone fixed-size array local (not nested in a struct), e.g. var arr: uint32[4];, does not have this problem — it is correctly allocated and readable/writable. The bug is specific to a fixed-size array used as a struct field combined with declaring the struct itself uninitialized.

Minimal reproduction

import Std::Io::Print;

struct Box {
    data: uint32[4];
}

func Main() -> int {
    Print("declared `var b: Box;` — about to read b.data[0] without ever writing to it...\n");
    var b: Box;
    let v = b.data[0];
    Print("survived — this line should not print\n");

    return 0;
}

Note this is a read, with no write performed anywhere beforehand — ruling out "stale/garbage data left over from a previous write" as the explanation. The pointer itself is invalid from the moment var b: Box; is declared.

Expected behavior

The program should print both lines and exit normally with code 0:

declared `var b: Box;` — about to read b.data[0] without ever writing to it...
survived — this line should not print

Actual behavior

declared `var b: Box;` — about to read b.data[0] without ever writing to it...
Segmentation fault (core dumped)

Exit code 139 (segfault).

Further verification: read and write at both ends of the array

Each example below was run as its own program (the process terminates on the first crash, so each is a separate execution). All four segfault.

Write to index 0:

struct Box {
    data: uint32[4];
}

func Main() -> int {
    var b: Box;
    b.data[0] = 111;
    return 0;
}

→ Segmentation fault (core dumped), exit code 139.

Write to index 3 (last element):

struct Box {
    data: uint32[4];
}

func Main() -> int {
    var b: Box;
    b.data[3] = 111;
    return 0;
}

→ Segmentation fault (core dumped), exit code 139.

Read index 0:

struct Box {
    data: uint32[4];
}

func Main() -> int {
    var b: Box;
    let v = b.data[0];
    return 0;
}

→ Segmentation fault (core dumped), exit code 139.

Read index 3 (last element):

struct Box {
    data: uint32[4];
}

func Main() -> int {
    var b: Box;
    let v = b.data[3];
    return 0;
}

→ Segmentation fault (core dumped), exit code 139.

Same result regardless of which index or which operation (read/write) — confirming the field's backing pointer is invalid in general, not just for a specific index.

Control case: a standalone array local works fine

import Std::Io::Print;
import Std::ToString;

func Main() -> int {
    var arr: uint32[4];
    arr[0] = 111;
    Print(ToString(arr[0]));
    Print("\n");
    arr[1] = 222;
    Print(ToString(arr[1]));
    Print("\n");
    return 0;
}

Output (correct, no crash):

111
222

This confirms fixed-size arrays are allocated correctly as plain locals — the bug only manifests when the same array type is a field of a struct that itself was declared without an initializer.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions