First version of the Extending Safe Mutability proposal. #78

Closed
wants to merge 2 commits into
from

9 participants

@matthieu-m

Here comes an improved version of the discussion started on reddit An alternative take on memory safety (mutability/aliasing).

I went down the rabbit hole, trying to dig up the fundamentals of memory safety at the lowest level available (ie, the C Standard level), hopefully, this work can always be reused later on in the event this RFC does not pan out.

I still feel that segregating mutability from aliasing is a worthy goal in and out of itself. For an extreme example, Concurrent Containers are generally shared and yet you might want to restrict mutation to only some references.

This proposal is thus about achieving safe mutability in the presence of aliasing:

  • with the mutability clearly documented in the type system
  • and opening the way to compiler-verified safely mutable types (and their methods)

This may be seen as adding significant complexity and it certainly adds complexity to the compiler. From a user point of view, however, the segregation makes both mutability and aliasing explicit and thus more easily searchable; with support from the compiler diagnostics (reporting all useful terms) it might actually end up being both more approachable and more flexible.

Finally, the appeal of immutability may be a sufficient reason in itself: it helps drawing all those users to which immutability has been taught as The Silver Bullet(tm) of programming.

@bgamari bgamari commented on an outdated diff May 16, 2014
active/0000-extending-safe-mutability.md
+
+In order to achieve safe mutability, we simply need to ensure that any mutation is safe; the type system must thus allow us to guarantee this.
+
+
+### 1. A focus on aliasing
+
+The simplest way to guarantee that all the remaining usable references in the system can be safely used after the mutation is to guarantee that none of them point to changed content.
+
+This proposal introduces the `exclusive` and `mutable` keyword:
+
+ - a reference qualified by `exclusive` is guaranteed to have no alias
+ - a reference qualified by `mutable` can be mutated
+
+*Note: why `mutable` and not `mut`? Just so we can easily identify snippets written in the current system from those written in the proposed system.*
+
+It is trivially provable that mutation through `&exclusive T` or `&exclusive mutable T` is safe, although we disable the former. The borrow-checker current implementation is sufficient to enforce the `exclusive` rule, and therefore we can easily convert today's code:
@bgamari
bgamari added a line comment May 16, 2014

