#`std.json.parseFromSlice` - need help understanding some memory management concepts

1 messages · Page 1 of 1 (latest)

ornate wigeon
#

Hi, hello!
I am trying to write a library that gets a response back from an http api, parses the json into a structured type, and returns that data structure or perhaps a subset of that data structure to a calling function.

I've learned about arena allocators, the leaky parsing functions, how freeing is for slices/arrays/pointer things and destroy is for others. I am however getting stuck on what exactly parseFromSlice is doing to the original memory in my json response. In my mind I thought it would be copying the relevant string portions into new memory within a struct and then converting types that aren't slices into the proper type and making that data owned by the caller as well. But I'm running into issues where it seems like the original json response might still be where some of the memory lives in the resulting parsed response as I am getting sementation faults when I do free the response but memory leaks if I don't.

This also might be an issue that is dependent on scope and how many functions I call. e.g. I was thinking of creating some functions that simply return raw json, parsed json, or parsed json leaky with an arena as input and then a function in the middle of a caller and those that dupes memory and condenses to smaller types, but I'm running into issues. Below is relevant code, I'm out of characters for discord free tier so I have to send this but any help is appreciated.

const response: []const u8 = http_request.reader().readAllAlloc(allocator, 3276800) catch unreachable;
defer allocator.free(response);
const parsed_response: std.json.Parsed(StructType) = try std.json.parseFromSlice(
    StructType, 
    allocator, 
    response,
    .{},
);
defer parsed_response.deinit();

// use parsed_response.value
defer arena_allocator.deinit();

const parsed_response: StructType = try std.json.parseFromSliceLeaky(
    StructType,
    arena_allocator,
    response,
    .{},
);
// use parsed_response
echo schooner
#

unfortunately that's not enough context to know your issue

ornate wigeon
#

Yeah, I ran out of characters :/ and I'm at the edges of my current comprehension so I can't reduce it down yet. I'll sit with it a bit more and come back and rewrite more with what I figure out. Thank you for the feedback @echo schooner

echo schooner
#

maybe if you had small snippet that produces your issue that you can link to or post here that would help a ton

#

some 2cents though:

  • remember that defers always execute at the end of the scope
  • arena_allocator.deinit(), frees all memory allocated with that allocator
  • parsed_response.deinit() frees the memory of the response and you can't refer to it anymore unless you copied it
ornate wigeon
#

I'll work on getting an example up but those are helpful things to know, ty. I guess the fundamental root of my lack of knowledge is does parseFromSlice rely on the memory passed into it's source(s: []const u8) ever or does it always create completely new memory and the source is copied/cloned where relevant?

pub fn parseFromSlice(
    comptime T: type,
    allocator: Allocator,
    s: []const u8,
    options: ParseOptions,
) ParseError(Scanner)!Parsed(T) {
    var scanner = Scanner.initCompleteInput(allocator, s);
    defer scanner.deinit();

    return parseFromTokenSource(T, allocator, &scanner, options);
}

Like if I take the Parsed(T) from above or perhaps just the T from the leaky version and discard the s passed in, will all memory in the Parsed(T) still be valid?

echo schooner
#

parseFromTokenSource takes allocator so one would assume so, but best way to know is to refer to the documentation