Stack allocation & pass by reference
use crypto::sha256;
use encoding::hex;
use hash;
use io;
use os;
use fmt;
export fn main() void = {
// Pointer basics
let i = 10;
fmt::println(i)!;
increment(&i);
fmt::println(i)!;
// Applied usage
const hash = sha256::sha256();
const file = os::open("main.ha")!;
io::copy(&hash, file)!;
let sum: [sha256::SIZE]u8 = [0...];
hash::sum(&hash, sum);
hex::encode(os::stdout, sum)!;
fmt::println()!;
};
fn increment(ptr: *int) void = {
*ptr = *ptr + 1;
};
This sample demonstrates the use of pointers and stack allocation, the latter of
which is an important design pattern in hare. First it passes a copy of the i
variable to the "increment" function by reference, allowing the increment
function to modify i
outside of main
by using the *
operator.
When you call a function in hare (or when the runtime calls main
for you), a
small block of memory called a stack frame is allocated to store all of the
function's variables and parameters. This is automatically cleaned up when you
return from the function, which makes it useful for getting rid of resources
when you're done using them. However, it's important not to allow a reference to
any stack-allocated variables to persist after the function ends. Additionally,
there is a limited amount of stack space (around 2-8Mb usually), so it's often
wise to seek alternative strategies when allocating large objects.
This sample computes and prints it's own source code's sha256 hash. A common
pattern in hare is for a constructor like sha256::sha256
to return an object
on the stack, which you can then pass into other functions by reference using
&
. This is convenient for many objects which can be cleaned up by simply
discarding their state on the stack, but other kinds of objects (such as file
handles) require additional steps.
Dynamic memory allocation & defer
use fmt;
use io;
use os;
export fn main() void = {
// Allocation basics
let x: *int = alloc(42);
fmt::printfln(" x: {}", x)!;
fmt::printfln("*x: {}", *x)!;
free(x);
// Applied example
const file = os::open(os::args[1])!;
defer io::close(file)!;
// XXX: There is a known bug here:
// https://todo.sr.ht/~sircmpwn/hare/657
let buffer: *[65535]u8 = alloc([0...]);
defer free(buffer);
const n = io::read(file, buffer)! as size;
io::write(os::stdout, buffer[..n])!;
};
Another allocation strategy in hare is heap or dynamic allocation of resources. A simple sample program is provided here, which opens the file referred to by its first command line argument, reads up to 64 KiB from it, and writes it to the standard output.
To allocate an object on the heap, use the alloc
keyword along with an
initializer in parenthesis. The runtime will request the necessary memory from
the operating system, initialize it to the value provided here, and return a
pointer to it's value. The first "fmt" call in this example prints the location
(or address) of the allocated memory, the second call prints the value which was
placed there.
Unlike stack-allocated resources, which clean themselves up when the function
exits, heap-allocated resources must be "freed" by the caller using the free
keyword. Another concept shown here is the use of defer
to defer the execution
of an expression to the end of the current scope (code bounded by {
}
). This
allows writing the code that cleans up an object right next to the code that
creates it.
Static memory allocation
The third major approach to allocation in hare is static allocation. This approach involves creating a single object (singleton) for the whole program to use. This never has to be cleaned up because it never goes away. The most apparent drawback of this approach is that you have to allocate all static resources in advance, when you write your program, and cannot allocate more or fewer static resources at runtime as needs demand them. This can increase the footprint of your program on RAM (and in some cases on disk), and there are special constraints imposed on the initializers for static variables.
There are two ways to use static allocation in Hare: static locals and static globals.
use fmt;
use io;
use os;
let items: [4]int = [1, 3, 3, 7];
export fn main() void = {
// Example A: Static globals
printitems();
items[3] = 1;
items[0] = 7;
printitems();
// Example B: Static locals
fmt::println(increment())!;
fmt::println(increment())!;
fmt::println(increment())!;
// Example C: Applied static allocation
const file = os::open(os::args[1])!;
static let buffer: [65535]u8 = [0...];
const n = io::read(file, buffer)! as size;
io::write(os::stdout, buffer[..n])!;
};
fn printitems() void = {
fmt::println(items[0], items[1], items[2], items[3])!;
};
fn increment() int = {
static let x: int = 41;
x += 1;
return x;
};