kota's memex
use fmt;

type coords = struct {
	x: int,
	y: int,
};

export fn main() void = {
	let pos = coords { x = 10, y = 20 };
	printcoords(null);
	printcoords(&pos);
};

fn printcoords(pos: nullable *coords) void = {
	match (pos) {
	case null =>
		fmt::println("(null)")!;
	case let pos: *coords =>
		fmt::printfln("({}, {})", pos.x, pos.y)!;
	};
};

nullable

Pointers in hare, by default, cannot be "null". In other words, all pointers must refer to a valid address. However, it is often useful to signal the absence of a value, and we can do this with a nullable pointer type.

The "printcoords" function in the sample code accepts an argument of type nullable *coords. We cannot dereference this type with * or . operators like we ordinarily might: we must first test if it is valid. One way to do this is with a match against null, as shown.

auto-dereferencing

Hare also includes a feature called "auto-dereferencing", which allows you to do things like using the . operator to access struct members via a pointer. This allows us to use pos.x instead of (*pos).x. Many other features work similarly, indexing arrays, append, etc. This works for any level of indirections: pointers to pointers to pointers and so on.

sub-typing

Pointers to structs enjoy a special feature in hare: automatic sub-typing. Essentually, a pointer to one type can be automatically cast to a pointer of another type so long as the second type appears at the beginning of the first type. This is used to implement abstract interfaces!

use fmt;
use io;
use os;

type limitstream = struct {
	stream: io::stream,
	sink: io::handle,
	limit: size,
};

const limit_vtable: io::vtable = io::vtable {
	writer = &limit_write,
	...
};

fn limitwriter(sink: io::handle, limit: size) limitstream = {
	return limitstream {
		stream = &limit_vtable,
		sink = sink,
		limit = limit,
	};
};

fn limit_write(st: *io::stream, buf: const []u8) (size | io::error) = {
	const st = st: *limitstream;
	const buf = if (len(buf) > st.limit) {
		yield buf[..st.limit];
	} else {
		yield buf;
	};
	st.limit -= len(buf);
	return io::write(st.sink, buf);
};

export fn main() void = {
	const limit = limitwriter(os::stdout, 5);
	fmt::fprintln(&limit, "Hello, world!")!;
};

This program takes advantage of this by implementing a custom io::stream that limits the total amount of data which can be written, essentially demonstrating a slimmed down version of the standard libraries built-in io::limitwriter. Running this program only writes the first 5 bytes to stdout: "Hello".

The pointer to &limit passed to the fmt::fprintln call is of type *limitstream, which embeds the io::stream type as the first field. Thus, we can safely pas it to a function expecting an *io::stream pointer, such as fmt::fprintln. In the limit_write function, we cast this pointer back to *limitstream so that we can access additional data we need to store to implement the limit writer functionality.