In SimplicityHL, all objects are currently passed to functions “by value” and by copying the value. (Under the hood, we use the pair “parallel composition” combinator which does exactly this, copies a value into the input of two expressions.)
However, for smart contracts it’s very important that API authors be able to design session types and for this we need the ability for functions/expressions to “consume” variables. For example, in the prototypical “raw hashing” code
let ctx1: Ctx8 = jet::sha_256_ctx_8_init();
let ctx2: Ctx8 = jet::sha_256_ctx_8_add_2(ctx1, 0x5120);
let ctx3: Ctx8 = jet::sha_256_ctx_8_add_32(ctx2, tweaked_key);
let hash: u256 = jet::sha_256_ctx_8_finalize(ctx3);
We have three ctx* variables, all of which are live. If you replace the final ctx3 with ctx2 say, you will skip hashing tweaked_key and it will be pretty hard for a reviewer to notice this. (Like in Rust, you can name all of these ctx, with each one shadowing the previous ones, which will help in this particular case, but this isn’t particularly general and can be a footgun.)
It would be better if the sha256 jets (or some HL wrapper around them) were to consume their sha256 context objects, and if the user actually intends to “fork” the context they should have to write some sort of explicit copy keyword.
In Rust, this behavior is mediated by the Copy trait and the borrowing system, which lets you avoid copying by passing “shared” or “unique” references. Functions and expressions declare (via their type signature) whether they expect a value, a shared reference, or a unique reference. Then the passed type gets to choose, via Copy, whether “a value” means that the function “moves” (invalidates the original binding) the object or not. The result is pretty natural-feeling, mainly because Rust has references, and the “borrowing rules” are designed to match sensible rules to prevent pointer invalidation.
Rust lacks “linear types”, meaning types that are not Copy (can only be used once) and also must be consumed (must be used once). This prevents lots of natural constructions and leads to weird hacks like the #[must_use] lint, but is hard to fit into the language since it has infinite loops and memory leaks and stuff.
HL does not have references, nor do we want to introduce a borrow checker (anytime soon). We do not have infinite loops or memory leaks. So I think we can introduce linear and affine types without too much trouble. The questions become:
- Should types or function signatures (or somehow both) be responsible for deciding whether a passed value is passed “by move” or “by copy”?
- For linear types which must be consumed, how do users implement the “final” consumption? (In Rust it’s been proposed to use destructuring syntax, so if you have a linear type
struct MyType { inner: u32 }then you can only get rid of it by writinglet MyType { inner: _ } = mytype. This has the nice benefit that it reuses the privacy system to determine who is allowed to destructure the type. Specifically, anyone who can name all fields can destructure. (Though Rust has special..syntax that lets you destructure without naming fields, which interacts poorly with these proposals.) - What escape hatches should exist?
I would propose:
- Types by default are linear. To make them affine (users are not required to consume them) you need to annotate it with some sort of “autodrop” annotation
- Types can be annotated to enable explicit copying (similar to Rust
Clone) or implicit copying (similar to RustCopy). - Linear types are consumed by destructuring.
- Functions can “opt in” to consuming their arguments, so if you pass them an object that would otherwise be
Copyit will get consumed anyway.
Then the SHA256 context, for example, would be “linear” and also support explicit copies. So you can fork the context all you want, but every context eventually would need to go into a finalize call to get destructured. (And I guess we should also add a drop method which just consumes the context without doing any hashing.)
I don’t have any strong opinions about what these annotations would look like.