tiny.preserves.ownership
Defined in tiny.preserves.
Calls that copy a whole value tree and free one, each by the rule that matches how the tree was built.
API (5)
Actions
Public operations.
Value: Returns the tagged union of all values whose embedded values have typeD.cloneValueDeep: Copiesvalall the way down into storage fromalloc: atom bytes, integer digits, bind names, compound storage and embedded values.freeValue: Frees the outer storage of one compound value and nothing below it.freeValueDeep: Frees a value's compound storage all the way down: records, sequences, sets, dictionaries, captures, binds and rest patterns.
Types and contracts
Public types and contracts.
NoEmbedded: A type of embedded values whose one value carries no data, for programs that embed nothing.
Source
Source: lib/preserves/src/ownership.zig
zig
//! Calls that copy a whole value tree and free one, each by the rule that matches how the tree was//! built. A tree can own every byte, own its structure while borrowing its text, or own only its//! outermost storage, and each case needs its own free.//!//! Nothing in a value records which of its bytes it owns, so the caller has to pick the free that//! matches how the tree was built. A Lean model of parser results proves that when a borrowed//! symbol and an owned symbol look the same, no single cleanup frees exactly the owned bytes of//! both. The same model proves that cleanup is exact when every resource in a result is owned.//!//! The package keeps the values of the [Preserves](https://preserves.dev/) data language and//! changes one thing for this file: the caller owns and frees all storage.//!//! `cloneValueDeep` copies a tree into storage that owns every byte, so the copy takes the one full//! free, `Value.deinit`, whatever the original borrowed. `freeValueDeep` frees the structure of a//! tree and leaves its text, for trees that own their structure and borrow their strings and//! symbols. `freeValue` frees one level of storage, the level the root constructors allocate.//!//! - *domain*: the type of the embedded values//! - *pattern form*: one of four value kinds that describe a matchconst std = @import("std");const Allocator = std.mem.Allocator;const value_mod = @import("value.zig");const domain_mod = @import("domain.zig");pub const Value = value_mod.Value;pub const NoEmbedded = domain_mod.NoEmbedded;/// Copies `val` all the way down into storage from `alloc`: atom bytes, integer digits, bind names,/// compound storage and embedded values. Code that keeps a value past the life of its source calls/// it for a copy that owns every byte, as the text reader and the property tests do. A set or/// dictionary copy keeps the original's storage order. Each embedded value is copied by its type's/// `clone`. For `AnyEmbedded` that calls the payload's copy function, shares a payload that has/// neither a copy nor a free function, and panics on one that has a free function and no copy/// function. `Value.deinit` with `alloc` frees the result. On `error.OutOfMemory` the call frees/// every partial copy and leaves `val` unchanged.pub fn cloneValueDeep(comptime D: type, alloc: Allocator, val: Value(D)) Allocator.Error!Value(D) { domain_mod.assertIsDomain(D); const V = Value(D); return switch (val) { .boolean => |v| V.initBoolean(v), .double => |v| V.initDouble(v), .signed_integer => |si| V.initSignedInteger(try si.clone(alloc)), .string => |s| try V.initString(alloc, s), .byte_string => |s| try V.initByteString(alloc, s), .symbol => |s| try V.initSymbol(alloc, s), .record => |record| blk: { var label = try cloneValueDeep(D, alloc, record.label.*); errdefer label.deinit(alloc); const fields = try alloc.alloc(V, record.fields.len); var filled: usize = 0; errdefer { while (filled > 0) { filled -= 1; fields[filled].deinit(alloc); } alloc.free(fields); } while (filled < record.fields.len) : (filled += 1) { fields[filled] = try cloneValueDeep(D, alloc, record.fields[filled]); } break :blk try V.initRecord(alloc, label, fields); }, .sequence => |items| blk: { const cloned = try alloc.alloc(V, items.len); var filled: usize = 0; errdefer { while (filled > 0) { filled -= 1; cloned[filled].deinit(alloc); } alloc.free(cloned); } while (filled < items.len) : (filled += 1) { cloned[filled] = try cloneValueDeep(D, alloc, items[filled]); } break :blk V.initSequence(cloned); }, .set => |items| blk: { const cloned = try alloc.alloc(V, items.len); var filled: usize = 0; errdefer { while (filled > 0) { filled -= 1; cloned[filled].deinit(alloc); } alloc.free(cloned); } while (filled < items.len) : (filled += 1) { cloned[filled] = try cloneValueDeep(D, alloc, items[filled]); } break :blk V.initSet(cloned); }, .dictionary => |entries| blk: { const cloned = try alloc.alloc(V.DictionaryEntry, entries.len); var filled: usize = 0; errdefer { while (filled > 0) { filled -= 1; cloned[filled].key.deinit(alloc); cloned[filled].value.deinit(alloc); } alloc.free(cloned); } while (filled < entries.len) : (filled += 1) { var key = try cloneValueDeep(D, alloc, entries[filled].key); errdefer key.deinit(alloc); cloned[filled].value = try cloneValueDeep(D, alloc, entries[filled].value); cloned[filled].key = key; } break :blk V.initDictionary(cloned); }, .embedded => |embedded| V.initEmbedded(try embedded.clone(alloc)), .discard => .{ .discard = {} }, .capture => |inner| blk: { const ptr = try alloc.create(V); errdefer alloc.destroy(ptr); ptr.* = try cloneValueDeep(D, alloc, inner.*); break :blk .{ .capture = ptr }; }, .bind => |binding| blk: { const ptr = try alloc.create(V); errdefer alloc.destroy(ptr); ptr.* = try cloneValueDeep(D, alloc, binding.pattern.*); errdefer ptr.deinit(alloc); break :blk .{ .bind = .{ .name = try alloc.dupe(u8, binding.name), .pattern = ptr, }, }; }, .rest_pattern => |rest| blk: { const prefix = try alloc.alloc(V, rest.prefix.len); var filled: usize = 0; errdefer { while (filled > 0) { filled -= 1; prefix[filled].deinit(alloc); } alloc.free(prefix); } while (filled < rest.prefix.len) : (filled += 1) { prefix[filled] = try cloneValueDeep(D, alloc, rest.prefix[filled]); } const rest_ptr = try alloc.create(V); errdefer alloc.destroy(rest_ptr); rest_ptr.* = try cloneValueDeep(D, alloc, rest.rest.*); break :blk .{ .rest_pattern = .{ .prefix = prefix, .rest = rest_ptr } }; }, };}/// Frees the outer storage of one compound value and nothing below it. Code that built a value with/// the root constructors calls it at the end, as the README's example does. The outer storage is a/// record's label cell and field slice, the slice of a sequence, set or dictionary, the inner cell/// of a capture or bind, and a rest pattern's prefix slice and rest cell. The call matches the root/// constructors, which allocate that outer storage and borrow everything below it. The call frees/// no child value, no atom bytes and no embedded payload, and it does nothing to an atom or an/// embedded value.pub fn freeValue(comptime D: type, alloc: Allocator, val: Value(D)) void { domain_mod.assertIsDomain(D); switch (val) { .record => |r| { alloc.destroy(r.label); alloc.free(r.fields); }, .sequence => |s| alloc.free(s), .set => |s| alloc.free(s), .dictionary => |d| alloc.free(d), .capture => |inner| alloc.destroy(inner), .bind => |b| alloc.destroy(b.pattern), .rest_pattern => |rp| { alloc.free(rp.prefix); alloc.destroy(rp.rest); }, else => {}, }}/// Frees a value's compound storage all the way down: records, sequences, sets, dictionaries,/// captures, binds and rest patterns. The pattern conversions and `toText` call it for trees whose/// structure they own and whose text they borrow. The call also frees the digits of every integer/// too large for 128 bits. The call leaves the bytes of strings, byte strings and symbols, the/// names of binds, and embedded payloads. `Value.deinit` frees all of those as well, so it fits/// only a tree whose every byte comes from `alloc`.pub fn freeValueDeep(comptime D: type, alloc: Allocator, val: Value(D)) void { domain_mod.assertIsDomain(D); switch (val) { .signed_integer => |si| { var copy = si; copy.deinit(alloc); }, .record => |r| { freeValueDeep(D, alloc, r.label.*); for (r.fields) |f| freeValueDeep(D, alloc, f); alloc.destroy(r.label); alloc.free(r.fields); }, .sequence => |s| { for (s) |item| freeValueDeep(D, alloc, item); alloc.free(s); }, .set => |s| { for (s) |item| freeValueDeep(D, alloc, item); alloc.free(s); }, .dictionary => |d| { for (d) |entry| { freeValueDeep(D, alloc, entry.key); freeValueDeep(D, alloc, entry.value); } alloc.free(d); }, .capture => |inner| { freeValueDeep(D, alloc, inner.*); alloc.destroy(inner); }, .bind => |b| { freeValueDeep(D, alloc, b.pattern.*); alloc.destroy(b.pattern); }, .rest_pattern => |rp| { for (rp.prefix) |item| freeValueDeep(D, alloc, item); alloc.free(rp.prefix); freeValueDeep(D, alloc, rp.rest.*); alloc.destroy(rp.rest); }, else => {}, }}test "freeValue frees outer slice only" { const V = Value(NoEmbedded); const allocator = std.testing.allocator; const fields = try allocator.alloc(V, 2); fields[0] = V.initI128(1); fields[1] = V.initI128(2); const label_ptr = try allocator.create(V); label_ptr.* = V.initBoolean(true); const r: V = .{ .record = .{ .label = label_ptr, .fields = fields } }; freeValue(NoEmbedded, allocator, r);}test "cloneValueDeep owns copied atom and compound storage" { const V = Value(NoEmbedded); const allocator = std.testing.allocator; const dict_entries = try allocator.alloc(V.DictionaryEntry, 1); errdefer allocator.free(dict_entries); dict_entries[0] = .{ .key = V{ .symbol = "key" }, .value = V{ .byte_string = "bytes" } }; const fields = try allocator.alloc(V, 2); errdefer allocator.free(fields); fields[0] = V{ .string = "name" }; fields[1] = V.initDictionary(dict_entries); const original = try V.initRecord(allocator, V{ .symbol = "shape" }, fields); defer { allocator.destroy(original.record.label); allocator.free(original.record.fields); allocator.free(dict_entries); } var cloned = try cloneValueDeep(NoEmbedded, allocator, original); defer cloned.deinit(allocator); try std.testing.expect(original.eql(cloned));}test "cloneValueDeep owns copied pattern form storage" { const V = Value(NoEmbedded); const allocator = std.testing.allocator; const name = try allocator.dupe(u8, "slot"); defer allocator.free(name); const capture_ptr = try allocator.create(V); capture_ptr.* = .{ .discard = {} }; const bind_ptr = try allocator.create(V); bind_ptr.* = .{ .capture = capture_ptr }; const prefix = try allocator.alloc(V, 2); prefix[0] = V.initI128(1); prefix[1] = .{ .bind = .{ .name = name, .pattern = bind_ptr } }; const rest_ptr = try allocator.create(V); rest_ptr.* = V{ .symbol = "tail" }; const original: V = .{ .rest_pattern = .{ .prefix = prefix, .rest = rest_ptr } }; var cloned = try cloneValueDeep(NoEmbedded, allocator, original); defer cloned.deinit(allocator); try std.testing.expect(original.eql(cloned)); freeValueDeep(NoEmbedded, allocator, original); try std.testing.expectEqualStrings("slot", cloned.rest_pattern.prefix[1].bind.name);}test "freeValueDeep frees compound tree but skips string/symbol slices" { const V = Value(NoEmbedded); const allocator = std.testing.allocator; const inner_items = try allocator.alloc(V, 2); inner_items[0] = V{ .string = "borrowed-a" }; inner_items[1] = V{ .symbol = "borrowed-b" }; const inner_seq = V{ .sequence = inner_items }; const outer_items = try allocator.alloc(V, 1); outer_items[0] = inner_seq; const label_ptr = try allocator.create(V); label_ptr.* = V{ .symbol = "Label" }; const r: V = .{ .record = .{ .label = label_ptr, .fields = outer_items } }; freeValueDeep(NoEmbedded, allocator, r);}Source: lib/preserves/src/root.zig:114
zig
pub const ownership = @import("ownership.zig");Audit
| Definitions | 4 |
|---|---|
| Public names | 4 |
| Members | 0 |
| Version | 26.7.0 |
| Revision | daab053ee433 |