Minor nit: "disallow" might be more clear than "disable" here. Also "borrow-check" should probably be "borrow-checker's"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@P1start P1start commented on an outdated diff May 16, 2014
active/0000-extending-safe-mutability.md
+```rust
+#![deriving(SafelyMutable)]
+struct A { i: u32, f: f64 }
+
+
+#![deriving(SafelyMutable)]
+struct B { i: u32, a: A, array: [A..5] }
+```
+
+This is simple enough, and there is no special restriction for the methods of those types.
+
+Since we use a trait system, we can use conditional implementation of `SafelyMutable`:
+
+```rust
+struct Cons<'b, T> {
+ priv value: T,
@P1start
P1start added a line comment May 16, 2014

struct fields are now private by default (and have been for some time), so priv is no longer a keyword. There are a number of other cases in this RFC where you’ve assumed public fields by default (e.g. line 232).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@huonw huonw commented on an outdated diff May 17, 2014
active/0000-extending-safe-mutability.md
@@ -0,0 +1,477 @@
+- Start Date: 2014-05-16
+- RFC PR #: (leave this empty)
+- Rust Issue #: (leave this empty)
+
+
+# Summary
+
+Introducing an orthogonal handling of mutability and aliasing, which will hopefully make code clearer to read and increase the scope of what can be written without dropping to unsafe code.
+
+
+# Motivation
+
+The current system of handling mutability is confusing and requires unsafe code and compiler hacks. It is confusing because types like `Cell` can be mutated through `&T` references as they are inherently mutable (an invisible property) and requires compiler hacks in the way closures are modelled.
@huonw
The Rust Programming Language member
huonw added a line comment May 17, 2014
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@huonw huonw commented on an outdated diff May 17, 2014
active/0000-extending-safe-mutability.md
+
+ - a built-in integral or floating point type is `SafelyMutable`
+ - a `struct` type declared `SafelyMutable` may only leak references to its `SafelyMutable` members
+ - an `enum` type cannot be declared `SafelyMutable`
+
+Of course, this trait would not be too useful without rules for its usage:
+
+> An instance of a `SafelyMutable` type `T` may be mutated through a `&mutable T` reference.
+
+This enables the following code:
+
+```rust
+fn main() {
+ let mutable x = Cell::new(5);
+ let mutable vec = vec!(&mutable x);
+ *x = Cell::new(4); // Mutable even though aliased!
@huonw
The Rust Programming Language member
huonw added a line comment May 17, 2014

Shouldn't this just be x = ...; without the dereference?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@huonw huonw and 1 other commented on an outdated diff May 17, 2014
active/0000-extending-safe-mutability.md
+
+ - [RFC #77: Unboxed Closures](https://github.com/rust-lang/rfcs/pull/77/) aims at solving the closure issue without modifying the current type system
+ - [Focusing on Ownership](http://smallcultfollowing.com/babysteps/blog/2014/05/13/focusing-on-ownership/) details how Rust would be better off focusing on aliasing than mutability
+ - [RFC #58: Rename &mut to &only](https://github.com/rust-lang/rfcs/pull/58) also argues that aliasing is the better focus
+
+There has, however, been an uproar of part of the community, the message was:
+
+ - mutability is an important concept, even if memory-safety can be enforced without it
+ - inherently mutable types (such as `Cell`) are confusing because even though `mut` is about inherited mutability it is viewed as a marker of the actual mutability of the object, causing a paper cut
+
+On the other hand, this proposal has a complexity overhead. Still, I do believe that the resulting code is clearer as we stop conflating mutability and aliasing, and it also opens up interesting avenues (such as `Vec::push_no_alloc`).
+
+
+# Unresolved questions
+
+* The exact interactions with concurrent/parallel programming are still unclear. It should be possible for a type such as `Mutex` to be both `SafelyMutable` and `Share`, for example, however this flies way above my head...
@huonw
The Rust Programming Language member
huonw added a line comment May 17, 2014

I don't think this is actually possible. Imagine you have two threads, each with an &mutable to the same Mutex. Being SafelyMutable would allow one thread to assign a new mutex value (right?), e.g. in pseudo-Rust.

// this probably should be passed via an `Arc` or some-such
let m: Mutex<T> = ...;

{
// thread 1
    let x: &mutable Mutex<T> = &mutable m;
    *x = Mutex::new(...);

    // `guard` provides `&exclusive` access to the `T`
    let guard = x.lock();
}
{
// thread 2
    let y: &mutable Mutex<T> = &mutable m;
    let guard = y.lock();
}

However, assigning the new mutex is done without any synchronisation, i.e. there is now the possibility of data races. E.g. imagine thread 2 trying to lock the mutex halfway through 1's reassignment: 2 might think it locked it, but then that information was overwritten by 1, leaving the mutex still unlocked, so another thread (1) can lock it, resulting in both threads thinking they have exclusive access.

@matthieu-m
matthieu-m added a line comment May 17, 2014

Indeed, assignment would be an extremely bad idea. I thus suppose that Mutex should not be SafelyMutable but could still use the mutable indicator to indicate whether it's allowed to mutate its inner value (or not).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@thestinger

I have a much simpler transitive mutability system to propose, although I don't really want the current system to change. The important thing to take into account is that mutability and aliasing are not orthogonal concepts. The useful definition of alias used by C and LLVM refers to a memory dependency. This means two pointers alias if one can observe a write through the other. If the pointers are both read-only, they do not alias. If they're guaranteed to point to immutability memory, they alias no other pointer.

There are 3 kinds of mutability in Rust:

  • external (observed) mutability (&mut)
  • internal (inherent) mutability (Cell)
  • inherited mutability

The mut keyword only refers to inherited mutability and the other two are always permitted. The proposal to introduce a third &uniq reference type simply takes &T and allows external mutability by borrowing the unique ownership of the value. Both would continue forbid inherited mutability and permit internal mutability. The only use case is avoiding some mut markers on locals, but with by-value captures for closures I think this will be so rare that it would be silly to special-case it.

In a system where Rust had no mutability markers for local variables (which is entirely a check of the programmer's intuition), it would only have the &T and &mut T pointer types. This was the alternative proposal by @nikomatsakis, although it was mixed with a renaming of the type. There would be no difference between &uniq T and the current &mut T type without immutable locals, so lets leave the naming issue aside for now.

If you want transitive immutability, then you don't want any changes to aliasing. The system you want is a distinction between three types of mutability:

  • inherited, external and internal mutability
  • external and internal mutability
  • transitive immutability

A fully backwards compatible example is the following:

  • &, x (internal mutability for both and external mutability for variables)
  • &mut, mut x (inherited, internal and external mutability)
  • &imm, imm x (transitive immutability) along with a reintroduction of Freeze

There are no real changes required to the type system or standard libraries to support this. The compiler is already fully aware of inherited (mut), internal (Unsafe<T>) and external mutability (&mut T). The feature would do nothing more than prevent coercion from &T to &imm T or &imm borrows when the type contains Unsafe<T> or &mut T. The Freeze trait would indicate a lack of internal Unsafe<T> and &mut T for use as a bound. I don't really see the value prospect, but that's all you need to extend inherited mutability to transitive immutability.

@thestinger thestinger referenced this pull request May 17, 2014
Closed

Rename &mut to &only #58

@kballard

I agree 100% with @thestinger. I don't think the current system should change, but if it should, adding &imm seems to be the only approach that actually does what all these various proposals are trying to do (which is, to provide a way to ensure deep immutability). I'd rather not have it, though, as I believe it's an unnecessary complication.

@kballard kballard and 1 other commented on an outdated diff May 17, 2014
active/0000-extending-safe-mutability.md
+
+# Detailed design
+
+*Disclaimer: this proposal introduces several names, they are purposely overly verbose and they are stand-in to facilitate the discussion. If this proposal is ever accepted in whole or in parts, then we can have a grand bike-shed.*
+
+
+## Interlude: Safe Mutation
+
+The heart of the issue is achieving safe mutation. However, what exactly is *safe mutation* is unclear. Meeting an unclear goal is difficult, thus we will first define our goal.
+
+
+### 1. A structural model for types
+
+Let us define a model to talk about how types are laid out in memory.
+
+For interoperability reasons, Rust lays its `struct`s out like C would. We will take the assumption that its `enum`s and arrays are laid out similarly.
@kballard
kballard added a line comment May 17, 2014

I don't believe we make any guarantees about how enums are laid out. There's no FFI compatibility issue there (as our enum types don't exist in C). The only guarantee we make is for C-like enums, where we guarantee that the enum is represented by an integral value with the given discriminant (and the integral type can be controlled with the #[repr] attribute).

@matthieu-m
matthieu-m added a line comment May 17, 2014

It might be unclear. The point I am trying to make is that much like struct, enum are (probably) laid out without concern for their names but only for their "shape". It might not hold for Option<~T> though, which is special cased (and I don't know if this special casing is done by name or shape).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@kballard kballard and 1 other commented on an outdated diff May 17, 2014
active/0000-extending-safe-mutability.md
+and additionally `bN` is *layout-substitutable* for `aN`.
+
+Regarding memory safety:
+
+ - if `A` and `B` are *layout-identical*, then using an instance of `B` through a `&A` is safe, and vice-versa (the relation is symmetric)
+ - if `B` is *layout-substitutable* for `A`, then using an instance of `B` through a `&A` is safe (the relation is asymmetric)
+
+
+### 3. Defining safe mutation
+
+Armed with the previous definition, we can define safe mutation.
+
+> A mutation is *safe* if after its execution, for any remaining *usable* reference `ri` of static type `&Ai`, `ri` either points to:
+
+> - an instance of type `Ai` itself
+> - an instance of type `Bi` that is *layout-substitutable* for `Ai`
@kballard
kballard added a line comment May 17, 2014

I do not believe this is correct. It sounds likely to be correct for the limited case of reading memory through this pointer. But it does not hold for calling methods, including calling Drop glue when mutating the value through this pointer.

The reason being, method calls through this pointer use the static type of the pointee to resolve the monomorphized function to invoke (this includes calling Drop glue). But you've just defined mutation here as allowing a &Ai pointer to actually point to a Bi instance, which is a distinct type. Calling methods based on the Ai type but with a Bi instance is obviously incorrect.

Now as to whether this can trigger a segfault without going to unsafe code at some point, I don't know. But this definition of mutability makes it impossible to wrap unsafe in a safe API, because that safe API relies on the type system accurately representing the types involved. As a trivial example, this definition means that Vec<u8> and Vec<StrBuf> are considered layout-identical (you're not actually precise here, but I'm assuming *T and *U are layout-identical, because they're both just raw pointers; since you're following the C model here I'm pretty sure C considers them layout-identical). Which means that if I can use mutation to replace a Vec<u8> with a Vec<StrBuf>, my existing &Vec<u8> reference matches your rules, and yet I'll crash horribly if I actually try and do anything with it.

@matthieu-m
matthieu-m added a line comment May 17, 2014

Actually, *T and *U are references to arrays, and thus *U is layout-substitutable for *T if [U] is layout-substitutable for [T], which means that: U is layout-identical with T and the instance of [U] is longer than the instance of [T].

But you do touch a point here that I raise later on in the Opened Doors section: while memory-wise the substitution is possible; there is an issue with methods (of any kind). Rust uses nominal-typing, not structural-typing, and thus changing the type (name) changes the interpretation of memory with possible drastic effects.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@kballard kballard and 1 other commented on an outdated diff May 17, 2014
active/0000-extending-safe-mutability.md
+
+This enables the following code:
+
+```rust
+fn main() {
+ let mutable x = Cell::new(5);
+ let mutable vec = vec!(&mutable x);
+ *x = Cell::new(4); // Mutable even though aliased!
+}
+```
+
+> Furthermore, within a method of a `SafelyMutable` type `T`:
+> - if the method is of `&self` kind, then references to non-`SafelyMutable` members are promoted to `&exclusive self`
+> - if the method is of `&mutable self` kind, then references to non-`SafelyMutable` members are promoted to `&exclusive mutable self`
+
+Only references to `SafelyMutable` members may have leaked to the exterior, thus it is safe to consider that the other members are effectively non-aliased. And even though the `SafelyMutable` members are aliased, they can still be modified through `&mutable self` references.
@kballard
kballard added a line comment May 17, 2014

This is not true. It is not safe to promote non-SafelyMutable members to &exclusive self in a &self method, or to &exclusive mutable self in a &mutable self method.

The reason is that these members are still aliased even though you haven't exposed references to them to the outside world. The reason is that, as @thestinger said, two pointers are aliased if writes made through one of them can be observed through the other. So if I have two &mutable references to a SafelyMutable type, and I call a method on one that mutates a non-SafelyMutable field, I can observe that write through the other &mutable reference. It doesn't matter that the field itself isn't exposed via a reference, because it's visible within methods that I call on my &mutable reference.

@matthieu-m
matthieu-m added a line comment May 17, 2014

This is actually the point (otherwise Cell would be useless). However the problem is more drastic, I still have a hard time defining how those "leaks" should be checked, and there are horrid situations like injecting a closure with a reference on the object that will pattern-match an internal enum one hand and assign it on the other...

In the end, it seems that for arbitrary types to be contained within a SafelyMutable little can be done safely except assignment. I am thinking of splitting the section between POD (for which it's always safe) and moving the idea of generalizing it to non-POD to the Opened Doors section.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@kballard

You didn't make it entirely clear as to whether you can take a &mutable reference to any type, or just to types of SafelyMutable (you defined SafelyMutable types as being mutable through &mutable references, but didn't say whether the reference itself was legal for non-SafelyMutable types). I assume it has to be legal to take a &mutable reference to any type, as that's the only way that type can mutate any contained SafelyMutable fields.

In any case, your proposal here makes it impossible to implement Rc. Specifically, Rc needs to mutate its contained Cells from the clone() method, but clone() is defined by a trait and must take &self. It cannot take &mutable self in the trait as it needs to be callable on non-mutable values. But if it takes &self then Rc can't update its reference count.

I don't see any solution to this. The only thing that makes Rc work is the ability for Cell to be mutated via a & reference. Even if you try and inline the behavior of Cell into Rc, it has the same problem (where mutation must be able to happen from within a &self method).

@kballard kballard commented on an outdated diff May 17, 2014
active/0000-extending-safe-mutability.md
+
+ /// Sets the contained value.
+ #[inline]
+ pub fn set(&mutable self, v: T) {
+ self.value = v;
+ }
+}
+```
+
+The key differences from the current implementation are:
+
+ - `value` is no longer `Unsafe<T>`
+ - no `unsafe` code in `get` and `set`
+ - `set` now requires a `&mutable self` parameter
+
+*Note: `noshare` should be unnecessary now, because the aliasing of this type is tracked properly.*
@kballard
kballard added a line comment May 17, 2014

noshare cannot be inferred, because that would prevent Mutex from being written. Mutex needs to be Share, but it also needs to be SafelyMutable. It's safe to be Share because the implementation of Mutex is designed to enforce this safety.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@kballard

If this proposal were implemented, I could still write a Cell-like type that allows for mutation through & pointers. I think it's possible with TLS (if you're willing to accept that the Cell is non-Send), and I think it's also possible using a static mut (using a thread-safe data structure to actually contain the values). The Cell in this case would only contain an immutable key.

@matthieu-m

The proposal does not seek to eliminate all uses of internal mutability (such as Rc), but instead to introduce mutable to document (in the type signature) visible mutability such as that of Cell.

It then goes on to try and define helpful rules to diminish the use of unsafe code, which is a somewhat orthogonal goal, but I seriously doubt that it will ever be possible to remove all instances of unsafe and therefore we need to draw the line... somewhere.

I think that as long as visible mutability is clearly documented, we have won something. If it takes unsafe to implement methods on &mutable self, so be it.

@matthieu-m matthieu-m Trim down the Extending Safe Mutability proposal.
There were holes and distractions in the previous iteration, however perfection is unnecessary so let's just aim for a step forward instead. Besides, the language will continue to evolve, and it will be easier to add it than to remove from it.

Notable changes:
- removed the distraction on layouts, Rust is nominally typed so let us focus on nominal typing.
- removed the vague "do not leak references", it is much too easy to accidentally introduce holes here.
- removed the example of RefCell, and focus on POD and Cell for now.
- reorganized the sections a bit, with more examples.
- added some more unresolved questions to ponder.
b3f8f0b
@kballard

This still doesn't solve the Rc problem. The only way for Rc to work is if it uses unsafe to coerce a &self pointer to &mutable self within the clone() method. And as far as I can tell, the ability to do that contradicts the entire point of this proposal (which is to disallow "hidden" mutability).

@matthieu-m

The point of the proposal is not actually to disable hidden mutable. Or not exactly.

The point of the proposal is to propose a framework for annotating operations that mutate user-visible state:

  • regardless of whether the reference is exclusive or not, which is an incident property
  • and with no regard for user-invisible state (such as the state of the actual mutex in Mutex or the reference count in Rc)
  • and with no intention of replacing fully unsafe contexts, though I do wish to make a dent here

With this proposal, MutexGuard would implement DerefExclusive (fn deref<'a>(&'a self) -> &'a exclusive T) and DerefExclusiveMut (fn deref<'a>(&'a mutable self) -> &'a exclusive mutable T). It always guarantee non-aliasing and the programmer may choose whether mutation of the internal object is allowed or not.

@kballard

If this proposal essentially allows opt-in mutability annotations, but doesn't actually prohibit internal mutability through a &self pointer, then it doesn't actually appear to solve any problem. I can already provide opt-in mutability annotations on methods by giving them more descriptive names. I thought the whole point was to require mutability annotations, because only then can you actually consider &T to be deeply immutable.

@P1start

With this proposal, would there be any downside to simply using &mutable T pointers anywhere where we’d use &T today? As far as I can tell, they only add functionality to &T (they can mutate SafelyMutable types) and don’t restrict any (they’re still aliasable). Presumably using &mutable pointers everywhere would also require making all variables mutable as well, so would it be possible to combine Niko’s proposal with this one, remove mutability and have 2 pointer types: &mutable T and &exclusive mutable T (obviously with different names; perhaps &T and &my T respectively)?

@kballard

@P1start That's functionally no different than just adopting Niko's proposal, because you've just gotten rid of &T and the only real purpose to this PR #78 is to establish the difference between &T and &mutable T (which of course requires &T to exist).

@P1start

@kballard How does that make it no different? It allows one to have multiple mutable pointers to the same location (if it’s SafelyMutable). The following code would not work under Niko’s proposal:

let x = 4;
let y = &x;
let z = &x;
*y = 5; // under Niko’s proposal this wouldn’t work
*z = 6; // … nor this

It also allows for a safe Cell implementation (which I don’t think Niko’s proposal does).

@zwarich

One correction to @thestinger's comment: aliasing in C and LLVM is not just about mutability.

If I have a fragment of LLVM IR with two loads from addresses with distinct TBAA tags, followed by a comparison of the two addresses, an optimization pass is allowed to optimize the comparison even though there is nothing involved with mutability. A more realistic example would be scalar replacement. If an alloca is potentially read from by a load that may read from multiple allocas depending on control flow, then that would block a scalar replacement pass from converting the alloca to an SSA variable (unless that pass is sufficiently advanced to rewrite the code to push the load up the control flow), and this has nothing to do with mutability. Another example is points-to analysis for devirtualization, which is nontrivial even without any mutation.

Aliasing is a dynamic property of variables and expressions in a language with a semantics based on memory locations. This dynamic property can be approximated with simple static analyses (e.g. comparing offsets on loads with a common base, looking for escaping pointers, etc.) and augmented with type systems. These static approximations of aliasing can then be used to create static approximations of dependence between memory operations, but aliasing is not defined in terms of memory dependence.

Aliasing is actually more important for safety of deallocation than safety of mutation. If code iterating over a fixed-size vector of integers has multiple mutable references into the vector, then no violation of memory safety can occur unless the vector's storage is deallocated. Of course, since Rust pervasively uses destructors, mutation often goes hand-in-hand with deallocation, and the removal of const means that POD and non-POD types now share the same rules, even though it is not required for memory safety.

@kballard

@P1start Ok, yes, I forgot that this proposal defined primitives as SafelyMutable. So you're right it's not exactly the same thing.

But I think that making primitives mutable like this is actually a bad thing. Today, LLVM knows that any &T pointer, where T is not Unsafe (and the vast majority of types are not Unsafe), does not alias with any mutable pointer. This allows it to optimize away redundant reads, or to reorder reads with respect to other write operations. But once T is a type that may mutate (which is to say, that's Unsafe today, or SafelyMutable with this proposal), it loses that optimization opportunity.

@zwarich

@kballard Today, LLVM does not know that &T does not alias with any mutable pointer. This property can not be expressed with LLVM's existing TBAA system, even disregarding Unsafe.

@kballard

@zwarich Really? Darn, I thought it did know that. That's rather unfortunate.

Still, there was talk about writing a custom LLVM pass to teach it about alias information that Rust has that it can't express to LLVM today. Presumably such a pass would then enable LLVM to make the optimizations I just described.

@P1start

@kballard Yes, I now see that it does seem somehow wrong to have almost no assertions about mutability anywhere. Although I’d probably personally find it easier to code with, all pointers being mutable seems like numerous optimisations could be made impossible, and just knowing that a reference or variable will not change can be useful when writing code.

@thestinger

@matthieu-m: The mutation performed during clone for Rc is essentially user-visible. It can cause the destructor (fn drop(&mut self) { println!("side effect") }) of the contained value to be run later.

@thestinger

@zwarich: That may apply to TBAA metadata, but I expect that we would be using a custom pass where NoAlias is documented as being entirely about memory dependency. The same set of rules is used for noalias parameters and return values, which we're already using.

@matthieu-m

@kballard: it's impossible to forbid internal mutability (via &self) seeing that unsafe code is allowed to do whatever it wants. This proposal, however, forbids mutability via &self outside of unsafe code. Also, contrary to more descriptive method names, the use of mutability is checked by the compiler.

@matthieu-m

@P1start: Indeed, we could merge this proposal with Niko's, and all that would be left would be the SafelyMutable trait which would allow mutating PODs (essentially) even when they are aliased.

@huonw
The Rust Programming Language member

it's impossible to forbid internal mutability (via &self) seeing that unsafe code is allowed to do whatever it wants. This proposal, however, forbids mutability via &self outside of unsafe code. Also, contrary to more descriptive method names, the use of mutability is checked by the compiler.

It's possible to forbid it by saying it is undefined behaviour, i.e. any unsafe code that does mutate data behind & is incorrect and the program won't necessarily behave as you expect. (This is approximately what we currently have: the only legal way to mutate data behind & is to use the Unsafe type, anything else is undefined behaviour.)

Unsafe code does have to uphold Rust's rules, the only reason unsafe exists is the compiler cannot enforce them itself in some contexts; using it is the programmer telling the compiler that she will keep track of all the invariants herself.

@matthieu-m

@kballard, @thestinger: you are both correct that we could use the fact that &T points to immutable storage to write further optimization passes within LLVM.

However, already today T must be analyzed in depth before claiming this; any Cell/RefCell/Mutex, ... within T must lead to disabling this potentially optimization. What this proposal offers is nothing more than another way (SafelyMutable) to disable this optimization; and with or without this proposal should people need the functionality they would manage to achieve it.

What might be controversial indeed is the idea that built-in types be SafelyMutable to start with. This could be changed easily, though. We could state that a struct may be declared SafelyMutable only if composed of SafelyMutable or built-in integers/floating point members; and that built-in integers/floating point members of a SafelyMutable struct behave as if they were SafelyMutable themselves, but only them. Or something to that effect, at least. This way, SafelyMutable would be truly opt-in, and most types would not inhibit optimization.

Would that seem better to you ?

@zwarich

@thestinger There are other aliasing rules in the LLVM LangRef, e.g. noalias pointers can't alias pointers that are not derived from them, global variables and allocas don't alias other memory, etc. You are correct in asserting that despite the name, LLVM's AliasAnalysis interface isn't really an alias analysis interface; it's a memory dependence interface. We would probably need a custom system of TBAA metadata on memory operations, a replacement for the BasicAlias pass, and some modifications to the inliner (to ensure that the must-not-alias constraints of a &mut inlined twice don't overlap). Hopefully we can ride on support for the existing TBAA metadata so that existing passes that copy that metadata will just copy ours, and the inliner will be the only existing pass we need to modify.

@matthieu-m The compiler doesn't need to analyze T in depth in order to determine whether &T aliases another mutable reference. Any load or store that violates this aliasing rule will have to occur behind an unsafe block, and the compiler will just have to mark those memory operations as potentially aliasing anything.

There is a bit of a problem with this approach: since transitive unsafety is quite pervasive (so many basic data structures are implemented in terms of unsafe), many operations will ultimately expand to an inlined unsafe block. At the moment, the compiler has to basically assume that unsafe code can do anything, a raw pointer used in an unsafe block has to be assumed to alias any other pointer. The only way to improve this situation would be to have some way to distinguish between type-unsafe and lifetime-unsafe. A lifetime-unsafe use of a T* would still be seen as only aliasing other pointers to T, whereas a type-unsafe use of T* would be seen as potentially aliasing any other pointer.

@huon What is gained by making modifications through &T in an unsafe block be undefined behavior?

@thestinger thestinger commented on the diff May 18, 2014
active/0000-extending-safe-mutability.md
+
+# Drawbacks
+
+By refining the type system, this RFC introduces 4 reference types where there were only 2 previously. It also requires introducing a new Trait.
+
+The benefits may not be worth the extra complexity overhead. It is hard to perform a costs/benefits analysis when everyone has a slightly different and vague idea of what the "system" being discussed is though, therefore here comes a "reasonable" system, in all its ugly details.
+
+
+# Detailed design
+
+*Disclaimer: this proposal introduces several names, they are purposely overly verbose as they are place-holders to facilitate the discussion. If this proposal is ever accepted in whole or in parts, then we can have a grand bike-shed.*
+
+
+## 1. Defining safe mutation
+
+Rust is nominally typed, and therefore it seems dangerous to allow a `&A` to point to an instance of anything else than a `A`. Not only methods could be called that would be unexpected, but it would possibly confuse the Type-Based Alias Analysis in LLVM.
@thestinger
thestinger added a line comment May 18, 2014

Rust doesn't really make any guarantees about this right now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@thestinger thestinger commented on the diff May 18, 2014
active/0000-extending-safe-mutability.md
+
+*Note: this definition is valid only when no concurrent thread executes, aliases between threads have to properly synchronize their writes and reads on top of respecting this definition.*
+
+
+## 2. A focus on aliasing
+
+The simplest way to guarantee that all the remaining usable references in the system can be safely used after the mutation is to guarantee that none of them point to changed content.
+
+This proposal introduces the `exclusive` and `mutable` keyword:
+
+ - a reference qualified by `exclusive` is guaranteed to have no alias
+ - a reference qualified by `mutable` allows mutation of the pointee
+
+*Note: why `mutable` and not `mut`? Just so we can easily identify snippets written in the current system from those written in the proposed system.*
+
+It is trivially provable that mutation through `&exclusive T` or `&exclusive mutable T` is safe, although we disallow the former. The borrow-check current implementation is sufficient to enforce the `exclusive` rule, and therefore we can easily convert today's code:
@thestinger
thestinger added a line comment May 18, 2014

The non-aliasing of &mut T is still theoretical:

fn main() {
    let mut x = 5;
    let y = &mut x;
    *y = 10;
    let z = x;
    println!("{} {}", z, *y);
}

The aliasing rules are just a way to get safe mutability. This specific case is going to change to support data parallelism, but it's not the case that we have these rules today.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@thestinger thestinger commented on the diff May 18, 2014
active/0000-extending-safe-mutability.md
@@ -0,0 +1,315 @@
+- Start Date: 2014-05-16
+- RFC PR #: (leave this empty)
+- Rust Issue #: (leave this empty)
+
+
+# Summary
+
+Introducing an orthogonal handling of mutability and aliasing, which will hopefully make code clearer to read and increase the scope of what can be written without dropping to unsafe code.
+
+
+# Motivation
+
+The current system of handling mutability is confusing and requires unsafe code and compiler hacks. It is confusing because types like `Cell` can be mutated through `&T` references as they are internally mutable (an invisible property) and requires compiler hacks in the way closures are modelled.
@thestinger
thestinger added a line comment May 18, 2014

It's misleading to blame the closure issues on the mutability system. The closure issues are caused by Rust having a poorly thought out implementation of closures carried over from ancient versions of the language. It is not necessary for there to be any special cases.

It's a stretch to call the simple existing system confusing. The current mut is clearly defined as allowing assignment to the variable, and granting inherited mutability. The inherited mutability system still exists after these changes and is distinct from internal mutability, but is called mutable exclusive instead of mut.

@matthieu-m
matthieu-m added a line comment May 19, 2014

I referenced your proposal for the closures immediately afterward so hopefully it is made clearer that we can get "proper" closures without this proposal.

Regarding the confusion: C++ is clearly defined as well, but it so happens that many users are confused by it anyway. We can either blame the users for not investing themselves enough or blame the language for not being easy enough to understand. I would suggest we try to meet half-way.

@thestinger
thestinger added a line comment May 19, 2014

I don't see the problem with the existing system if you're allowing internal mutability anyway.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@thestinger thestinger commented on the diff May 18, 2014
active/0000-extending-safe-mutability.md
+```rust
+use std::cell::Cell;
+
+fn main() {
+ let c = Cell::new(4); // note: prior assignment occurs here
+ c.set(3);
+ c = Cell::new(5); // error: re-assignment of immutable variable `c`
+}
+```
+
+So, it's okay to mutate `c` via `set`, but not to assign to `c` because it is *immutable*...
+
+
+# Drawbacks
+
+By refining the type system, this RFC introduces 4 reference types where there were only 2 previously. It also requires introducing a new Trait.
@thestinger
thestinger added a line comment May 18, 2014

That's a lot of extra complexity, without any clear gains.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@thestinger thestinger commented on the diff May 18, 2014
active/0000-extending-safe-mutability.md
@@ -0,0 +1,315 @@
+- Start Date: 2014-05-16
+- RFC PR #: (leave this empty)
+- Rust Issue #: (leave this empty)
+
+
+# Summary
+
+Introducing an orthogonal handling of mutability and aliasing, which will hopefully make code clearer to read and increase the scope of what can be written without dropping to unsafe code.
+
+
+# Motivation
+
+The current system of handling mutability is confusing and requires unsafe code and compiler hacks. It is confusing because types like `Cell` can be mutated through `&T` references as they are internally mutable (an invisible property) and requires compiler hacks in the way closures are modelled.
+
+*Note: a concurrent RFC for [unboxed closures](https://github.com/rust-lang/rfcs/pull/77/) might solve the closure issue; it does not address the confusion issue though, nor the necessity to drop to unsafe code in order to mutate aliased objects.*
@thestinger
thestinger added a line comment May 18, 2014

It's misleading to say this might solve the issues. Eliminating the current crippled closures by defining them in terms of other existing features would eliminate special cases. You're stating the current system is confusing again, but I don't buy it. Feigning misunderstanding of inherited mutability while still leaving the entire thing intact with new complexity on top is not a convincing argument.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@thestinger thestinger commented on the diff May 18, 2014
active/0000-extending-safe-mutability.md
+
+*Note: a concurrent RFC for [unboxed closures](https://github.com/rust-lang/rfcs/pull/77/) might solve the closure issue; it does not address the confusion issue though, nor the necessity to drop to unsafe code in order to mutate aliased objects.*
+
+The confusion is not helped by the compiler diagnostics:
+
+```rust
+use std::cell::Cell;
+
+fn main() {
+ let c = Cell::new(4); // note: prior assignment occurs here
+ c.set(3);
+ c = Cell::new(5); // error: re-assignment of immutable variable `c`
+}
+```
+
+So, it's okay to mutate `c` via `set`, but not to assign to `c` because it is *immutable*...
@thestinger
thestinger added a line comment May 18, 2014

That's right, a mutable variable means you can assign to it. This is how mutable and immutable variables work in most languages, and yet no one claims val vs. var in Scala in confusing. The feigned confusion about this weakens the argument against the current system.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@thestinger thestinger commented on the diff May 18, 2014
active/0000-extending-safe-mutability.md
+
+fn main() {
+ let c = Cell::new(4); // note: prior assignment occurs here
+ c.set(3);
+ c = Cell::new(5); // error: re-assignment of immutable variable `c`
+}
+```
+
+So, it's okay to mutate `c` via `set`, but not to assign to `c` because it is *immutable*...
+
+
+# Drawbacks
+
+By refining the type system, this RFC introduces 4 reference types where there were only 2 previously. It also requires introducing a new Trait.
+
+The benefits may not be worth the extra complexity overhead. It is hard to perform a costs/benefits analysis when everyone has a slightly different and vague idea of what the "system" being discussed is though, therefore here comes a "reasonable" system, in all its ugly details.
@thestinger
thestinger added a line comment May 18, 2014

The current mutability system is certainly reasonable. I think it's a lot more reasonable than one where there's a useless &exclusive / &uniq type that's not intended to be used. That's certainly a lot more confusing to me.

Beyond there being people who understand Rust and those who don't, I'm missing where there's any difference / vagueness in the understanding of the system. It's pretty clearly defined and well understood, unlike the borrow checker rules which are far more subtle.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@thestinger thestinger commented on the diff May 18, 2014
active/0000-extending-safe-mutability.md
+It is trivially provable that mutation through `&exclusive T` or `&exclusive mutable T` is safe, although we disallow the former. The borrow-check current implementation is sufficient to enforce the `exclusive` rule, and therefore we can easily convert today's code:
+
+```rust
+enum Variant { Integer(int), String(~str) }
+
+fn replace(v: &mut Variant, new: &Variant) {
+ *v = *new
+}
+```
+
+to tomorrow's code:
+
+```rust
+enum Variant { Integer(int), String(~str) }
+
+fn double(v: &exclusive mutable Variant, new: &Variant) {
@thestinger
thestinger added a line comment May 18, 2014

Even as &uniq mut / &mut uniq this is significantly more verbose.

@matthieu-m
matthieu-m added a line comment May 19, 2014

I would rather note enter a bike-shedding argument now. If this proposal is rejected on semantics ground, then all discussions about syntax will have been a waste of effort anyway. And if the proposal is accepted, we can always promote a "fused" keyword if judged necessary.

@thestinger
thestinger added a line comment May 19, 2014

The proposal is mostly just moving things around via renames, so I think questioning the changes is fair. Why not just have 3 reference types rather than 2 keywords you can combine together in 4 ways?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@thestinger thestinger commented on the diff May 18, 2014
active/0000-extending-safe-mutability.md
+
+to tomorrow's code:
+
+```rust
+enum Variant { Integer(int), String(~str) }
+
+fn double(v: &exclusive mutable Variant, new: &Variant) {
+ *v = *new
+}
+```
+
+Essentially, this is simple bike-shedding for now. We add the `exclusive` and `mutable` keywords and declare that mutation may only be achieved through a `&exclusive mutable` reference. `exclusive` guarantees the safety and `mutable` guarantees the programmer intent.
+
+Still, now aliasing and mutability are expressed through two distinct keywords and thus we can move on to:
+
+ - having non-aliasing without mutability (which we will just note as an amusing side-effect, for now)
@thestinger
thestinger added a line comment May 18, 2014

Needless complication is more than an amusing side-effect, it demonstrates that the system doesn't map well to what is required.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@thestinger thestinger commented on the diff May 18, 2014
active/0000-extending-safe-mutability.md
+to tomorrow's code:
+
+```rust
+enum Variant { Integer(int), String(~str) }
+
+fn double(v: &exclusive mutable Variant, new: &Variant) {
+ *v = *new
+}
+```
+
+Essentially, this is simple bike-shedding for now. We add the `exclusive` and `mutable` keywords and declare that mutation may only be achieved through a `&exclusive mutable` reference. `exclusive` guarantees the safety and `mutable` guarantees the programmer intent.
+
+Still, now aliasing and mutability are expressed through two distinct keywords and thus we can move on to:
+
+ - having non-aliasing without mutability (which we will just note as an amusing side-effect, for now)
+ - having *safe* mutability in the presence of aliasing
@thestinger
thestinger added a line comment May 18, 2014

This already exists via types like Cell and RefCell so it's not a new feature.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@thestinger thestinger commented on the diff May 18, 2014
active/0000-extending-safe-mutability.md
+}
+```
+
+Essentially, this is simple bike-shedding for now. We add the `exclusive` and `mutable` keywords and declare that mutation may only be achieved through a `&exclusive mutable` reference. `exclusive` guarantees the safety and `mutable` guarantees the programmer intent.
+
+Still, now aliasing and mutability are expressed through two distinct keywords and thus we can move on to:
+
+ - having non-aliasing without mutability (which we will just note as an amusing side-effect, for now)
+ - having *safe* mutability in the presence of aliasing
+
+Let's go.
+
+
+## 3. Introducing the `SafelyMutable` Trait
+
+Types such as `int` or `Cell` can be safely mutated even when multiple aliases exist, yet the type system currently forbids it. This leads us to:
@thestinger
thestinger added a line comment May 18, 2014

This functionality already exists via Cell and it's called Pod. There is no use case for Cell if you no longer need it to mutate Pod types through non-exclusive references. The Unsafe<T> marker informs the compiler about internal mutability.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@thestinger thestinger commented on the diff May 18, 2014
active/0000-extending-safe-mutability.md
+
+Essentially, this is simple bike-shedding for now. We add the `exclusive` and `mutable` keywords and declare that mutation may only be achieved through a `&exclusive mutable` reference. `exclusive` guarantees the safety and `mutable` guarantees the programmer intent.
+
+Still, now aliasing and mutability are expressed through two distinct keywords and thus we can move on to:
+
+ - having non-aliasing without mutability (which we will just note as an amusing side-effect, for now)
+ - having *safe* mutability in the presence of aliasing
+
+Let's go.
+
+
+## 3. Introducing the `SafelyMutable` Trait
+
+Types such as `int` or `Cell` can be safely mutated even when multiple aliases exist, yet the type system currently forbids it. This leads us to:
+
+ - compile-time errors when attempting to borrow them into multiple `&mut` simultaneously
@thestinger
thestinger added a line comment May 18, 2014

That's because &T is currently how this is done.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@thestinger thestinger commented on the diff May 18, 2014
active/0000-extending-safe-mutability.md
+Essentially, this is simple bike-shedding for now. We add the `exclusive` and `mutable` keywords and declare that mutation may only be achieved through a `&exclusive mutable` reference. `exclusive` guarantees the safety and `mutable` guarantees the programmer intent.
+
+Still, now aliasing and mutability are expressed through two distinct keywords and thus we can move on to:
+
+ - having non-aliasing without mutability (which we will just note as an amusing side-effect, for now)
+ - having *safe* mutability in the presence of aliasing
+
+Let's go.
+
+
+## 3. Introducing the `SafelyMutable` Trait
+
+Types such as `int` or `Cell` can be safely mutated even when multiple aliases exist, yet the type system currently forbids it. This leads us to:
+
+ - compile-time errors when attempting to borrow them into multiple `&mut` simultaneously
+ - confusion when `let c = Cell::new(4);` can be mutated even though it is not apparently mutable (and attempts to assign to it fail because it is *immutable*)
@thestinger
thestinger added a line comment May 18, 2014

A variable or reference being immutable means you can't assign to it. Anyone lacking an understanding of inherited mutability will still be confused after these changes, because the distinction is still there.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@thestinger thestinger commented on the diff May 18, 2014
active/0000-extending-safe-mutability.md
+
+or
+
+```rust
+struct Cell<T> { ... }
+
+impl<T> SafelyMutable for Cell<T> {}
+```
+
+The compiler will enforce the necessary rules for a `SafelyMutable` type to really be safe. Let us start simple:
+
+ - a built-in integral or floating point type is `SafelyMutable`
+ - a fixed-length array of `SafelyMutable` types, is itself `SafelyMutable`
+ - a `struct` may be declared `SafelyMutable` if all its members are `SafelyMutable`
+ - a `struct` may be declared `SafelyMutable` if it has an unsafe interior
+ - an `enum` may be declared `SafelyMutable` if it is a C-like `enum` (no payload for any enumerator)
@thestinger
thestinger added a line comment May 18, 2014

It seems that Cell will just be Pod enum -> SafelyMutable enum as the other use cases are gone. I feel like there's a lot of non-orthogonality between SafelyMutable and Cell as they accomplish nearly but not quite the same thing.

@matthieu-m
matthieu-m added a line comment May 19, 2014

Indeed, the very goal of SafelyMutable is to describe the functionality of Cell in a Trait the type system is aware of so that other types can implement Cell's functionality (and assignment). Instead of being an OVNI, Cell becomes one of the types implementing SafelyMutable.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@thestinger thestinger commented on the diff May 18, 2014
active/0000-extending-safe-mutability.md
+
+*Note: this is maybe too complicated, and we can start by requiring that a type either always is or is not `SafelyMutable` regardless of the parameters, as for `Cell`.*
+
+
+## Mutable members and `&exclusive self`
+
+In C++, one can use the `mutable` keyword to declare members that may be mutated even in `const` methods. This is often used to implement either lazy-computation or caching of values. Today, this code in Rust requires either:
+
+ - unsafe code
+ - exposing a `&mut self` method, even though the mutation is hidden to the user
+
+If we allow such an extension to the language, then `&exclusive self` is necessary:
+
+```rust
+struct LazyComputation<T> {
+ priv producer: mutable proc () -> T,
@thestinger
thestinger added a line comment May 18, 2014

You're proposing extra complexity to get rid of hidden internal mutability, but here it is added right back. Just as we can't enforce how people are going to use Cell today, there's no way to enforce conventions for mutable.

@matthieu-m
matthieu-m added a line comment May 19, 2014

First: this is a possible extension.

Second, I do not propose to get rid of internal mutability. I propose to get rid of visible internal mutability. A type can be logically immutable while still being implemented with internal mutability for efficiency reasons; what matters is its interface not its implementation.

The problem with Cell today is that its interface exposes surprising changes; on the other hand I take no issue with Rc maintaining a references counter internally because the users care little about that counter.

@thestinger
thestinger added a line comment May 19, 2014

How will the compiler enforce that mutable is not used for visible mutability? The Rc internal mutability is certainly visible, because a destructor can have a side effect and calling clone will delay the effect.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@thestinger thestinger commented on the diff May 18, 2014
active/0000-extending-safe-mutability.md
+}
+
+impl<T: SafelyMutable> SafelyMutable for Cons<T> {}
+```
+
+*Note: this is maybe too complicated, and we can start by requiring that a type either always is or is not `SafelyMutable` regardless of the parameters, as for `Cell`.*
+
+
+## Mutable members and `&exclusive self`
+
+In C++, one can use the `mutable` keyword to declare members that may be mutated even in `const` methods. This is often used to implement either lazy-computation or caching of values. Today, this code in Rust requires either:
+
+ - unsafe code
+ - exposing a `&mut self` method, even though the mutation is hidden to the user
+
+If we allow such an extension to the language, then `&exclusive self` is necessary:
@thestinger
thestinger added a line comment May 18, 2014

This means the &exclusive reference type means inherited mutability just like &exclusive mutable. The only difference is the need to jump through an extra hoop to use the obfuscated &exclusive mutability.

@matthieu-m
matthieu-m added a line comment May 19, 2014

I'll refer you to my precedent comment. Mutability not exposed to the user is an implementation detail.

@thestinger
thestinger added a line comment May 19, 2014

It's not an implementation detail, because it has an impact on the reasoning that's possible for the type. If it gets in the way of sane user-defined optimizations via rewrite rules (it does) then it's part of the public API.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@thestinger thestinger commented on the diff May 18, 2014
active/0000-extending-safe-mutability.md
+impl<T> LazyComputation<T> {
+ pub fn get(&'a exclusive self) -> &'a T {
+ // since `self` is `&exclusive`, `self.value` is `&exclusive mutable`
+ match self.value {
+ None => self.value = producer();
+ _ => ();
+ }
+ self.value.get()
+ }
+}
+```
+
+
+# Alternatives
+
+There are several other RFCs that aim to tackle some issues, and Niko had a blog post about focusing on non-aliasing rather than mutability to enforce memory-safety.
@thestinger
thestinger added a line comment May 18, 2014

The mutability system isn't what provides safety in today's system either.

@matthieu-m
matthieu-m added a line comment May 19, 2014

Maybe poorly worded; I certainly did not attempt to make such claim. My point was that Niko wanted to shift the emphasis on non-aliasing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@thestinger thestinger commented on the diff May 18, 2014
active/0000-extending-safe-mutability.md
+ None => self.value = producer();
+ _ => ();
+ }
+ self.value.get()
+ }
+}
+```
+
+
+# Alternatives
+
+There are several other RFCs that aim to tackle some issues, and Niko had a blog post about focusing on non-aliasing rather than mutability to enforce memory-safety.
+
+For reference:
+
+ - [RFC #77: Unboxed Closures](https://github.com/rust-lang/rfcs/pull/77/) aims at solving the closure issue without modifying the current type system
@thestinger
thestinger added a line comment May 18, 2014

This was proposed long before the fuss about mutability. It was not proposed as a way to eliminate a special case from the compiler. It eliminates the special case as a side effect of being sane rather than as a goal.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@thestinger thestinger commented on the diff May 18, 2014
active/0000-extending-safe-mutability.md
+
+
+# Alternatives
+
+There are several other RFCs that aim to tackle some issues, and Niko had a blog post about focusing on non-aliasing rather than mutability to enforce memory-safety.
+
+For reference:
+
+ - [RFC #77: Unboxed Closures](https://github.com/rust-lang/rfcs/pull/77/) aims at solving the closure issue without modifying the current type system
+ - [Focusing on Ownership](http://smallcultfollowing.com/babysteps/blog/2014/05/13/focusing-on-ownership/) details how Rust would be better off focusing on aliasing than mutability
+ - [RFC #58: Rename &mut to &only](https://github.com/rust-lang/rfcs/pull/58) also argues that aliasing is the better focus
+
+There has, however, been an uproar of part of the community, the message was:
+
+ - mutability is an important concept, even if memory-safety can be enforced without it
+ - internally mutable types (such as `Cell`) are confusing because even though `mut` is about inherited mutability it is viewed as a marker of the actual mutability of the object, causing a paper cut
@thestinger
thestinger added a line comment May 18, 2014

This problem isn't solved if mutable exists for fields for mutation through &exclusive.

@matthieu-m
matthieu-m added a line comment May 19, 2014

Once again, we worry about different things. I worry about logical mutability while you worry about bit-wise mutability.

From a logical mutability point of view, the existence of mutable T or `Unsafe fields is not necessarily an issue providing this implementation detail does not leak into the interface.

@thestinger
thestinger added a line comment May 19, 2014

If the compiler is not enforcing that it's only logical mutability (whatever that means), it's no different than Cell. It's only a convention that you want people to follow, but have no way of enforcing. The fact that it's a language feature doesn't make the convention any more likely to be respected.

@rkjnsn
rkjnsn added a line comment May 21, 2014

C++ provides many ways to defeat const, but it's still very useful for expressing intent and catching programming mistakes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@thestinger thestinger commented on the diff May 18, 2014
active/0000-extending-safe-mutability.md
+```
+
+
+# Alternatives
+
+There are several other RFCs that aim to tackle some issues, and Niko had a blog post about focusing on non-aliasing rather than mutability to enforce memory-safety.
+
+For reference:
+
+ - [RFC #77: Unboxed Closures](https://github.com/rust-lang/rfcs/pull/77/) aims at solving the closure issue without modifying the current type system
+ - [Focusing on Ownership](http://smallcultfollowing.com/babysteps/blog/2014/05/13/focusing-on-ownership/) details how Rust would be better off focusing on aliasing than mutability
+ - [RFC #58: Rename &mut to &only](https://github.com/rust-lang/rfcs/pull/58) also argues that aliasing is the better focus
+
+There has, however, been an uproar of part of the community, the message was:
+
+ - mutability is an important concept, even if memory-safety can be enforced without it
@thestinger
thestinger added a line comment May 18, 2014

I'm not really sure that it's an important concept. Enforcing a complex mutability system feels a lot like obfuscating code while doing a pointless song and dance to work around it because it's not really how things work at a low-level. The current system is only worth it to me because the inherited mutability concept already exists for &mut and having it available to catch common errors with local variables has a low cost.

I can almost see the point of having 3 reference types (transitive immutability, internal/external mutability, internal/external/inherited mutability) but I can't understand what we gain from having four. Even with 3 reference types, it certainly feels like there's going to be a large increase in complexity over the current system. At the very least it's going to result in a plethora of new methods for handling each of &imm T, &mut T and &T unless I'm missing something.

@matthieu-m
matthieu-m added a line comment May 19, 2014

I must admit I still do not understand your classification; it's probably that I lack some background but unfortunately I consequently have a hard time understanding this system.

My feeling is that imm would represent bit-wise immutable types, whilst the two others would stand for what they are today; but in this case once again we fall on the issue that this proposal is about the interface of a type and not its implementation: logical const-ness vs bit-wise const-ness.

@thestinger
thestinger added a line comment May 19, 2014

I don't think there's truly a concept of "logical" immutability for a language to expose in a coherent way. Logical immutability is one of the C++ misfeatures removed by D.

@rkjnsn
rkjnsn added a line comment May 21, 2014

My understanding is that D uses bitwise immutability for thread safety and optimization, problems that Rust tackles through different means. Coming from C++, I find logical immutability very useful, as it allows one to see intent and reason about behavior independently of implementation.

@thestinger
thestinger added a line comment May 21, 2014

Rust will still depend on immutability for thread safety when using &T for data parallelism. The &T type will never simply mean aliasing reference, because otherwise it could allow all mutation that does not invalidate references, without Cell. This would be more flexible than Cell, because you could mutate a field without a by-value assignment or take references into fields of the mutable data. It's simply untrue that aliasing is somehow independent of mutability, since aliasing has to be defined in terms of mutation. Two references alias if there can be a memory dependency between them. It's trivial to prove that Rust does not enforce aliasing defined terms of address equality.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@thestinger

@zwarich: The current rules require that internal mutability happens through an Unsafe<T> wrapper type, so the compiler is aware of types where unsafe is used for that purpose.

@zwarich

@thestinger: That rule is frequently violated in Rust and Servo code, so I consider it more wishful thinking at the moment. I'm also not entirely convinced it's sound, although it's difficult to say since it's never even been precisely stated.

@huonw
The Rust Programming Language member

The statement is exactly that: the only legal way to mutate aliased data is by using the Unsafe type to perform the mutation.

RefCell, Cell, Mutex, RWLock and the atomics all use Unsafe internally. (There's only a few remaining instances of non-Unsafe mutation in Rust; at least, I removed the obvious ones when I removed the old transmute_mut function and, iirc, the only ones I didn't migrate to Unsafe were those where the & references didn't alias with anything. I left fixmes on them too.)

The advantage of this rule is &T references are known to be NoAlias except if T contains Unsafe.

@nrc

@huonw by 'legal', do you mean by convention or that the compiler enforces it?

@huonw
The Rust Programming Language member

I mean: not using Unsafe is undefined behaviour (in the technical sense, like dereferencing null or signed integer overflow in C). That is, the compiler is allowed to assume that no-one ever mutates behind & without using Unsafe and is allowed to optimise based on it never happening, which may break any program that does invoke the bad behaviour.

@thestinger

@zwarich: The manual has stated that this is undefined behaviour for quite some time. The standard library types with inherited mutability used to rely on a #[no_freeze] marker, followed by NoFreeze and now Unsafe<T>. If Servo is relying on this, I think that's an issue for Servo to fix rather than one rustc has to be concerned with.

@rkjnsn

I'm pretty new to Rust, but I thought I'd drop my two cents.

To me, uniqueness and mutability seem to be distinct, though related, things, and expressing them independently, as in this proposal, makes the most intuitive sense.

Uniqueness (or exclusiveness) is a property of the reference, is strongly enforced by the compiler, is required to provide compile-time memory safety, and is one of the features that makes Rust so exciting.

Mutability, on the other hand, I feel is more an expression of programmer intent, and helps with reading and reasoning about code. Having it in the type system is not required for safety, but including it allows the compiler to catch programmer mistakes, which is nice. (E.g., you tried to mutate a value you said you wouldn't (error), or you didn't mutate a value you said you would (warning/lint)).

Because mutability would be a statement of programmer intent, it makes sense to me that it would be concerned with "logical" mutability. In other words, any mutation to a non-mutable object would be limited to private members and would not be readily apparent to the user of the object. This would allow an implementation to be changed to, e.g., use caching for better performance without changing the interface.

While the additional possibilities for reference types introduced by this proposal can be seen as adding complexity, I feel separating exclusivity and mutability actually makes things conceptually simpler and easier to reason about. For example, changing the value in a Cell would require mutability but not exclusiveness, which I find easier to understand/explain than the current situation. I also know that a non-mutable (exclusive or not) reference shouldn't change in a way I have to worry about as a consumer of the type. I think all four combinations are useful and worth having.

All that said, I'm not as convinced about SafelyMutable. It seems like a fair amount of complexity, and I'm not sure what it actually gets you. It seems to have a lot of overlap with the capabilities of Cell.

@brson

Discussed at https://github.com/rust-lang/rust/wiki/Meeting-RFC-triage-2014-06-19. This is a large and complex proposal, containing major changes to language semantics. At this stage in development we don't want to consider such a major overhaul. Closing. Thank you.

@brson brson closed this Jun 19, 2014
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment