Hello, author here!
Closures are implemented with the environment parameter as an extra parameter on a function type. So internally a function `i32 - i32 -> i32` (wonky function type syntax currently with - separating arguments) which uses an environment of type String is represented as a pair of the function and its environment: `(i32 - i32 - String -> i32), String`. The same way C++ and Rust represent their closures.
Function arguments are passed by value currently, though I may explore pass by move and other options in the future.
I hope for ante to be usable without a heap, though the `ref` type automatically handling lifetimes makes this more difficult. These refs compile to an equivalent of destination-passing in C, but can require dynamic allocation if a function creates a ref and another function calls the first in a loop.
I also plan on having an Allocate effect for whenever a function can allocate, so that it is trivial to handle this with your own handle that can do anything. This would be an easier alternative to zig's approach of "pass the allocator everywhere manually," but there are still things I need to iron out, like how it interacts with the lifetime inference issues above, so its still in the design phase.