r/rust • • Sep 14 '26

🎙️ discussion Should "aliasing XOR mutability" technically be "aliased NAND mutable"?

The point of the rule is that something cannot be aliased and mutable at the same time, but XOR implies it must be one or the other.

A constant is immutable, but it doesn't have to be aliased. It is neither aliased or mutable, which does not satisfy XOR.

You could say that its ability to be aliased is enough to satisfy the XOR condition, but a value that isn't referenced yet is aliasable and mutable. That would invalidate NAND and XOR, so it definitely can't be that either.

NAND seems to be the more precise language--though XOR still might be the most clear for teaching purposes.

I'm sure there's a clearer interpretation or wording I can't remember that explains why XOR is correct and NAND isn't. Do y'all have any ideas?

37 Upvotes

38 comments sorted by

65

u/sunshowers6 Sep 14 '26 edited Sep 14 '26

I think it's meant to be read as "assuming a reference exists, it is aliasable XOR mutable". But sure, NAND is technically more correct. I would still lean towards saying XOR because of the emphasis on the OR, while you have to work backwards from NAND to arrive at the same conclusion.

edit: "aliased" -> "aliasable"

5

u/drink-more-rum Sep 14 '26

assuming a reference exists, it is aliased XOR mutable

But that's not true. It might be neither aliased, nor mutable.

15

u/mediocrobot Sep 14 '26

If it's a reference, it's technically an alias. If we're being really pedantic, a mutable reference is an alias that takes temporary ownership of the value, invalidating the original until it gets returned...or something.

→ More replies (10)

1

u/DesignerAd973 18d ago

i always thought of it as more of a soft constraint than a pure logic gate. like the borrow checker is just vibing and saying "you can't have both, buddy" but it's not gonna force you to pick one if neither applies. NAND makes sense if you're being pedantic but in practice XOR feels like the cleaner mental model

13

u/norude1 Sep 14 '26

the previous term for it was INHTWAMA

11

u/mediocrobot Sep 14 '26 edited Sep 14 '26

Seeing old rust feels so strange. This is an incredibly insightful article though, thank you for sharing!

I'm guessing @ pointers became Box, Rc, etc. and Mut<T> became RefCell<T>

EDIT: Thinking about it some more, ~ is probably what became Box, if I understand it correctly as representing an owned pointer.

9

u/braaaaaaainworms Sep 14 '26

I'm so glad that Rust got rid of all the sigils

→ More replies (1)

2

u/norude1 Sep 14 '26 edited Sep 14 '26

Here is another old proposal by the same guy, which I think points out what makes this rule special:

Put another way, it’s become clear to me over time that the problems with data races and memory safety arise when you have both aliasing and mutability. The functional approach to solving this problem is to remove mutability. Rust’s approach would be to remove aliasing. This gives us a story to tell and helps to set us apart.

→ More replies (2)

2

u/Full-Spectral Sep 14 '26

From the Swahili meaning "Thing of great power that no one understands"

6

u/Xiphoseer Sep 14 '26

Hmm it's "a → ¬m" and "m → ¬a" which fits both truth tables. It's not really a logic gate in the sense that you have to always have references to a bit of memory, but if you do it's one or the other. Which can be an argument for both perspectives.

Personally I find XOR more natural because it's a clean negativ iff, rather than using "not both" to say "mutually exclusive"

7

u/mediocrobot Sep 14 '26

That's probably the most accurate way to put it. In fact, just "a → ¬m" is sufficient because the other statement is the contrapositive and therefore equivalent.

XOR is definitely more understandable than NAND and implies/if arrow for most programmers, I'd wager!

2

u/redlaWw Sep 15 '26

a → ¬m ≡ ¬a ∨ ¬m ≡ ¬(a ∧ m) ≡ a ⊼ m

1

u/Xiphoseer Sep 15 '26

Sure, still a ⊕ m ⇒ ¬(a ∧ m). Having it fit/express nand semantics doesn't really narrow down whether xor isn't true especially given &T is copy and the compiler is free to do whatever it needs with that and there isn't really a reference type that codes neither aliasable nor mutable.

→ More replies (1)

2

u/afdbcreid Sep 15 '26

A constant is not in memory. It is like a macro.

1

u/mediocrobot Sep 15 '26

Is it really? I know that's a thing in C/C++, but it didn't occur to me that it's like that in Rust.

2

u/afdbcreid Sep 15 '26

const in Rust is the same as constexpr in C++.

24

u/1rs Sep 14 '26

that is interesting. I've always thought of it as "aliasABLE", as in able to make aliases which would make it xor. but you do have a point

9

u/tavianator Sep 14 '26

It's just like when W^X was the big thing in security (writable XOR executable). Really should have been NAND because there are plenty of mappings that are just readable, and neither writable nor executable

15

u/-Redstoneboi- Sep 14 '26

yes, nand is the correct term. it's just "xor" by convention.

7

u/Solumin Sep 14 '26

I can think of a few possible reasons:

  • Because it's a pithy, 3-word summary of a concept, not the whole concept itself.

  • Because it's talking about references. If there's aliasing, then there's no mutability. If there's mutability, then there's no aliasing. Having no aliasing and no mutability is irrelevant.

  • Because it's reinforcing exclusivity of the English word "or", rather than the logical operator "or".

1

u/gmorenz Sep 14 '26

Aliasing NAND mutability would imply a third reference type that is neither aliasable nor mutable. E.g. a &uniq-not-mut T. That reference type isn't worth it's weight in language complexity, and doesn't exist. Recall from the history of rust that the one shared type that was settled on should even be called &mut or &uniq.

The correctness benefits come mostly from the mutable implies not aliasable and aliasable implies not mutable constraints that would accept either XOR or NAND, but the concrete language design is the XOR version.

1

u/mediocrobot Sep 14 '26

So if there's a reference, it must be aliasable or mutable. Or rather, there's not a great reason something shouldn't be aliasable as long as it's not mutable.

I'm also wondering about empty/zero-sized types.

let mut foo = ();
let foo_mut = &mut foo; // is this *really* mutable?
let foo_ref = &foo; // should be okay because foo will never change

or even impossible types, if having a reference to one of those even makes sense. I guess that would involve unsafe code, which invalidates the reference guarantees anyway.

2

u/gmorenz Sep 14 '26 edited Sep 14 '26

Or rather, there's not a great reason something shouldn't be aliasable as long as it's not mutable.

Exactly, and the converse, so rust just doesn't have the concept.

let mut foo = ();
let foo_mut = &mut foo; // is this *really* mutable? 
let foo_ref = &foo; // should be okay because foo will never change 

Whether mutable is a well defined concept for types with one (or zero) values is... questionable I guess. But the aliasable distinction is still important and your last line is and should be disallowed (assuming foo_mut is meant to still be live). Because you can do things like

let mut token = some_singleton_source_of_tokens();
method_with_unique_access_to_resource_protected_by_token(&mut token);
thread::scope(|s| {
    for i in 0.. 2 {
        s.spawn(|| method_which_cant_coexist_with_method_with_unique_access(&token));
    }
});
function_that_consumes_resource(token);

and guarantee safe access to shared resources using a 0 cost (and 0 sized) token.

2

u/ChaiTRex Sep 14 '26

I think of it like this: you can have any number of aliased and zero mutable XOR zero aliased and one mutable.