Как корректно передать данные из std.ArrayList в Slice?

Столкнулся со странным поведением структуры в zig. Точнее слайса ([]const u8) являющегося одним из элементов структуры, ниже пример.

const Statement = struct {
content: []const u8,
};

// read file
var script_file = try std.fs.cwd().openFile("text.txt", .{});
defer script_file.close();

// allocator
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();

// Read the text file by line
var buf_reader = std.io.bufferedReader(script_file.reader());
const reader = buf_reader.reader();

var line = std.ArrayList(u8).init(allocator);
defer line.deinit();

var line_no: usize = 0;
var statements: [26]Statement = undefined;  // в файле фиксированное количество строк

while (reader.streamUntilDelimiter(line.writer(), '\n', null)) : (line_no += 1) {
    defer line.clearRetainingCapacity();

    var i: u16 = 0;

    // write content in statments
    for (line.items) |char| {
        if (char == ' ') {
            statements[line_no] = .{ .content = line.items[(i + 1)..] };
            break;
        }
        i += 1;
    }
    std.debug.print("State content -- {d}:\t {s}\n", .{ line_no, statements[line_no].content });  // тут statements[line_no].content выводит корректно
} else |err| switch (err) {
    error.EndOfStream => {}, // Continue on
    else => std.debug.print("Ошибка при построчном чтении файла... {any}", .{err}), // Print error
}

inline for (statements) |s| {
    std.debug.print("State content len: {d}\n", .{s.content.len}); // корректно печатает длину слайса
    std.debug.print("State content ptr: {any}\n", .{s.content.ptr});  // печатает указатель на слайс
    std.debug.print("State content: {s}\n", .{s.content});    // здесь происходит ошибка:
    // State content: thread 13433 panic: reached unreachable code

}

Т.е. компиляция проходит успешно, но обратиться к элементу структуры вне цыкла в котором были перенесины данные не получается. При том длину строки отображает корректно. Возможно кто-то сталкивался с таким? Версия zig - 0.12.0-dev.2341+92211135f.


Ответы (1 шт):

Автор решения: insolor

Тут проблема в том, что вы в структуры записываете указатель на один и тот же буфер из line. При отладочном выводе в конце видно, что content.ptr один и тот же (тестировалось на релизных версиях 0.12.0, 0.13.0, panic не было):

State content len: 9
State content ptr: u8@72a84e679005
State content: artrthrth
State content len: 7
State content ptr: u8@72a84e679005
State content: artrthr
State content len: 4
State content ptr: u8@72a84e679005
State content: artr
State content len: 14
State content ptr: u8@72a84e679005
State content: artrthrth5rth4
State content len: 10
State content ptr: u8@72a84e679005
State content: artrthrth5

Входные данные были такие:

test aasdfwef1
test ergerh2
test tjy3
test aweerhrethrth4
test artrthrth5

Видно, что State content выше отображает какой-то мусор (точнее, не какой-то мусор, а вполне конкретный: то, что последнее было записано в буфер line, но с длиной как у фактически записанной строки, т.к. слайс хранит адрес и длину, вот длина как раз правильная, но адрес один и тот же).

Ну хотя если бы в моих данных перой частью был не test везде, а слова разной длины, то адрес был бы разный, но отличающийся на единицы байт.


Чтобы работало нормально, нужно на каждую строку аллоцировать новый буфер, в него копировать нужную часть строки:

// Уменьшил количество строк до 5, чтобы не набивать 26 рандомных строк файла
var statements: [5]Statement = undefined;

// Считаем, что при выходе из программы все строки заполнены корректными данными,
// и в content лежат валидные указатели, которые нужно будет освободить
defer {
    for (statements) |s| {
        allocator.free(s.content);
    }
}

while (reader.streamUntilDelimiter(line.writer(), '\n', null)) : (line_no += 1) {
    defer line.clearRetainingCapacity();

    var i: u16 = 0;

    // write content in statments
    for (line.items) |char| {
        if (char == ' ') {
            // Аллоцируем память и копируем в нее данные из нужного куска line.items
            const content = try allocator.dupe(u8, line.items[i + 1 ..]);
            // Записываем указатель на буфер в структуру
            statements[line_no] = .{ .content = content };
            break;
        }
        i += 1;
    }
    std.debug.print("State content -- {d}:\t {s}\n", .{ line_no, statements[line_no].content });
} else |err| switch (err) {
    error.EndOfStream => {}, // Continue on
    else => std.debug.print("Ошибка при построчном чтении файла... {any}", .{err}), // Print error
}

Входной файл:

test aasdfwef1
test ergerh2
test tjy3
test aweerhrethrth4
test artrthrth5

Вывод:

State content -- 0:      aasdfwef1
State content -- 1:      ergerh2
State content -- 2:      tjy3
State content -- 3:      aweerhrethrth4
State content -- 4:      artrthrth5
State content len: 9
State content ptr: u8@75d2defd8000
State content: aasdfwef1
State content len: 7
State content ptr: u8@75d2defe0000
State content: ergerh2
State content len: 4
State content ptr: u8@75d2defee000
State content: tjy3
State content len: 14
State content ptr: u8@75d2defd8010
State content: aweerhrethrth4
State content len: 10
State content ptr: u8@75d2defd8020
State content: artrthrth5
→ Ссылка