Comment by ChadNauseam

3 days ago

> Passing a reference is not the same thing as passing the actual value, so this does not contradict affinity.

I agree that passing a reference is not the same thing as passing the actual value. If it were, there would really be no point to references. However, it does contradict affinity. Specifically, the fact that multiple references can be created from the same value, combined with the properties of references, contradicts affinity.

> At its core, "affine" means that the type system has exchange and weakening but not contraction, and that exactly characterizes Rust's type system.

Well, the rust type system certainly does support contraction, as I can use a reference multiple times. So what is that if not contraction? It seems like rust at least does support contraction for references.

But in practice, having absolutely no contraction is not a very useful definition of affine, because no practical programming language would ever satisfy it. It prohibits too much and the language would not even be turing complete. Instead, there is usually an "affine world" and an "exponential world". (Exponential meaning "unrestricted" values that you can do whatever you want with). And the convention is that values can go from the exponential world to the affine world, but not back. So a function taking an affine value can be passed any value, but must use in in an affine way, and meanwhile but a function taking an exponential (unrestricted) value can only be passed exponential and not an affine value.

If you don't believe me, you can try using linear haskell, and notice that a function taking a linear argument can be passed a non-linear argument, but not the other way around.

If you interpret Rust's type system this way, it's natural to interpret references as exponentials. But references have the opposite convention. You can go from owned values to references, but not the other way around, which is precisely the opposite situation as the convention around linear/affine type systems. Because these systems feel very different to use and enforce very different properties, I do think it's important that we have separate names for them rather than referring to both as "affine". And the usual name for the rust-like system is "uniqueness types", see https://docs.idris-lang.org/en/latest/reference/uniqueness-t... or https://en.wikipedia.org/wiki/Uniqueness_type .

The paper "Linearity and Uniqueness: an entente cordiale" by Marshall,Vollmer,Orchard offers a good discussion and explanation of the "opposite convention" you describe.

There is a dual nature of linearity and uniqueness, and it only arises when there are expressions that are not linear/not unique. At the same time, they have a lot in common, so we do not have a situation that warrants separate names. It is even possible to combine both in the same type system, as the authors demonstrate.

Taken from the paper:

"Linearity and uniqueness behave dually with respect to composition, but identically with respect to structural rules, i.e., their internal plumbing."

> Well, the rust type system certainly does support contraction, as I can use a reference multiple times. So what is that if not contraction? It seems like rust at least does support contraction for references.

Good question! For shared references, the answer is that they are `Copy`, so they indeed have contraction. Affinity just means that contraction is not a universal property, but some types/propositions may still have contraction. For mutable references, you can't actually use them multiple times. However, there is a desugaring phase going on before affinity gets checked, so uses of mutable references `r` get replaced by `&mut *r` everywhere. That's not using contraction, it's not literally passing `r` somewhere, it is calling a particular (and interesting) operating on `r` ("reborrowing").

Rust is not just an affine system, it is an affine system extended with borrowing. But I think it is still entirely fair to call it an affine system, for the simple fact that the language will prevent you from "using" a variable twice. "reborrowing" is just not a case of "using", it is its own special case with its own rules.

> But in practice, having absolutely no contraction is not a very useful definition of affine,

Obviously Rust has a class of "duplicable" types, called `Copy`. That's besides the point though.

> If you interpret Rust's type system this way, it's natural to interpret references as exponentials.

Why would that be natural? Mutable references are not even duplicable, so what you say makes little sense for references in general. Maybe you mean shared references -- those are just an example of a duplicable type.

Rust doesn't have a modality in its type system that would make every type duplicable, so there is no equivalent to exponentials. (In particular, `&T` isn't a modality around `T`. It's a different type, with a different representation. And as you noted, even if it were a modality, it wouldn't correspond to exponentials.)

But a type system can be affine/linear without having exponentials so I don't understand the point of this remark.

Uniqueness types seem to be all about how many references there are to a value. You can use linear/affine types to enforce such a uniqueness property (and that is indeed what Rust does), but that doesn't take away from the fact that you have a linear/affine type system.

> Because these systems feel very different to use and enforce very different properties,

I can't talk about the "feel" as I never programmed in an affine language (other than Rust ;), but in terms of the properties, what Rust does is extremely closely related to affine logics: the core property being enforced is that things do not get duplicated. My model of Rust, RustBelt, uses an affine separation logic to encode the properties of the Rust type system, and there's a lot of overlap between separation logic and linear logic. So we have further strong evidence here that it makes perfect sense to call Rust an affine language.

  • The main point of Affine logic is that it doesn't allow contraction, and the Rust type system does allow different forms of contraction. How exactly is Rust an "affine language"?

    Also, the claims about Curry-Howard correspondence are wrong. It does not prove that rust is an affine language: https://liamoc.net/forest/loc-000S/index.xml

    But Swift DOES have affine types with the "Non copyable" types that doesn't allow contraction.

    • > The main point of Affine logic is that it doesn't allow contraction, and the Rust type system does allow different forms of contraction. How exactly is Rust an "affine language"?

      The point of Affine logic is that it doesn't allow universal, unconstrained contraction, not that you can never do an operation that has the same properties that contraction would have in some circumstances. The same is true of Rust's type system.

    • Rust has types that don't allow contraction, too: e.g., String, vectors and boxes.

      Their being that way is essential for the borrow checker to provide the memory-safety guarantees it provides.

      2 replies →

    • An affine type system is one in which some things don't have contraction, not one in which nothing has contraction.

    • I brought up Curry-Howard to explain why I am using an SO post about "affine logic" to make an argument about the definition of "affine language". Both are defined the same way: no (universal) contraction. That claim is obviously correct, so you are going to have to be a more more concrete about which claim you disagree with.

      (The other part you said about contraction and affine logics has already been successfully rebutted in some other replies so I won't repeat their points.)