-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
First version of the Extending Safe Mutability proposal. #78
Conversation
|
||
*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: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor nit: "disallow" might be more clear than "disable" here. Also "borrow-check" should probably be "borrow-checker's"
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:
The 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 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:
A fully backwards compatible example is the following:
There are no real changes required to the type system or standard libraries to support this. The compiler is already fully aware of inherited ( |
I agree 100% with @thestinger. I don't think the current system should change, but if it should, adding |
|
||
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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't believe we make any guarantees about how enum
s 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).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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).
You didn't make it entirely clear as to whether you can take a In any case, your proposal here makes it impossible to implement I don't see any solution to this. The only thing that makes |
- 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.* |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
If this proposal were implemented, I could still write a |
The proposal does not seek to eliminate all uses of internal mutability (such as It then goes on to try and define helpful rules to diminish the use of I think that as long as visible mutability is clearly documented, we have won something. If it takes |
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.
This still doesn't solve the |
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:
With this proposal, |
If this proposal essentially allows opt-in mutability annotations, but doesn't actually prohibit internal mutability through a |
With this proposal, would there be any downside to simply using |
@P1start That's functionally no different than just adopting Niko's proposal, because you've just gotten rid of |
@kballard How does that make it no different? It allows one to have multiple mutable pointers to the same location (if it’s 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 |
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 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 |
@P1start Ok, yes, I forgot that this proposal defined primitives as But I think that making primitives mutable like this is actually a bad thing. Today, LLVM knows that any |
@kballard Today, LLVM does not know that |
@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. |
@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. |
@matthieu-m: The mutation performed during |
@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 |
@thestinger There are other aliasing rules in the LLVM LangRef, e.g. @matthieu-m The compiler doesn't need to analyze 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 @huon What is gained by making modifications through |
|
||
## 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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rust doesn't really make any guarantees about this right now.
@zwarich: The current rules require that internal mutability happens through an |
@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. |
The statement is exactly that: the only legal way to mutate aliased data is by using the
The advantage of this rule is |
@huonw by 'legal', do you mean by convention or that the compiler enforces it? |
I mean: not using |
@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 |
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 All that said, I'm not as convinced about |
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. |
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:
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.