Below is my understanding of affine types and how they help us in Rust (unsure if I got this right - please correct if I am talking nonsense).
The issue is... How can we use the power of a type system so a compiler will block us from doing this wrong sequence of calls?
FILE *fp = NULL;
fclose(fp);
fp = fopen("...");
An idea is to "mirror" the states that the file variable goes through (unused/closed, opened) in the type system:
class FileUnusedOrClosed {
FileOpened open(...);
}
class FileOpened {
FileUnusedOrClosed close();
}
FileUnusedOrClosed f;
...
FileOpened o = f.open("...");
f = o.close();
If we try to do it in the wrong sequence, the non-affine-types kind of languages (C++, Java, etC) will catch us: open
only exists in FileUnusedOrClosed
, and close
only exists in FileOpened
.
BUT - they won't catch this:
FileOpened o = f.open("...");
FileOpened q = f.open("...");
This is where affine types help - Highlander style: THERE CAN BE ONLY ONE OWNER.
After the assignment to o
, f
becomes "invalidated" - the compiler knows that you can't reuse it, so the second line is caught... at compile time.
This pattern applies to many state machines (allocating/releasing memory, open/closing files-sockets, etc).
Basically, whenever methods (like open
above) must automatically move you to a new state, this pattern applies.
UPDATE: A nice comment from /r/glaebhoerl: This becomes even clearer in this "use after free":
void example() {
FileUnusedOrClosed f;
FileOpened o = f.open("...");
f = o.close();
o.write("oops");
}
Can your language catch that last invalid write - at compile-time? The call to o.close()
makes o
lose ownership, so the compiler will tell you that o.write
is not allowed.
Discussion in Reddit/Rust:
https://www.reddit.com/r/rust/comments/4mdgux/rust_and_affine_types_did_i_get_it_right/