#Question about ` []const u8` lifetimes and allocations in struct fields

1 messages · Page 1 of 1 (latest)

vale lark
#

One of my projects is a torrent client in Zig. Provided the code below, I am wondering what are the lifetimes of short_path and full_path and where are they allocated (function caller call stack?).

const std = @import("std");

const log = std.log.scoped(.files);

/// `TorrentFile` is a structure representing a torrent file, its paths and contents
pub const TorrentFile = struct {
    /// `short_path` is the base name of the file (last segment of the path).
    short_path: []const u8,
    /// `full_path` is the complete path of the file.
    full_path: []const u8,
    /// `contents` are the contents of the file read into memory.
    contents: []const u8,
    /// `allocator` is the allocator used to manage memory for this structure.
    allocator: std.mem.Allocator,

    /// Opens and reads the contents of a file into memory, returning a `TorrentFile` structure.
    ///
    /// This function takes an allocator and the file path. It opens the file, reads its contents
    /// into memory, and returns a `TorrentFile` structure.
    /// The caller of the function needs to call `close` to free allocated memory.
    pub fn read(allocator: std.mem.Allocator, path: []const u8) !@This() {
        log.info("Opening file: {s}", .{path});
        const torrent_file = try std.fs.cwd().openFile(path, .{});
        defer torrent_file.close();

        const basename = std.fs.path.basename(path);
        log.debug("File base name is: {s}", .{basename});

        const file_size = try torrent_file.getEndPos();
        log.debug("Torrent file {s} size is: {d}", .{ basename, file_size });

        const buffer = try allocator.alloc(u8, file_size);
        errdefer allocator.free(buffer);

        const read_bytes = try torrent_file.readAll(buffer);
        log.debug("Read {d} bytes from file: {s}:", .{ read_bytes, basename });

        return .{
            .short_path = basename,
            .full_path = path,
            .contents = buffer,
            .allocator = allocator,
        };
    }

    /// Frees the memory allocated for the file contents.
    ///
    /// This function should be called to release the memory allocated by the `read` function
    /// when the `TorrentFile` structure is no longer needed.
    pub fn close(self: @This()) void {
        self.allocator.free(self.contents);
    }
};
thick willow
#

It depends entirely on the rest of the code. It seems like they aren't owned by the struct itself as close doesn't deallocate the fields you mentioned.

vale lark
thick willow
#

Yes, I've noticed.

#

How am I supposed to know how you handle memory management of the other fields? It could be on the heap, it could be on the stack, it could be somewhere entirely different.

vale lark
#

Let's say I have a function that has similar code (not main as in the example below):

const std = @import("std");
const fls = @import("files/file.zig");

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer std.debug.assert(gpa.deinit() == .ok);

    const alloc = gpa.allocator();

    const f = try fls.TorrentFile.read(alloc, "build.zig");
    std.debug.print("short: {s}, full: {s}, contents size: {d}\n", .{ f.short_path, f.full_path, f.contents.len });
    defer f.close();
}

Wil it be allocated on that func call stack?

thick willow
#

They'll both be pointers into static memory in that case, as they'll be slices of a string literal.

vale lark
#

So both short_path and full_path should last as long as the program is running, right?