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.