Rename utils.allocator to utils.gpa

it seems like `gpa` has become pretty much the universally agreed upon
name for your... gpa, so we're renaming.
This commit is contained in:
Ben Buhse 2026-02-12 13:40:57 -06:00
commit 95425aa73f
No known key found for this signature in database
GPG key ID: 7916ACFCD38FD0B4
14 changed files with 89 additions and 90 deletions

View file

@ -30,7 +30,7 @@ pub fn deinit(buffer_pool: *BufferPool) void {
it = node.next;
const buffer: *Buffer = @fieldParentPtr("node", node);
buffer.deinit();
utils.allocator.destroy(buffer);
utils.gpa.destroy(buffer);
}
}
@ -82,8 +82,8 @@ fn findSuitableBuffer(buffer_pool: *BufferPool, wl_shm: *wl.Shm, width: u31, hei
fn newBuffer(buffer_pool: *BufferPool, wl_shm: *wl.Shm, width: u31, height: u31) !*Buffer {
log.debug("creating new buffer {}x{}", .{ width, height });
const buffer = try utils.allocator.create(Buffer);
errdefer utils.allocator.destroy(buffer);
const buffer = try utils.gpa.create(Buffer);
errdefer utils.gpa.destroy(buffer);
buffer.* = try Buffer.init(wl_shm, width, height);
buffer.setListener();
buffer_pool.buffers.append(&buffer.node);
@ -103,7 +103,7 @@ fn cullBuffers(buffer_pool: *BufferPool) void {
if (!buffer.busy) {
buffer.deinit();
buffer_pool.buffers.remove(node);
utils.allocator.destroy(buffer);
utils.gpa.destroy(buffer);
buffer_pool.len -= 1;
overhead -= 1;
}

View file

@ -136,15 +136,15 @@ const InputConfigNodeName = enum {
const KeybindNodeName = @typeInfo(XkbBindings.Command).@"union".tag_type.?;
pub fn create() !*Config {
var config: *Config = try utils.allocator.create(Config);
var config: *Config = try utils.gpa.create(Config);
errdefer config.destroy();
config.* = .{}; // create() gives us undefined memory
if (try known_folders.getPath(utils.allocator, .local_configuration)) |config_dir| blk: {
defer utils.allocator.free(config_dir);
if (try known_folders.getPath(utils.gpa, .local_configuration)) |config_dir| blk: {
defer utils.gpa.free(config_dir);
const config_path = try std.fmt.allocPrint(utils.allocator, "{s}/{s}", .{ config_dir, CONFIG_FILE });
defer utils.allocator.free(config_path);
const config_path = try std.fmt.allocPrint(utils.gpa, "{s}/{s}", .{ config_dir, CONFIG_FILE });
defer utils.gpa.free(config_path);
const file = fs.openFileAbsolute(config_path, .{}) catch break :blk;
@ -157,21 +157,21 @@ pub fn create() !*Config {
for (config.keybinds.items) |keybind| {
switch (keybind.command) {
.spawn => |argv| {
for (argv) |arg| utils.allocator.free(arg);
utils.allocator.free(argv);
for (argv) |arg| utils.gpa.free(arg);
utils.gpa.free(argv);
},
else => {},
}
}
config.keybinds.clearAndFree(utils.allocator);
config.tag_binds.clearAndFree(utils.allocator);
config.pointer_binds.clearAndFree(utils.allocator);
config.keybinds.clearAndFree(utils.gpa);
config.tag_binds.clearAndFree(utils.gpa);
config.pointer_binds.clearAndFree(utils.gpa);
for (config.input_configs.items) |ic| {
if (ic.name) |name| utils.allocator.free(name);
if (ic.name) |name| utils.gpa.free(name);
}
config.input_configs.clearAndFree(utils.allocator);
config.input_configs.clearAndFree(utils.gpa);
if (config.wallpaper_image_path) |path| {
utils.allocator.free(path);
utils.gpa.free(path);
}
config.* = .{};
};
@ -184,28 +184,28 @@ pub fn destroy(config: *Config) void {
for (config.keybinds.items) |keybind| {
switch (keybind.command) {
.spawn => |argv| {
for (argv) |arg| utils.allocator.free(arg);
utils.allocator.free(argv);
for (argv) |arg| utils.gpa.free(arg);
utils.gpa.free(argv);
},
else => {},
}
}
config.keybinds.deinit(utils.allocator);
config.tag_binds.deinit(utils.allocator);
config.pointer_binds.deinit(utils.allocator);
config.keybinds.deinit(utils.gpa);
config.tag_binds.deinit(utils.gpa);
config.pointer_binds.deinit(utils.gpa);
for (config.input_configs.items) |ic| {
if (ic.name) |name| utils.allocator.free(name);
if (ic.name) |name| utils.gpa.free(name);
}
config.input_configs.deinit(utils.allocator);
config.input_configs.deinit(utils.gpa);
if (config.wallpaper_image_path) |path| {
utils.allocator.free(path);
utils.gpa.free(path);
}
utils.allocator.destroy(config);
utils.gpa.destroy(config);
}
fn load(config: *Config, reader: *Io.Reader) !void {
var parser = try kdl.Parser.init(utils.allocator, reader, .{});
defer parser.deinit(utils.allocator);
var parser = try kdl.Parser.init(utils.gpa, reader, .{});
defer parser.deinit(utils.gpa);
const hostname = blk: {
var uname = std.posix.uname();
@ -216,7 +216,7 @@ fn load(config: *Config, reader: *Io.Reader) !void {
var next_child_block: ?NodeName = null;
var pending_input_name: ?[]const u8 = null;
defer if (pending_input_name) |n| utils.allocator.free(n);
defer if (pending_input_name) |n| utils.gpa.free(n);
// Parse the KDL config
while (try parser.next()) |event| {
@ -226,7 +226,7 @@ fn load(config: *Config, reader: *Io.Reader) !void {
if (next_child_block) |child_block| {
logWarnMissingChildBlock(child_block);
next_child_block = null;
if (pending_input_name) |n| utils.allocator.free(n);
if (pending_input_name) |n| utils.gpa.free(n);
pending_input_name = null;
}
// If it's a node, we check if it's a valid NodeName
@ -310,7 +310,7 @@ fn load(config: *Config, reader: *Io.Reader) !void {
},
.input => {
pending_input_name = if (node.prop(&parser, "name")) |n|
try utils.allocator.dupe(u8, utils.stripQuotes(n))
try utils.gpa.dupe(u8, utils.stripQuotes(n))
else
null;
next_child_block = .input;
@ -437,7 +437,7 @@ fn loadKeybindsChildBlock(config: *Config, parser: *kdl.Parser, hostname: ?[]con
},
};
try config.tag_binds.append(utils.allocator, .{
try config.tag_binds.append(utils.gpa, .{
.modifiers = modifiers,
.command = command,
.keysym = null, // Tag binds don't need a keysym (automatically 1-9)
@ -466,8 +466,8 @@ fn loadKeybindsChildBlock(config: *Config, parser: *kdl.Parser, hostname: ?[]con
continue;
});
// Keysym.fromName() needs a [*:0]const u8
const z = try utils.allocator.dupeZ(u8, key_str);
defer utils.allocator.free(z);
const z = try utils.gpa.dupeZ(u8, key_str);
defer utils.gpa.free(z);
const keysym = xkbcommon.Keysym.fromName(z, .case_insensitive);
const command: XkbBindings.Command = sw: switch (name) {
@ -540,7 +540,7 @@ fn loadKeybindsChildBlock(config: *Config, parser: *kdl.Parser, hostname: ?[]con
},
};
try config.keybinds.append(utils.allocator, .{
try config.keybinds.append(utils.gpa, .{
.modifiers = modifiers,
.command = command,
.keysym = keysym,
@ -596,7 +596,7 @@ fn loadPointerBindsChildBlock(config: *Config, parser: *kdl.Parser, hostname: ?[
.resize_window => .resize_window,
};
try config.pointer_binds.append(utils.allocator, .{
try config.pointer_binds.append(utils.gpa, .{
.modifiers = modifiers,
.button = button,
.action = action,
@ -619,7 +619,7 @@ fn loadPointerBindsChildBlock(config: *Config, parser: *kdl.Parser, hostname: ?[
fn loadInputChildBlock(config: *Config, parser: *kdl.Parser, name: ?[]const u8, hostname: ?[]const u8) !void {
var input_config: InputConfig = .{ .name = name };
errdefer if (input_config.name) |n| utils.allocator.free(n);
errdefer if (input_config.name) |n| utils.gpa.free(n);
while (try parser.next()) |event| {
switch (event) {
@ -697,7 +697,7 @@ fn loadInputChildBlock(config: *Config, parser: *kdl.Parser, name: ?[]const u8,
try config.skipChildBlock(parser);
},
.child_block_end => {
try config.input_configs.append(utils.allocator, input_config);
try config.input_configs.append(utils.gpa, input_config);
return;
},
}
@ -822,9 +822,9 @@ fn logDebugSettingNode(node_name: anytype, node_value: []const u8) void {
fn expandTilde(path: []const u8) ![]const u8 {
if (path.len > 0 and path[0] == '~') {
const home = std.posix.getenv("HOME") orelse return error.HomeNotSet;
return std.fmt.allocPrint(utils.allocator, "{s}{s}", .{ home, path[1..] });
return std.fmt.allocPrint(utils.gpa, "{s}{s}", .{ home, path[1..] });
}
return utils.allocator.dupe(u8, path);
return utils.gpa.dupe(u8, path);
}
/// Check whether this machine's hostname matches the hostname property
@ -918,7 +918,7 @@ test "parseButton invalid" {
test "expandTilde with tilde" {
const result = try expandTilde("~/foo/bar");
defer utils.allocator.free(result);
defer utils.gpa.free(result);
const home = std.posix.getenv("HOME") orelse return;
try testing.expect(mem.startsWith(u8, result, home));
try testing.expect(mem.endsWith(u8, result, "/foo/bar"));
@ -926,6 +926,6 @@ test "expandTilde with tilde" {
test "expandTilde without tilde" {
const result = try expandTilde("/absolute/path");
defer utils.allocator.free(result);
defer utils.gpa.free(result);
try testing.expectEqualStrings("/absolute/path", result);
}

View file

@ -58,7 +58,7 @@ pub const Options = struct {
};
pub fn create(options: Options) !*Context {
const context = try utils.allocator.create(Context);
const context = try utils.gpa.create(Context);
errdefer context.destroy();
context.* = .{
@ -88,7 +88,7 @@ pub fn destroy(context: *Context) void {
}
context.buffer_pool.deinit();
utils.allocator.destroy(context);
utils.gpa.destroy(context);
}
pub fn manage(context: *Context) void {

View file

@ -18,7 +18,7 @@ name: ?[]const u8 = null,
link: wl.list.Link,
pub fn create(river_input_device_v1: *river.InputDeviceV1) !*InputDevice {
const input_device = try utils.allocator.create(InputDevice);
const input_device = try utils.gpa.create(InputDevice);
errdefer input_device.destroy();
input_device.* = .{
@ -40,10 +40,10 @@ pub fn destroy(input_device: *InputDevice) void {
libinput_device.input_device = null;
}
if (input_device.name) |name| {
utils.allocator.free(name);
utils.gpa.free(name);
}
input_device.link.remove();
utils.allocator.destroy(input_device);
utils.gpa.destroy(input_device);
}
fn riverInputDeviceV1Listener(river_input_device_v1: *river.InputDeviceV1, event: river.InputDeviceV1.Event, input_device: *InputDevice) void {
@ -54,7 +54,7 @@ fn riverInputDeviceV1Listener(river_input_device_v1: *river.InputDeviceV1, event
input_device.destroy();
},
.type => |ev| input_device.type = ev.type,
.name => |ev| input_device.name = utils.allocator.dupe(u8, mem.span(ev.name)) catch @panic("Out of memory"),
.name => |ev| input_device.name = utils.gpa.dupe(u8, mem.span(ev.name)) catch @panic("Out of memory"),
}
}

View file

@ -17,7 +17,7 @@ libinput_devices: wl.list.Head(LibinputDevice, .link),
pub fn create(context: *Context, river_input_manager_v1: *river.InputManagerV1, river_libinput_config_v1: *river.LibinputConfigV1) !*InputManager {
log.debug("Creating new InputManager", .{});
const im = try utils.allocator.create(InputManager);
const im = try utils.gpa.create(InputManager);
errdefer im.destroy();
im.* = .{
@ -38,7 +38,7 @@ pub fn create(context: *Context, river_input_manager_v1: *river.InputManagerV1,
}
pub fn destroy(im: *InputManager) void {
utils.allocator.destroy(im);
utils.gpa.destroy(im);
}
pub fn inputManagerV1Listener(river_input_manager_v1: *river.InputManagerV1, event: river.InputManagerV1.Event, im: *InputManager) void {

View file

@ -78,7 +78,7 @@ rotation_current: ?u32 = null,
link: wl.list.Link,
pub fn create(context: *Context, river_libinput_device_v1: *river.LibinputDeviceV1) !*LibinputDevice {
const libinput_device = try utils.allocator.create(LibinputDevice);
const libinput_device = try utils.gpa.create(LibinputDevice);
errdefer libinput_device.destroy();
libinput_device.* = .{
@ -101,7 +101,7 @@ pub fn destroy(libinput_device: *LibinputDevice) void {
input_device.libinput_device = null;
}
libinput_device.link.remove();
utils.allocator.destroy(libinput_device);
utils.gpa.destroy(libinput_device);
}
fn riverLibinputDeviceV1Listener(river_libinput_device_v1: *river.LibinputDeviceV1, event: river.LibinputDeviceV1.Event, libinput_device: *LibinputDevice) void {

View file

@ -69,7 +69,7 @@ pub const PendingManage = struct {
};
pub fn create(context: *Context, river_output_v1: *river.OutputV1) !*Output {
var output = try utils.allocator.create(Output);
var output = try utils.gpa.create(Output);
errdefer output.destroy();
output.* = .{
@ -95,10 +95,10 @@ pub fn destroy(output: *Output) void {
window.destroy();
}
output.tag_layout_overrides.deinit(utils.allocator);
output.tag_layout_overrides.deinit(utils.gpa);
output.deinitWallpaperLayerSurface();
output.river_output_v1.destroy();
utils.allocator.destroy(output);
utils.gpa.destroy(output);
}
/// Get the next window in the list that shares at least one tag
@ -239,7 +239,7 @@ fn wlOutputListener(_: *wl.Output, event: wl.Output.Event, output: *Output) void
output.scale = @intCast(ev.factor);
},
.name => |ev| {
output.name = utils.allocator.dupe(u8, mem.span(ev.name)) catch @panic("Out of memory");
output.name = utils.gpa.dupe(u8, mem.span(ev.name)) catch @panic("Out of memory");
},
else => {},
}
@ -449,7 +449,7 @@ pub fn manage(output: *Output) void {
}
if (output.pending_manage.tags) |new_tags| {
// Save current layout for the old tagmask
output.tag_layout_overrides.put(utils.allocator, output.tags, .{
output.tag_layout_overrides.put(utils.gpa, output.tags, .{
.primary_count = output.primary_count,
.primary_ratio = output.primary_ratio,
}) catch @panic("Out of memory");

View file

@ -57,7 +57,7 @@ pub const PointerOp = union(enum) {
};
pub fn create(context: *Context, river_seat_v1: *river.SeatV1) !*Seat {
var seat = try utils.allocator.create(Seat);
var seat = try utils.gpa.create(Seat);
errdefer seat.destroy();
seat.* = .{
@ -77,7 +77,7 @@ pub fn destroy(seat: *Seat) void {
if (seat.move_pointer_binding) |binding| binding.destroy();
if (seat.resize_pointer_binding) |binding| binding.destroy();
seat.river_seat_v1.destroy();
utils.allocator.destroy(seat);
utils.gpa.destroy(seat);
}
fn seatListener(river_seat_v1: *river.SeatV1, event: river.SeatV1.Event, seat: *Seat) void {

View file

@ -9,17 +9,17 @@ pixels: std.ArrayList(u32),
// TODO: Make image_path nullable, if null, do a single color with a single_pixel_buffer instead(?)
pub fn create(image_path: []const u8) !*WallpaperImage {
var wallpaper_image = try utils.allocator.create(WallpaperImage);
errdefer utils.allocator.destroy(wallpaper_image);
var wallpaper_image = try utils.gpa.create(WallpaperImage);
errdefer utils.gpa.destroy(wallpaper_image);
var read_buf: [zigimg.io.DEFAULT_BUFFER_SIZE]u8 = undefined;
var image = try zigimg.Image.fromFilePath(utils.allocator, image_path, &read_buf);
defer image.deinit(utils.allocator);
var image = try zigimg.Image.fromFilePath(utils.gpa, image_path, &read_buf);
defer image.deinit(utils.gpa);
// We don't want to deal with all the possible formats,
// so let's just convert to one we can use with pixman.
if (image.pixelFormat() != .rgba32) {
try image.convert(utils.allocator, .rgba32);
try image.convert(utils.gpa, .rgba32);
}
log.debug("image loaded ({}x{})", .{ image.width, image.height });
@ -27,8 +27,8 @@ pub fn create(image_path: []const u8) !*WallpaperImage {
const pixels = image.pixels.rgba32;
// We have to manually convert to argb --
// It's only guaranteed that Wayland compositors will have xrgb and argb support but zigimg doesn't have either of those.
wallpaper_image.pixels = try std.ArrayList(u32).initCapacity(utils.allocator, pixels.len);
errdefer wallpaper_image.pixels.deinit(utils.allocator);
wallpaper_image.pixels = try std.ArrayList(u32).initCapacity(utils.gpa, pixels.len);
errdefer wallpaper_image.pixels.deinit(utils.gpa);
for (0..pixels.len) |i| {
const a: u32 = @intCast(pixels[i].a);
const r: u32 = @intCast(pixels[i].r);
@ -45,9 +45,9 @@ pub fn create(image_path: []const u8) !*WallpaperImage {
pub fn destroy(wallpaper_image: *WallpaperImage) void {
_ = wallpaper_image.image.unref();
wallpaper_image.pixels.deinit(utils.allocator);
wallpaper_image.pixels.deinit(utils.gpa);
utils.allocator.destroy(wallpaper_image);
utils.gpa.destroy(wallpaper_image);
}
const std = @import("std");

View file

@ -68,7 +68,7 @@ pub const PendingRender = struct {
};
pub fn create(context: *Context, river_window_v1: *river.WindowV1, output: ?*Output) !*Window {
var window = try utils.allocator.create(Window);
var window = try utils.gpa.create(Window);
errdefer window.destroy();
window.* = .{
@ -89,7 +89,7 @@ pub fn destroy(window: *Window) void {
window.river_window_v1.destroy();
window.river_node_v1.destroy();
utils.allocator.destroy(window);
utils.gpa.destroy(window);
}
fn windowListener(river_window_v1: *river.WindowV1, event: river.WindowV1.Event, window: *Window) void {

View file

@ -17,7 +17,7 @@ outputs: wl.list.Head(Output, .link),
orphan_windows: wl.list.Head(Window, .link),
pub fn create(context: *Context, window_manager_v1: *river.WindowManagerV1) !*WindowManager {
const wm = try utils.allocator.create(WindowManager);
const wm = try utils.gpa.create(WindowManager);
errdefer wm.destroy();
wm.* = .{
@ -53,7 +53,7 @@ pub fn destroy(wm: *WindowManager) void {
}
}
utils.allocator.destroy(wm);
utils.gpa.destroy(wm);
}
/// Get the next output in the list, wrapping to first if at end

View file

@ -55,7 +55,7 @@ const XkbBinding = struct {
const FocusDirection = enum { next, prev };
fn create(xkb_binding_v1: *river.XkbBindingV1, command: Command, context: *Context) !*XkbBinding {
var xkb_binding = try utils.allocator.create(XkbBinding);
var xkb_binding = try utils.gpa.create(XkbBinding);
errdefer xkb_binding.destroy();
xkb_binding.* = .{
@ -72,7 +72,7 @@ const XkbBinding = struct {
pub fn destroy(xkb_binding: *XkbBinding) void {
xkb_binding.xkb_binding_v1.destroy();
utils.allocator.destroy(xkb_binding);
utils.gpa.destroy(xkb_binding);
}
fn xkbBindingListener(river_xkb_binding_v1: *river.XkbBindingV1, event: river.XkbBindingV1.Event, xkb_binding: *XkbBinding) void {
@ -93,7 +93,7 @@ const XkbBinding = struct {
// TODO: Should I log.warn when commands return early?
switch (xkb_binding.command) {
.spawn => |cmd| {
var child = std.process.Child.init(cmd, utils.allocator);
var child = std.process.Child.init(cmd, utils.gpa);
_ = child.spawn() catch |err| {
log.err("Failed to spawn \"{s}\": {}", .{ cmd[0], err });
};
@ -404,7 +404,7 @@ xkb_bindings_v1: *river.XkbBindingsV1,
bindings: wl.list.Head(XkbBinding, .link),
pub fn create(context: *Context, xkb_bindings_v1: *river.XkbBindingsV1) !*XkbBindings {
const xkb_bindings = try utils.allocator.create(XkbBindings);
const xkb_bindings = try utils.gpa.create(XkbBindings);
errdefer xkb_bindings.destroy();
xkb_bindings.* = .{
@ -423,9 +423,9 @@ pub fn destroy(xkb_bindings: *XkbBindings) void {
while (it.next()) |binding| {
binding.link.remove();
binding.xkb_binding_v1.destroy();
utils.allocator.destroy(binding);
utils.gpa.destroy(binding);
}
utils.allocator.destroy(xkb_bindings);
utils.gpa.destroy(xkb_bindings);
}
pub fn addBinding(xkb_bindings: *XkbBindings, river_seat_v1: *river.SeatV1, keysym: xkbcommon.Keysym, modifiers: river.SeatV1.Modifiers, command: Command) void {

View file

@ -21,7 +21,7 @@ const Globals = struct {
while (it.next()) |output| {
output.*.release();
}
globals.wl_outputs.deinit(utils.allocator);
globals.wl_outputs.deinit(utils.gpa);
}
};
@ -78,10 +78,10 @@ pub fn main() !void {
}
}
const wayland_display_var = try utils.allocator.dupeZ(u8, process.getEnvVarOwned(utils.allocator, "WAYLAND_DISPLAY") catch {
const wayland_display_var = try utils.gpa.dupeZ(u8, process.getEnvVarOwned(utils.gpa, "WAYLAND_DISPLAY") catch {
fatal("Error getting WAYLAND_DISPLAY environment variable. Exiting", .{});
});
defer utils.allocator.free(wayland_display_var);
defer utils.gpa.free(wayland_display_var);
const wl_display = wl.Display.connect(null) catch {
fatal("Error connecting to Wayland server. Exiting", .{});
@ -156,7 +156,7 @@ fn registryListener(registry: *wl.Registry, event: wl.Registry.Event, globals: *
// We can get multiple wl_outputs, so we have to try add them to our HashMap
// instead of just keeping the one
globals.wl_outputs.put(utils.allocator, ev.name, wl_output) catch |e| {
globals.wl_outputs.put(utils.gpa, ev.name, wl_output) catch |e| {
fatal("Failed to add wl_output to hashmap: {any}", .{@errorName(e)});
};
} else if (mem.orderZ(u8, ev.interface, wl.Shm.interface.name) == .eq) {

View file

@ -2,9 +2,8 @@
//
// SPDX-License-Identifier: GPL-3.0-only
// Allocator used by the program. We use the c_allocator since we interact with C code
// via zig-wayland (and, in the future as of 2026-01-26, other libraries, too).
pub const allocator = std.heap.c_allocator;
// Allocator used by the program. We use the c_allocator since we interact with C code via our dependencies.
pub const gpa = std.heap.c_allocator;
pub const RiverColor = struct {
red: u32,
@ -79,8 +78,8 @@ pub fn parseModifiers(s: []const u8) !?river.SeatV1.Modifiers {
if (part.len < 3 or part.len > 5) return null;
// Case-insensitive comparison by lowercasing
const lower = try std.ascii.allocLowerString(utils.allocator, part);
defer utils.allocator.free(lower);
const lower = try std.ascii.allocLowerString(utils.gpa, part);
defer utils.gpa.free(lower);
if (mem.eql(u8, lower, "none")) {
// No modifier bits to set
@ -108,10 +107,10 @@ pub fn tokenizeToOwnedSlices(input: []const u8, delimiter: u8) ![]const []const
var list: std.ArrayList([]const u8) = .empty;
var it = std.mem.tokenizeScalar(u8, input, delimiter);
while (it.next()) |part| {
const duped = try allocator.dupe(u8, part);
try list.append(utils.allocator, duped);
const duped = try gpa.dupe(u8, part);
try list.append(utils.gpa, duped);
}
return list.toOwnedSlice(utils.allocator);
return list.toOwnedSlice(utils.gpa);
}
pub fn stripQuotes(s: []const u8) []const u8 {