Files
bun.sh/src/ptr/owned.zig
2025-09-26 19:46:50 -07:00

429 lines
18 KiB
Zig

const owned = @This();
/// An owned pointer or slice that was allocated using the default allocator.
///
/// This type is a wrapper around a pointer or slice of type `Pointer` that was allocated using
/// `bun.default_allocator`. Calling `deinit` on this type first calls `deinit` on the underlying
/// data, and then frees the memory.
///
/// `Pointer` can be a single-item pointer, a slice, or an optional version of either of those;
/// e.g., `Owned(*u8)`, `Owned([]u8)`, `Owned(?*u8)`, or `Owned(?[]u8)`.
///
/// This type is an alias of `OwnedIn(Pointer, bun.DefaultAllocator)`, and thus has no overhead
/// because `bun.DefaultAllocator` is a zero-sized type.
pub fn Owned(comptime Pointer: type) type {
return OwnedIn(Pointer, bun.DefaultAllocator);
}
/// An owned pointer or slice allocated using any `std.mem.Allocator`.
///
/// This type is an alias of `OwnedIn(Pointer, std.mem.Allocator)`, and thus stores the
/// `std.mem.Allocator` at runtime.
pub fn Dynamic(comptime Pointer: type) type {
return OwnedIn(Pointer, std.mem.Allocator);
}
/// An owned pointer or slice, allocated using an instance of `Allocator`.
///
/// `Allocator` must be one of the following:
///
/// * `std.mem.Allocator`
/// * A type with a method named `allocator` that takes no parameters (except `self`) and returns
/// an instance of `std.mem.Allocator`.
///
/// If `Allocator` is a zero-sized type, the owned pointer has no overhead compared to a raw
/// pointer.
pub fn OwnedIn(comptime Pointer: type, comptime Allocator: type) type {
return struct {
const Self = @This();
const info = PointerInfo.parse(Pointer, .{});
const NonOptionalPointer = info.NonOptionalPointer;
const Child = info.Child;
const ConstPointer = AddConst(Pointer);
#pointer: Pointer,
#allocator: Allocator,
/// An unmanaged version of this owned pointer. This type doesn't store the allocator and
/// is the same size as a raw pointer.
///
/// If `Allocator` is a zero-sized type, there is no advantage to using this type. Just
/// use a normal owned pointer, which has no overhead in this case.
pub const Unmanaged = owned.Unmanaged(Pointer, Allocator);
/// Allocates a new owned pointer with a default-initialized `Allocator`.
pub const alloc = switch (info.kind()) {
.single => struct {
pub fn alloc(value: Child) AllocError!Self {
return .allocIn(value, bun.memory.initDefault(Allocator));
}
},
.slice => struct {
/// Note: this creates *shallow* copies of `elem`.
pub fn alloc(count: usize, elem: Child) AllocError!Self {
return .allocIn(count, elem, bun.memory.initDefault(Allocator));
}
},
}.alloc;
/// Allocates a new owned pointer with the given allocator.
pub const allocIn = switch (info.kind()) {
.single => struct {
pub fn allocIn(value: Child, allocator_: Allocator) AllocError!Self {
const data = try bun.memory.create(
Child,
bun.allocators.asStd(allocator_),
value,
);
return .{
.#pointer = data,
.#allocator = allocator_,
};
}
},
.slice => struct {
/// Note: this creates *shallow* copies of `elem`.
pub fn allocIn(count: usize, elem: Child, allocator_: Allocator) AllocError!Self {
const data = try bun.allocators.asStd(allocator_).alloc(Child, count);
@memset(data, elem);
return .{
.#pointer = data,
.#allocator = allocator_,
};
}
},
}.allocIn;
/// Allocates an owned pointer for a single item, and calls `bun.outOfMemory` if allocation
/// fails.
///
/// It must be possible to default-initialize `Allocator`.
pub const new = if (info.kind() == .single) struct {
pub fn new(value: Child) Self {
return bun.handleOom(Self.alloc(value));
}
}.new;
/// Creates an owned pointer by allocating memory and performing a shallow copy of `data`.
///
/// It must be possible to default-initialize `Allocator`.
pub fn allocDupe(data: ConstPointer) AllocError!Self {
return .allocDupeIn(data, bun.memory.initDefault(Allocator));
}
/// Creates an owned pointer by allocating memory with the given allocator and performing
/// a shallow copy of `data`.
pub fn allocDupeIn(data: ConstPointer, allocator_: Allocator) AllocError!Self {
const unwrapped = if (comptime info.isOptional())
data orelse return .initNull()
else
data;
return switch (comptime info.kind()) {
.single => .allocIn(unwrapped.*, allocator_),
.slice => .{
.#pointer = try bun.allocators.asStd(allocator_).dupe(Child, unwrapped),
.#allocator = allocator_,
},
};
}
/// Creates an owned pointer from a raw pointer.
///
/// Requirements:
///
/// * It must be permissible to free `data` with a new instance of `Allocator` created
/// with `bun.memory.initDefault(Allocator)`.
/// * `data` must not be freed for the life of the owned pointer.
///
/// NOTE: If `Allocator` is the default allocator, and `Pointer` is a single-item pointer,
/// `data` must have been allocated with `bun.new`, `bun.tryNew`, or `bun.memory.create`,
/// NOT `bun.default_allocator.create`. If `data` came from an owned pointer, this
/// requirement is satisfied.
///
/// `Allocator` is the default allocator if `Allocator.allocator` returns
/// `bun.default_allocator` when called on a default-initialized `Allocator` (created with
/// `bun.memory.initDefault`). Most notably, this is true for `bun.DefaultAllocator`.
pub fn fromRaw(data: Pointer) Self {
return .fromRawIn(data, bun.memory.initDefault(Allocator));
}
/// Creates an owned pointer from a raw pointer and allocator.
///
/// Requirements:
///
/// * It must be permissible to free `data` with `allocator`.
/// * `data` must not be freed for the life of the owned pointer.
///
/// NOTE: If `allocator` is the default allocator, and `Pointer` is a single-item pointer,
/// `data` must have been allocated with `bun.new`, `bun.tryNew`, or `bun.memory.create`,
/// NOT `bun.default_allocator.create`. If `data` came from `intoRaw` on another owned
/// pointer, this requirement is satisfied.
///
/// `allocator` is the default allocator if either of the following is true:
/// * `allocator` is `bun.default_allocator`
/// * `allocator.allocator()` returns `bun.default_allocator`
pub fn fromRawIn(data: Pointer, allocator_: Allocator) Self {
return .{
.#pointer = data,
// Code shouldn't rely on null pointers having a specific allocator, since
// `initNull` necessarily sets this field to undefined.
.#allocator = if ((comptime info.isOptional()) and data == null)
undefined
else
allocator_,
};
}
/// Calls `deinit` on the underlying data (pointer target or slice elements) and then
/// frees the memory.
///
/// `deinit` is also called on the allocator.
///
/// This method invalidates `self`.
pub fn deinit(self: *Self) void {
self.deinitImpl(.deep);
}
/// Frees the memory without calling `deinit` on the underlying data. `deinit` is still
/// called on the allocator.
///
/// This method invalidates `self`.
pub fn deinitShallow(self: *Self) void {
self.deinitImpl(.shallow);
}
/// Returns the inner pointer or slice.
pub fn get(self: Self) Pointer {
return self.#pointer;
}
/// Converts an owned pointer into a raw pointer. This releases ownership of the pointer.
///
/// This method calls `deinit` on the allocator. If you need to retain access to the
/// allocator, use `intoRawWithAllocator`.
///
/// NOTE: If the current allocator is the default allocator, and `Pointer` is a single-item
/// pointer, the pointer must be freed with `bun.destroy` or `bun.memory.destroy`, NOT
/// `bun.default_allocator.destroy`. Or it can be turned back into an owned pointer.
///
/// This method invalidates `self`.
pub fn intoRaw(self: *Self) Pointer {
defer self.* = undefined;
if ((comptime !info.isOptional()) or self.#pointer != null) {
bun.memory.deinit(&self.#allocator);
}
return self.#pointer;
}
const PointerAndAllocator = if (info.isOptional())
?struct { NonOptionalPointer, Allocator }
else
struct { Pointer, Allocator };
/// Converts an owned pointer into a raw pointer and allocator, releasing ownership of the
/// pointer.
///
/// NOTE: If the current allocator is the default allocator, and `Pointer` is a single-item
/// pointer, the pointer must be freed with `bun.destroy` or `bun.memory.destroy`, NOT
/// `bun.default_allocator.destroy`. Or it can be turned back into an owned pointer.
///
/// This method invalidates `self`.
pub fn intoRawWithAllocator(self: *Self) PointerAndAllocator {
defer self.* = undefined;
const data = if (comptime info.isOptional())
self.#pointer orelse return null
else
self.#pointer;
return .{ data, self.#allocator };
}
/// Returns a null owned pointer. This function is provided only if `Pointer` is an
/// optional type.
///
/// It is permitted, but not required, to call `deinit` on the returned value.
pub const initNull = if (info.isOptional()) struct {
pub fn initNull() Self {
return .{
.#pointer = null,
.#allocator = undefined,
};
}
}.initNull;
/// Converts an `Owned(?T)` into an `?Owned(T)`.
///
/// This method sets `self` to null. It is therefore permitted, but not required, to call
/// `deinit` on `self`.
///
/// This method is provided only if `Pointer` is an optional type.
pub const take = if (info.isOptional()) struct {
const OwnedNonOptional = OwnedIn(NonOptionalPointer, Allocator);
pub fn take(self: *Self) ?OwnedNonOptional {
defer self.* = .initNull();
return .{
.#pointer = self.#pointer orelse return null,
.#allocator = self.#allocator,
};
}
}.take;
/// Like `deinit`, but sets `self` to null instead of invalidating it.
///
/// This method is provided only if `Pointer` is an optional type.
pub const reset = if (info.isOptional()) struct {
pub fn reset(self: *Self) void {
defer self.* = .initNull();
self.deinit();
}
}.reset;
/// Converts an `Owned(T)` into a non-null `Owned(?T)`.
///
/// This method invalidates `self`.
pub const toOptional = if (!info.isOptional()) struct {
const OwnedOptional = OwnedIn(?Pointer, Allocator);
pub fn toOptional(self: *Self) OwnedOptional {
defer self.* = undefined;
return .{
.#pointer = self.#pointer,
.#allocator = self.#allocator,
};
}
}.toOptional;
/// Converts this owned pointer into an unmanaged variant that doesn't store the allocator.
///
/// There is no reason to use this method if `Allocator` is a zero-sized type, as a normal
/// owned pointer has no overhead in this case.
///
/// This method invalidates `self`.
pub fn toUnmanaged(self: *Self) Self.Unmanaged {
defer self.* = undefined;
return .{
.#pointer = self.#pointer,
};
}
/// Converts an owned pointer that uses a fixed type of allocator into a dynamic one
/// that uses any `std.mem.Allocator`.
///
/// It must be possible to use the `std.mem.Allocator` returned by `Allocator.allocator`
/// even after deinitializing the `Allocator`. As a safety check, this method will not
/// compile if `Allocator.Borrowed` exists and is a different type from `Allocator`, as
/// this likely indicates a scenario where this invariant will not hold.
///
/// There is no reason to use this method if `Allocator` is already `std.mem.Allocator`.
///
/// This method invalidates `self`.
pub fn toDynamic(self: *Self) owned.Dynamic(Pointer) {
if (comptime @hasDecl(Allocator, "Borrowed") and Allocator.Borrowed != Allocator) {
// If this allocator can be borrowed as a different type, it's likely that the
// `std.mem.Allocator` returned by `Allocator.allocator` won't be valid after the
// `Allocator` is dropped.
@compileError("allocator won't live long enough");
}
defer self.* = undefined;
const data = if (comptime info.isOptional())
self.#pointer orelse return .initNull()
else
self.#pointer;
defer bun.memory.deinit(&self.#allocator);
return .fromRawIn(data, self.getStdAllocator());
}
const MaybeAllocator = if (info.isOptional())
?bun.allocators.Borrowed(Allocator)
else
bun.allocators.Borrowed(Allocator);
/// Returns a borrowed version of the allocator.
///
/// Not all allocators have a separate borrowed type; in this case, the allocator is
/// returned as-is. For example, if `Allocator` is `std.mem.Allocator`, this method also
/// returns `std.mem.Allocator`.
pub fn allocator(self: Self) MaybeAllocator {
return if ((comptime info.isOptional()) and self.#pointer == null)
null
else
bun.allocators.borrow(self.#allocator);
}
fn getStdAllocator(self: Self) std.mem.Allocator {
return bun.allocators.asStd(self.#allocator);
}
fn deinitImpl(self: *Self, comptime mode: enum { deep, shallow }) void {
defer self.* = undefined;
const data = if (comptime info.isOptional())
self.#pointer orelse return
else
self.#pointer;
if (comptime mode == .deep) {
bun.memory.deinit(data);
}
switch (comptime info.kind()) {
.single => bun.memory.destroy(self.getStdAllocator(), data),
.slice => self.getStdAllocator().free(data),
}
bun.memory.deinit(&self.#allocator);
}
};
}
/// An unmanaged version of `OwnedIn(Pointer, Allocator)` that doesn't store the allocator.
///
/// If `Allocator` is a zero-sized type, there is no benefit to using this type. Just use a
/// normal owned pointer, which has no overhead in this case.
///
/// This type is accessible as `OwnedIn(Pointer, Allocator).Unmanaged`.
fn Unmanaged(comptime Pointer: type, comptime Allocator: type) type {
return struct {
const Self = @This();
const info = PointerInfo.parse(Pointer, .{});
#pointer: Pointer,
const Managed = OwnedIn(Pointer, Allocator);
/// Converts this unmanaged owned pointer back into a managed version.
///
/// `allocator` must be the allocator that was used to allocate the pointer.
///
/// This method invalidates `self`.
pub fn toManaged(self: *Self, allocator: Allocator) Managed {
defer self.* = undefined;
const data = if (comptime info.isOptional())
self.#pointer orelse return .initNull()
else
self.#pointer;
return .fromRawIn(data, allocator);
}
/// Deinitializes the pointer or slice. See `Owned.deinit` for more information.
///
/// `allocator` must be the allocator that was used to allocate the pointer.
///
/// This method invalidates `self`.
pub fn deinit(self: *Self, allocator: Allocator) void {
var managed = self.toManaged(allocator);
managed.deinit();
}
/// Returns the inner pointer or slice.
pub fn get(self: Self) Pointer {
return self.#pointer;
}
};
}
const bun = @import("bun");
const std = @import("std");
const AllocError = std.mem.Allocator.Error;
const meta = @import("./meta.zig");
const AddConst = meta.AddConst;
const PointerInfo = meta.PointerInfo;