Skip to content
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

Rename &mut to &only #58

Closed
wants to merge 5 commits into from
Closed

Rename &mut to &only #58

wants to merge 5 commits into from

Conversation

DaGenix
Copy link

@DaGenix DaGenix commented Apr 30, 2014

The main guarantee provided by &mut is one of non-aliasability, but the name implies that the main guarantee is related to mutability. Additionally, &mut implies that & is immutable, which it isn't due to types such as Cell and Mutex that implement interior mutability. So, &mut is a misleading name. Rename it to &only which more closely reflects what it guarantees and also doesn't imply anything incorrect about &.

@huonw
Copy link
Member

huonw commented Apr 30, 2014

This would allow mutation through an arbitrary &only reference, right? And so would effectively mean that Rust variables are not immutable by-default (and indeed that there is no such thing as immutable, even for types that don't transitively contain an Unsafe)?

It seems particularly peculiar that one of the following is valid (y) and the other is not (x):

let x = 1;
x = 2;

let y = 1;
let z = &only y;
*z = 2;

@lifthrasiir
Copy link
Contributor

This does have a benefit of a consistent pattern syntax: &mut x is a mutable reference to the type x in the type context, but it's any reference (mutable or not) destructed into a mutable slot x in the pattern context. With this RFC, the former will change to &only x which can be made invalid in the latter.

@DaGenix
Copy link
Author

DaGenix commented May 1, 2014

I'm not really expecting for this RFC to be accepted, however, I do think there is a problem that needs to be addressed. The point of this RFC is to present one solution and hopefully kick of a discussion. The problem that I'm trying to address is that the existence of &mut and mut imply that & and non-mut are immutable, which really isn't the case. Take:

use std::cell;
fn main() {
    let c = cell::Cell::new(4u);
    println!("Val: {}", c.get());
    c.set(5u);
    println!("Val: {}", c.get());
    // Output: 4\n5\n
} 

Something is clearly being mutated, even though the word 'mut' doesn't appear anywhere in the program. The reason that mutation occurs here is because Cell contains an Unsafe. So, the rule could be: An & is an immutable reference as long as no structure transitively reachable from the referenced struct contains an Unsafe. However, the only way to tell if some transitively reachable struct contains an Unsafe is to review all of the code. There is also nothing that prevents some code that didn't used to contain an Unsafe from changing to include an Unsafe at some point in the future. My argument is that that isn't really a useful guarantee for programmers. If you tell me that the only way to make sure that an object won't be mutated is to audit all of the code for the object looking for an Unsafe, then I might just as well audit the code to tell if its mutating the object. So, my conclusion is that Rust already doesn't support immutable structs.

So, if you accept my assertion that Rust already lacks immutable types and references (which i'll admit is a big 'if'), then, the question becomes, what are we really saying when we put a mut or a &mut in front of something? We're not making it mutable, since, its already (potentially) mutable. It seems to me that what is really being said by &mut is that the reference is non-aliasable and & is aliasable which is only tangentially related to mutability. In my opinion, having the term &mut really obscures that important distinction which is why I'm proposing renaming it to something that is more clearly related to aliasability.

If &mut is renamed to something else, then, the next question is why can't I borrow a non-aliasable reference to any local variable that is currently non-aliased. Whether or not the variable is currently in a mutable slot or not seems irrelevant. Granted, doing so would lead to that weird case you outline.

Anyway, I'm sure there are a variety of issues with this proposal. The first thing I'm hoping to do is to see if others agree if there is an issue. Second, if its agreed that this is an issue, I'd like to explore ways to address it. This is just one possible way to address it; I'm sure there are others and I would be surprised if some of those other ways weren't superior.

@DaGenix
Copy link
Author

DaGenix commented May 4, 2014

Maybe this is a better example of what I'm getting at:

use std::cell::Cell;

fn some_func(c: &Cell<uint>) {
...
}

fn funca(c1: &Cell<uint>, c2: &Cell<uint>) -> uint {
    c1.set(10);
    some_func(c2);
    c1.get()
}

fn funcb(c1: &mut Cell<uint>, c2: &Cell<uint>) -> uint {
    c1.set(10);
    some_func(c2);
    c1.get()
}

So, we have two functions - funca and funcb which are completely identical except that c1 is passed with a & in funca and a &mut in funcb.

What does funca return? Based on the code here, there is no way to know. Its return value depends on whether or not c1 and c2 refer to the same struct (which is determined by the caller), what some_func does, and also whether or not Cell contains an Unsafe. Its well known that Cell does contain an Unsafe, however, for a more complex object, there is no good way to know (other than auditing the code of the struct, but, even that isn't a great solution, since a) implementations can change and b) if the struct contains a ~Trait or is a generic type, its going to be very hard to tell if there is an Unsafe in the struct).

What does funcb return? It always returns 10. It doesn't matter what was passed in, what some_func does, or if Cell contains an Unsafe. The reason is because &mut guarantees that c1 is not aliased so some_func can't mutate c1 because we only pass it c2.

So, in this case, & really provides us no ability to locally reason about how an object will be mutated. &mut does provide us with the ability to locally reason about mutation. However, the thing to note is that we have the &mut reference to c1, but what that is providing us is a guarantee not about c1 but rather the relationship between c1 and c2. Specifically the guarantee is that they do not reference the same object and thus mutating c2 won't impact c1.

I think the word 'mut' in &mut obscures this very important distinction. Just looking at the code, the &mut really, really looks like its only related to c1. I think this can cause a variety of issues. An example: since funcb never actually calls a method that requires a &mut, I think its going to be very easy to accidentally change that &mut into a & without realizing that it completely changes the meaning of what the code does. If &mut were called &only (or something like that), however, I think it makes it much clearer what's going on.

@DaGenix
Copy link
Author

DaGenix commented May 4, 2014

I do have a tendency to write extremely long comments. So, to put is more succinctly: The real guarantees provided by & and &mut related to aliasabilty, not mutability, so the names should reflect that.

@bstrie
Copy link
Contributor

bstrie commented May 6, 2014

It seems more like the issue is with Cell's subversion of the type system than with the naming of &mut. How common is Cell in practice?

@huonw
Copy link
Member

huonw commented May 6, 2014

I wonder if there is some value in types like &, &only (immutable but non-aliased) and &mut. It would allow rust-lang/rust#13996 to be fixed in a natural way too: *only T. (Although the raw pointer form would really need to be *only mut T (or *mut only T).)

However, this is adding more complication to our pointers. As a point of comparison, does C++ allow restrict on references?

@thestinger
Copy link

C++ doesn't have restrict, it's a C99/C11 feature that's not included. C++ does have a mutable keyword allowing mutation of fields inside a const type so there is precedence for this.

@DaGenix
Copy link
Author

DaGenix commented May 7, 2014

The original RFC I had was a bit rambling and hand-wavey. I've completely re-written the RFC, hopefully laying out my arguments much better and incorporating my comments from above. Additionally, I tried to arrange my arguments so that it should be easier to discuss and critique each of my individual points.

I've also removed everything about borrowing a non-mut slot to an &only. That's a separate discussion and I don't really care about it all that much anyway.

@DaGenix
Copy link
Author

DaGenix commented May 7, 2014

@bstrie I don't think that Cell is really subverting the type system. It does have an unsafe implementation, but its interface is completely safe. Mutex is another type that has similar behaviour. Regardless of how common they are, if they break a guarantee assumed of one of the reference types, then it really wasn't a guarantee to begin with. Either that or Cell, Mutex, and related types should be removed to restore the guarantee, but I don't think that's practical since they are both very useful.

@DaGenix DaGenix changed the title Rename &mut to &only; then allow &only borrowing of non-mut slots Rename &mut to &only May 7, 2014
@arielb1
Copy link
Contributor

arielb1 commented May 7, 2014

@DaGenix

Unsafe does not introduce correctness problems that wont exist before it - Rust never tried to have referential transparency, and Cell::get() and some_func() could communicate via global variables, thread-local variables, and/or IO with a shared resource.

An indistinguishable-up-to-performance implementation of Cell is a GUID referring to an object stored outside of the system (either in a global table or a different task/process/machine).

@nikomatsakis
Copy link
Contributor

For the record, I am still in favor of something along these lines (I haven't had time yet to read the RFC in detail). I agree that the Rust type is really about UNIQUENESS, and our current "let mut" rules are basically a kind of lint. The last few times I've made this case I've been rebuffed, but I've been quietly gathering evidence, and I do plan to make one last attempt to argue my case before 1.0.

@DaGenix
Copy link
Author

DaGenix commented May 9, 2014

@arielb1

I'm not suggesting that there is anything incorrect with Cell, Unsafe, or the reference types. This RFC, as currently written, suggests no functional changes - it just changes the name of &mut.

Regarding how some_func relates to Cell - in this example, I'm using a real type specifically because everyone knows how it is implemented. Although an arbitrary struct could do what you point out, we know that Cell does not use any IO, thread locals, or global variables. Furthermore, we know that two different instances of Cell are always independent of each other. However, even though we know all of this, if you are handed two &Cell values, you still have to be careful if you write to either of them (or if you pass one to a another function that might) since they might actually point to the same chunk of memory. If either of those references is a &mut Cell, however, you no longer have to worry about this. What &mut is guaranteeing is not about the ability to mutate a particular Cell, but instead the immutability of one &Cell through a different reference. I feel that the term &mut doesn't do a great job of conveying this distinction, however. All that being said, I think I could make this example a bit better, so, I'll work on it a bit.

Putting it another way - let's assume that Cell did have your hypothetical GUID pointer to another location implementation. If this were the case, I believe that Cell would represent the actual location the value is stored in (or something indistinguishable from the location). I believe this is the case since two different Cell instances are always independent. A &Cell is a (potentially) non-unique GUID that points to the location contained in the Cell. A &mut Cell is a guaranteed unique GUID to the location which means that we can be sure that no other GUID points to that same location. As before, this is a guarantee of aliasability, not mutability.

@DaGenix
Copy link
Author

DaGenix commented May 9, 2014

I've pushed an update to make my example for my 3rd point a bit clearer (I think) and also to make various other improvements and fixups.

@DaGenix
Copy link
Author

DaGenix commented May 9, 2014

@nikomatsakis I think I'm been trying to do pretty much the same thing as you are - gather evidence. The "Detailed Design" section of the RFC is just 4 words. The rest of the RFC is dedicated to trying to make the case that this renaming should be done.

In this RFC I'm just focusing on &mut and I didn't touch let mut at all, not because I don't think that let mut could use some renaming or improvements but because a) don't really have any concrete ideas of what to do with it and b) hope that the issue of what to call &mut is orthogonal to how let mut works.

@nikomatsakis
Copy link
Contributor

@DaGenix I'll try to read the RFC today, but the interaction between &mut and let mut is anything but orthogonal.

@DaGenix
Copy link
Author

DaGenix commented May 9, 2014

@nikomatsakis "Orthogonal" wasn't a good word. What I meant is that I hope I could create a reasonable argument for the renaming of &mut just focused on &mut and &.

@pcwalton
Copy link
Contributor

pcwalton commented May 9, 2014

I find this attractive from a simplicity point of view, but my only (no pun intended) concern is one of making the language accessible to newcomers. Which is an easier statement (and this is an honest non-rhetorical question as I don't know the answer):

  • "References are by default immutable in Rust. You can make a mutable reference with &mut, but you can't have two such references to the same piece of data at the same time."
  • Or, "Rust will only let you modify data if you have the only reference to it. Because of this, the compiler will not let you mutate data through an ordinary & reference, but there is a special type of reference

@pcwalton
Copy link
Contributor

pcwalton commented May 9, 2014

argh, phone commented early.

"... called &only, which must be the only reference to the data it points to. The compiler will let you mutate data through &only references."

@bvssvni
Copy link

bvssvni commented May 10, 2014

While the interaction between '&mut' and 'let mut' is not orthogonal in theory, it might be almost orthogonal in practice. Looking through my recently written code I can not find '&mut' in other places than function arguments. Are there any cases where using '&mut' in let expressions is not just a way to simplify passing arguments?

'let mut' could be changed from deep mutable to mutable binding (and values) without changing Rust-Graphics or Piston semantically. '&mut' can simply be replaced by '&only'. On the other hand, changing 'let' to mutable by default will have a huge impact. I wonder how this affects other projects.

Changing 'let mut' to mutable binding will make 'let mut x = &a;' legal code.

@thestinger
Copy link

let mut x = &x is already legal code, so I'm not quite sure what you mean by that. It makes a mutable variable holding an immutable (aliasable) reference, so it can have another reference assigned.

@DaGenix
Copy link
Author

DaGenix commented May 11, 2014

@pcwalton

The & is immutable and &mut is mutable description might be slightly easier for newcomers to think they understand. When I was a newcomer, I thought I understood how they worked. I read about types like Cell (or whatever it was called at the time) and I didn't really understand them so I ignored them. Then, after a few months with working with Rust, I started to try to use types like Cell as part of some code I was working on and I became quite confused, since Cell didn't fit into my mental model for how I thought Rust worked. So, I felt like I had to re-learn Rust to understand how those types worked and why they worked. My motivation for writing this RFC is that I don't want newcomers to learn Rust once when they start, and then feel like they need to fully relearn it a few months later as happened with me.

I don't think that &only is any more complicated that &mut to understand initially. Why uniqueness is so important might be. However, it has the benefit of making very explicit the concepts of ownership and uniqueness that are so important to Rust. The earlier that newcomers are exposed to those topics, the better, in my opinion.

@asb
Copy link

asb commented May 11, 2014

@pcwalton I think the problem with your question is it doesn't quite represent how people learn. Either description makes sense as it's being read, but the average rust newbie isn't going to remember everything they read about it. As such, I think it's more important that the keywords used give a useful hint as to their meaning. The advantage of &only is it highlights arguably the most important issue (ownership).

@DaGenix
Copy link
Author

DaGenix commented May 12, 2014

it's also easier to understand that &mut enforces uniqueness, whereas understanding that &only includes mutability is just confusing

I think its significantly more confusing that the "immutable reference" type, &, is actually mutable in some circumstances, and that the "mutable reference" type &mut, isn't actually guaranteed to be mutable.

Not only that, but from a practical standpoint, mutability is the goal of &mut, not uniqueness. The latter is important for preventing bugs, yes, but in general &mut pointers are created because the author needed to mutate the value, not because the author needed to ensure the pointer was unique.

At the most basic level, the author uses the types & and &mut in order to ask the compiler to do something on their behalf. &mut does not make the value mutable, it makes sure its non-aliased. The name is conflating the common intent of the author with the actual guarantee of the type. Most of the time this isn't a problem, but when what the author thinks they are getting and what they compiler actually gives them do differ, the result are bugs and confusion. These are bad things. These are also problems that can be fixed simply by renaming the types to make it very clear what the compiler is doing.

The RFC says

&mut's real guarantee is 'non-aliased'; mutability is just a consequence

But it's not true that mutability is a consequence of non-aliasing. It's perfectly plausible to have a non-aliasing immutable pointer, or a non-aliasing pointer that doesn't care about mutability (which is what I believe &uniq is, that special pointer type that closures use for capturing values). "if mutable then non-aliasing" does not imply "if non-aliasing then mutable".

All I claim is that the only guarantee that &mut provides is non-aliased. I do not claim that a non-aliased pointer must also be mutable. In current Rust, its perfectly valid to have a &mut pointer to a type that provides no method to mutate it.

I think the important distinction to make here is that & only guarantees shallow immutability. A &Cell has interior mutability, but it's still shallowly-immutable.

I completely agree. I think the current naming confuses the issue, though. The name &mut implies that & is immutable. When you think of a type being immutable, I make claim that no one intuitively expects shallow immutability; deep immutability is what is expected. Even if you know that what & provides is shallow immutability, I don't think that it is a generally useful thing for a human to reason about (it is exceptionally useful for an optimizer, of course).

I think the basic argument here is that & does not guarantee full immutability, and some people find this concerning. But this argument basically boils down to "if you don't know what type you're working with, you don't know if that type is immutable". If I have a T, I have no idea if it has interior mutability. If I have a &T, I still don't know that, but I do know it has shallow immutability. If I have a &int I know it does not have interior immutability, and if I have a &Cell I know it does. I think people are putting too much emphasis on this &T type, where you know enough to know it's a reference but don't know enough to know if the pointee type has interior mutability. And I don't know why that matters.

Generic functions are quite common in Rust, so why wouldn't I care about them? If anything, I care more about them than non-generic functions since due to them working on types that might not even exist when the function is originally written they are much harder for a human to reason about. This is exactly the scenario where I want the most help from the compiler to reason about what is going on.

Let's take a look at one of the examples given in the RFC:

use std::cell::Cell;

fn some_func(c: &Cell) { ... }

fn funca(c1: &Cell, c2: &Cell) -> uint {
some_func(c2);
c1.get()
}

fn funcb(c1: &mut Cell, c2: &Cell) -> uint {
some_func(c2);
c1.get()
}

This is used to try and demonstrate why non-aliasing is important, with the question "what does funca() return?". It states as given that the caller expects both funca() and funcb() to return the value in the first parameter. And it makes the claim that, because funca() does not enforce non-aliasing, it doesn't meet this guarantee.

But I don't see this as an example of how aliasing is important. I see this as an example of how to write code that breaks invariants. The correct definition of funca() is

fn funca(c1: &Cell, c2: &Cell) -> uint {
let ret = c1.get();
some_func(c2)
ret
}

This guarantees it returns the value from c1 regardless of whether c1 and c2 are actually references to the same Cell. Furthermore, I think funcb() is actually broken. Because this function can be written to behave correctly without the non-aliasing guarantee, requiring non-aliasing is actually overly-restrictive.

My point here is not that this is a great example of Rust code or that its particular useful, but that it is valid code where &mut clearly is not guaranteeing mutability, even though the type is mutable. What I am trying to establish is what the guarantee provided by &mut is at its most basic level - the thing that it always guarantees. Non-aliasability and mutability are not equivalent concepts. Only one of them can be the fundamental guarantee. The other thing may be related, but it is not fundamental. If that guarantee were mutability, examples like this shouldn't exist. Can you come up with an example where &mut guarantees mutability without guaranteeing non-aliasability? If you can, that totally destroys my point. If you can't, I think my point stands - that non-aliasability is the fundamental guarantee.

The RFC also tries to confuse the issue by claiming that, were these args not Cell but in fact some other type defined in some other file (let's say Frob), that you have to audit its code to find out if it has interior mutability, in order to find out if your guarantee holds. And this is preposterous. If you don't know what the heck Frob is, why are you using it? If you know that Frob has a get() -> T method, but you don't know if it has interior mutability, then what exactly are you expecting Frob to even be doing? You don't even know the semantics of the get() method (and if you do look them up, then you have only yourself to blame for not reading the rest of the documentation on Frob. The semantics of the entire type are important, not just a single method). And besides, as I have shown, even not knowing whether Frob has interior mutability, you can still enforce your invariant merely by writing the correct code.

Let's say I went and read every line of documentation for Frob and I determined that it doesn't implement interior mutability. Great - then I can use a & and be sure that it won't be mutated. A few weeks later, however, I update the library that provides the Frob class. Now what? I have to re-read everything? If & guaranteed deep immutability, I wouldn't have to. But, & doesn't guarantee that, so, I do have to. I'm not claiming that that is a bad thing, all I'm claiming is that that is how things work. So, naming that implies & guarantees deep immutability is a bad thing since that isn't actually guaranteed.

Fundamentally, I don't think there's any value to being able to guarantee complete immutability. If there was, we would have never gotten rid of the Freeze trait. That's precisely what it guaranteed, but it was decided that all we really cared about was thread safety, not immutability, which is why we replaced it with Share.

If you need to guarantee complete immutability for whatever reason, then you simply need to understand the semantics of the types you're working with. Which you should be doing anyway, so there's no extra burden here.

I don't think we're in significant disagreement here. All I'm saying is that & and &mut provide pretty poor guarantees regarding mutability, but ironclad guarantees regarding aliasability. If you want to understand how objects will be mutated, you need to read the documentation. The names, however, imply much stronger guarantees. All I'm suggesting is that the names be aligned with the guarantees that are provided.

@lilyball
Copy link
Contributor

@DaGenix You're still ignoring the fact that the programmer wants mutability. Non-aliasability is almost never a concern of the programmer, it's only a property that's enforced in order to provide safe mutability. You're also continuing to assert that & is only immutable sometimes. That's wrong, it's immutable always. The important thing to note is that it is shallowly immutable, whereas you're making the unstated assumption that "immutable" means deeply immutable. And the one time you address this point, you make a statement without evidence that I cannot agree with:

I make claim that no one intuitively expects shallow immutability; deep immutability is what is expected.

Why is deep immutability expected? & is just a reference. If it's a reference to a type with interior mutability, that type still has interior mutability. The C equivalent would be a T const * pointer. The const there only provides shallow immutability for the type. As a trivial example, if T is int*, then the full type is int * const *, and that's a pointer to a const pointer to an int. I can still mutate the underlying int even though I can't mutate the pointer to it. And yet nobody accuses C's const keyword of not providing "deep constness".

In current Rust, its perfectly valid to have a &mut pointer to a type that provides no method to mutate it.

That makes no sense. You don't need methods to mutate &mut references. If you did, how would you implement the first method? You can mutate a &mut reference merely by dereferencing and assigning:

let mut x = 3i
let y = &mut x;
*y = 4i;

This mutates the value without using methods.

And &mut provides exactly the same guarantees about mutability as it does about non-aliasability. You claim the non-aliasability guarantees are ironclad but the mutability are not. The only way &mut isn't mutable is if you wrap it in another & pointer, e.g. &&mut. But, surprise surprise, this also breaks the non-aliasability guarantee. In fact, the breaking of the non-aliasability guarantee is precisely why &&mut disables mutability. And it's not because of any notion of non-aliasability leading to mutability, but because the non-aliasability guarantee is enforced as a means to make mutability safe, and therefore when it can't be enforced, mutability isn't safe.

Can you come up with an example where &mut guarantees mutability without guaranteeing non-aliasability?

This question doesn't make sense. The answer being "no" does not at all imply that non-aliasability is the fundamental guarantee. It merely implies that non-aliasability is a required property that must be enforced in order to make mutation safe. I can flip it around as well, asking "Can you come up with an example where &mut guarantees non-aliasability without guaranteeing mutability?". And the answer is "no" to that as well.


In the end, I expect that renaming to &my is probably the best approach. It seems to satisfy the arguments against &mut without hitting the serious flaws of &only.

@thestinger
Copy link

I don't think deep immutability makes sense. An I/O handle is used to mutate memory somewhere, so it would need to be mutable... it's far more sensible for it to be based on ownership. The same ownership-based mutability rules will be around for &only even if this change is made and there is no more mut for local variables.

@DaGenix
Copy link
Author

DaGenix commented May 12, 2014

@kballard

You're still ignoring the fact that the programmer wants mutability.

They may want mutability, but what they are getting is non-aliasability.

Non-aliasability is almost never a concern of the programmer, it's only a property that's enforced in order to provide safe mutability.

Non-aliasability is one of the very core things Rust provides. Its the mechanism that provides for memory safety. That guarantee should be front and center, not hidden behind a term that's highly confusing.

You're also continuing to assert that & is only immutable sometimes. That's wrong, it's immutable always. The important thing to note is that it is shallowly immutable, whereas you're making the unstated assumption that "immutable" means deeply immutable. And the one time you address this point, you make a statement without evidence that I cannot agree with:

I make claim that no one intuitively expects shallow immutability; deep immutability is what is expected.

I don't have the resources to run a comprehensive study on the subject. Are you saying that if someone tells you that something is immutable that you are expecting shallow immutability? Given an arbitrary type, what benefit does that provide you for reasoning about its mutability? Can you pass a reference to it to another method and be sure it won't be changed?

Why is deep immutability expected? & is just a reference. If it's a reference to a type with interior mutability, that type still has interior mutability. The C equivalent would be a T const * pointer. The const there only provides shallow immutability for the type. As a trivial example, if T is int*, then the full type is int * const *, and that's a pointer to a const pointer to an int. I can still mutate the underlying int even though I can't mutate the pointer to it. And yet nobody accuses C's const keyword of not providing "deep constness".

In current Rust, its perfectly valid to have a &mut pointer to a type that provides no method to mutate it.

That makes no sense. You don't need methods to mutate &mut references. If you did, how would you implement the first method? You can mutate a &mut reference merely by dereferencing and assigning:

let mut x = 3i
let y = &mut x;
*y = 4i;

This mutates the value without using methods.

What about structs with all private fields? How can you mutate them without methods?

And &mut provides exactly the same guarantees about mutability as it does about non-aliasability. You claim the non-aliasability guarantees are ironclad but the mutability are not. The only way &mut isn't mutable is if you wrap it in another & pointer, e.g. &&mut. But, surprise surprise, this also breaks the non-aliasability guarantee. In fact, the breaking of the non-aliasability guarantee is precisely why &&mut disables mutability. And it's not because of any notion of non-aliasability leading to mutability, but because the non-aliasability guarantee is enforced as a means to make mutability safe, and therefore when it can't be enforced, mutability isn't safe.

The &mut is the only reference to the object. There is no other way to get to that object except through that &mut. However, there are multiple paths to get to the &mut. So, the in effect, the value behind the &mut is aliased. Because its aliased, it can't be mutated. So, you have &mut that can't be used to mutate memory. This seems to prove my point, that &mut != mutable.

Can you come up with an example where &mut guarantees mutability without guaranteeing non-aliasability?

This question doesn't make sense. The answer being "no" does not at all imply that non-aliasability is the fundamental guarantee. It merely implies that non-aliasability is a required property that must be enforced in order to make mutation safe. I can flip it around as well, asking "Can you come up with an example where &mut guarantees non-aliasability without guaranteeing mutability?". And the answer is "no" to that as well.

Lets examine the following code:

struct Something {
    value: int
}

impl Something {
    fn new(val: int) -> Something {
        Something { value: val }
    }
    fn get(&self) -> int { self.val }
}

Even if you have a &mut reference to it, you still can't mutate it.

In the end, I expect that renaming to &my is probably the best approach. It seems to satisfy the arguments against &mut without hitting the serious flaws of &only.

@DaGenix
Copy link
Author

DaGenix commented May 12, 2014

To be clear, I'm not saying that anything is wrong with the semantics of Rust. All I'm saying is that &mut doesn't do a good job of reflecting what those semantics are. I'm suggesting is that &mut be renamed to better reflect what the compiler is actually doing.

@thestinger Just to make sure we're on the same page, I'm not saying that deep immutability does make sense in regards to Rust. All I'm saying is that Rust doesn't provide it.

@lilyball
Copy link
Contributor

@DaGenix

They may want mutability, but what they are getting is non-aliasability.

They're getting both. Non-aliasability is a prerequisite for safe mutability. When the compiler cannot guarantee non-aliasability, it disables mutability (e.g. with &&mut T). You could flip it around and say "They may want non-aliasability, but what they are getting is mutability" and it would be equally valid, but it doesn't make as much sense because, in most cases, the programmer doesn't care about non-aliasability, only about safe mutability.

Non-aliasability is one of the very core things Rust provides. Its the mechanism that provides for memory safety. That guarantee should be front and center, not hidden behind a term that's highly confusing.

But the goal of non-aliasability is not non-aliasability. That is a means to an end. It is the mechanism by which Rust provides the safety, but the actual goal is safe mutability.

Are you saying that if someone tells you that something is immutable that you are expecting shallow immutability?

No, but that's not what's going on here. &T is not "a thing that is immutable". &T is an immutable reference to a value of type T. The reference is immutable. If T has inherited mutability, then T will also be immutable. But if T has interior mutability, then T will be mutable. All that depends on what T is. And that's fine.

To compare to C again, if I have a T * const I don't have a "thing that is constant", I have a "const pointer to T". The pointer itself is const, but nobody expects that this means that T is const. And nobody is confused by this.

So basically, the problem is when you and others say that & provides immutability. That's not what it does, and you shouldn't say that. & is merely an immutable reference to a value. And most values have inherited mutability.

What about structs with all private fields? How can you mutate them without methods?

By assigning a new value of that struct type to the mutable reference.

let mut x = OpaqueStruct::with_field(3i);
{
    let y = &mut x;
    *y = OpaqueStruct::with_field(4i);
}
assert_eq!(x.get(), 4i);

[...] So, you have &mut that can't be used to mutate memory. This seems to prove my point, that &mut != mutable.

And if it were called &only then the same problem would exist. You'd have an &only, but it can be accessed from multiple paths. It's effectively aliased. And can be made literally aliased by reborrowing from those multiple paths (which don't know about each other, so reborrowing from one path can't affect the usage of the other). So, you have an &only that doesn't guarantee non-aliasability. That proves that &only != non-aliased.

Lets examine the following code: [...] Even if you have a &mut reference to it, you still can't mutate it.

See previous answer. I can absolutely mutate the value being pointed to, merely by reassigning another Something value to it.


What I'm trying to prove here is that every valid argument you have against &mut is also an argument against &only (note: your claims that you can't mutate a &mut value without methods is trivially falsifiable and therefore not valid). Since mutability is the goal of the programmer rather than non-aliasability (which, don't get me wrong, is important, but only as a means to the end that is safe mutability), of the two &mut is the more sensible name.

However, the more we argue about it, the more I think &my makes sense as the correct name. If you want mutability or you want non-aliasability, what you really want is to borrow ownership. If you are borrowing ownership, you're guaranteed that you're the only one to have this borrow, and you're also allowed to mutate it. This also makes &&my T make more sense, because in that case, you're not borrowing ownership. You have a non-owning borrow of someone else's borrowed ownership. But they still own it, not you, which means you can't mutate it.

I think it's easier to explain mutability and non-aliasability as a consequence of borrowed ownership, than to explain one as a consequence of the other. And in that light, I'm declaring myself now as fully in favor of &my as the new name (as opposed to it being my second choice behind &mut). The only real problem is what terminology to use to describe something that isn't &my, because "non-ownership-borrowing" is quite a mouthful (e.g. as "immutable" is the opposite of "mutable", and "aliasing" is the opposite of "non-aliasing", what's the opposite of "ownership-borrowing"?).

@thestinger
Copy link

I like &mut because it works the same way as mut in locals, and I don't think we should change only one. Any complaint about &mut also applies to the use of mut in local variables so I don't think they should be tackled as a separate issue. Cell in an immutable local variable works the same way as with &T, so Rust is consistent in how it approaches this right now. The mut on locals and &mut for references refers to inherited mutability, while inner mutability is a separate system.

@pcwalton
Copy link
Contributor

Not necessarily; locals are always unique by dint of being locals, while
& is not always unique.

@thestinger
Copy link

@pcwalton: I mean that we're using mut on locals to mean inherited mutability just as we are with &mut, and inner mutability is separate. If it's going to change for references, it should also change for variables, so I don't think it makes sense to talk about &mut -> &only in isolation.

@bgamari
Copy link

bgamari commented May 12, 2014

In my opinion, what the programmer "wants" in this context isn't terribly relevant. The keyword should reflect what the compiler verifies. To do otherwise unnecessarily conflates concerns and while the two notions are often related, speaking from experience I can say that this does lead to confusion. I, and judging by the Reddit thread, many others, was quite perplexed by the exact meaning of mut for quite some time while picking up Rust.

It is probably true that users typically want mutability more often than uniqueness. However, with proper documentation it takes very little effort for the user to learn that safe mutability is enabled by uniqueness. In exchange for having to make this connection the user is rewarded with a more accurate mental model of what the language is doing for them.

@dobkeratops
Copy link

Could it be seen as an issue of library design; breaking the hint of immutability/ mutability, and the deep mutability - an idea we're already familiar with from c++. Rust as it stands today is closer to the ideal sitaution (expressing the difference between inputs and outputs, and allowing compiler optimizations without resitrict plus messy semantics) . I would rather see mut stay and get tightened up if its posible, and use other keywords and/or unsafe code to go beyond this.

@DaGenix
Copy link
Author

DaGenix commented May 13, 2014

@kballard

They may want mutability, but what they are getting is non-aliasability.

They're getting both. Non-aliasability is a prerequisite for safe mutability. When the compiler cannot guarantee non-aliasability, it disables mutability (e.g. with &&mut T). You could flip it around and say "They may want non-aliasability, but what they are getting is mutability" and it would be equally valid, but it doesn't make as much sense because, in most cases, the programmer doesn't care about non-aliasability, only about safe mutability.

Non-aliasability is one of the very core things Rust provides. Its the mechanism that provides for memory safety. That guarantee should be front and center, not hidden behind a term that's highly confusing.

But the goal of non-aliasability is not non-aliasability. That is a means to an end. It is the mechanism by which Rust provides the safety, but the actual goal is safe mutability.

The goal is often mutability, but I believe there are cases where non-aliasability is the goal, and &mut provides that even in cases where no mutability is involved. I believe that the term &mut is extremely confusing here.

Are you saying that if someone tells you that something is immutable that you are expecting shallow immutability?

No, but that's not what's going on here. &T is not "a thing that is immutable". &T is an immutable reference to a value of type T. The reference is immutable. If T has inherited mutability, then T will also be immutable. But if T has interior mutability, then T will be mutable. All that depends on what T is. And that's fine.

I don't disagree that that is fine. What I disagree with is the current terminology around it. I don't think this distinction is particularly useful when trying to reason about immutability. I feel like the current terminology makes it sound like the guarantees that are provided by the types regarding mutability are far stronger than they actually are.

To compare to C again, if I have a T * const I don't have a "thing that is constant", I have a "const pointer to T". The pointer itself is const, but nobody expects that this means that T is const. And nobody is confused by this.

Nobody is confused by this? This is a very strong statement. I'll make the also unsubstantiated claim that const in C / C++ is often times quite confusing, especially when you get into the const-ness of the pointer vs. the memory that the pointer is referencing.

So basically, the problem is when you and others say that & provides immutability. That's not what it does, and you shouldn't say that. & is merely an immutable reference to a value. And most values have inherited mutability.

I don't say that & is immutable. At least not anymore. When first learning the language, that is what I believed. And thinking about & as immutable is extremely confusing when trying to make sense of types like Cell or AtomicInt.

What about structs with all private fields? How can you mutate them without methods?

By assigning a new value of that struct type to the mutable reference.

let mut x = OpaqueStruct::with_field(3i);
{
let y = &mut x;
*y = OpaqueStruct::with_field(4i);
}
assert_eq!(x.get(), 4i);

[...] So, you have &mut that can't be used to mutate memory. This seems to prove my point, that &mut != mutable.

This is an excellent point. I stayed up much too late when trying to respond yesterday. I don't think this disproved my argument that aliasability is more fundamental, but, touché, well done.

And if it were called &only then the same problem would exist. You'd have an &only, but it can be accessed from multiple paths. It's effectively aliased. And can be made literally aliased by reborrowing from those multiple paths (which don't know about each other, so reborrowing from one path can't affect the usage of the other). So, you have an &only that doesn't guarantee non-aliasability. That proves that &only != non-aliased.

The &only is still the only direct pointer to the location. Its the &only itself that is aliased. I don't believe that the ramifications of this are confusing.

What I'm trying to prove here is that every valid argument you have against &mut is also an argument against &only (note: your claims that you can't mutate a &mut value without methods is trivially falsifiable and therefore not valid). Since mutability is the goal of the programmer rather than non-aliasability (which, don't get me wrong, is important, but only as a means to the end that is safe mutability), of the two &mut is the more sensible name.

However, the more we argue about it, the more I think &my makes sense as the correct name. If you want mutability or you want non-aliasability, what you really want is to borrow ownership. If you are borrowing ownership, you're guaranteed that you're the only one to have this borrow, and you're also allowed to mutate it. This also makes &&my T make more sense, because in that case, you're not borrowing ownership. You have a non-owning borrow of someone else's borrowed ownership. But they still own it, not you, which means you can't mutate it.

I think it's easier to explain mutability and non-aliasability as a consequence of borrowed ownership, than to explain one as a consequence of the other. And in that light, I'm declaring myself now as fully in favor of &my as the new name (as opposed to it being my second choice behind &mut). The only real problem is what terminology to use to describe something that isn't &my, because "non-ownership-borrowing" is quite a mouthful (e.g. as "immutable" is the opposite of "mutable", and "aliasing" is the opposite of "non-aliasing", what's the opposite of "ownership-borrowing"?).

I don't think we're really in disagreement here, even if our reasoning is very different. I opened up this RFC because I think that &mut is a very bad name. I don't especially love &my, however, I think its significantly better than &mut. If you're in favour of doing a renaming, then I'm quite happy that we're on the same side.

@lilyball
Copy link
Contributor

The goal is often mutability, but I believe there are cases where non-aliasability is the goal, and &mut provides that even in cases where no mutability is involved. I believe that the term &mut is extremely confusing here.

So, even though 99% of the time mutability is the goal, and 1% non-aliasability is the goal (and I think even that's massively overestimating it), you believe that &only is better than &mut because it emphasizes the non-aliasability instead of the mutability.

Surely that cannot be what you actually believe, because that is preposterous. But that's what you're arguing here.

Nobody is confused by this? This is a very strong statement.

And yet it has vastly more evidence than your completely made-up claim about the opposite. In all my years as a programmer, I have never seen anyone admit to confusion about a const pointer not making the pointed-to value const. I have never seen a programming tutorial that even bothers to try and explain this concept. I have never even seen the idea raised, anywhere, that const is a confusing concept.

Of course, this is all anecdotal, but short of a study on the subject (which I doubt exists), it's the only evidence that exists. But your counter-claim has no evidence for it at all. You just made it up to try and refute my claim.

I don't say that & is immutable. At least not anymore. When first learning the language, that is what I believed.

And that may be at the root of the problem. I don't know if you used the official documentation, or third-party tutorials, but the official documentation is rather deficient. If we're teaching programmers that & explicitly means "this is an immutable value", then we're teaching wrong. That says nothing about whether &mut needs to be renamed, it only says that our documentation needs to be fixed.

& and &mut actually reflect local bindings pretty well. A local binding let a = ... is an immutable variable. But the value itself may contain interior mutability, or it may have inherited mutability. If it's the latter, the entire value is immutable. If it's the former, then the value contains mutability despite being stored in an immutable variable. Similarly, if I have a let mut b = ... binding, this is a mutable variable, and any types that have inherited mutability are therefore mutable.

Nobody has argued (so far) that this is a confusing concept. Because it's not. It's rather simple. Two types of value mutability: inherited mutability, and interior mutability. Two types of slots: immutable and mutable.

And two types of references. Immutable references, which correspond to immutable slots (in that values with inherited mutability are immutable and values with interior mutability still contain mutability). And mutable references, which correspond to mutable slots (in that values with inherited mutability are mutable).

The only wart here is &&mut T, which is unfortunate, but also not particularly relevant as this wart is not being claimed as the reason to rename &mut.

The &only is still the only direct pointer to the location. Its the &only itself that is aliased. I don't believe that the ramifications of this are confusing.

fn main() {
    let mut x = 3i;
    let onlyx = &only x;
    println!("onlyx: &only int = {}", onlyx as *only _);
    let refonlyx = &onlyx;
    let refonlyx_ = refonlyx; // copy the &T pointer, which is always legal
    // both refonlyx and refonlyx_ are unborrowed and legally accessible right now
    let refx = &**refonlyx_;
    // refx is now an &int containing the same pointer value as onlyx
    // and yet, refonlyx is un-borrowed.
    // So I can access my &only pointer via *refonlyx
    // while simultaneously accessing the exact same pointer via refx.
    // Voilá, I have now aliased my &only pointer.
    // This is precisely the hole that requires &&mut T to be non-mutable.
    // Calling it &only does not change the fact that I just aliased it.
    println!("*refonlyx: &only int = {}", *refonlyx as *only _);
    println!("refx: &int = {}", refx as *_);
}

If you're in favour of doing a renaming, then I'm quite happy that we're on the same side.

I'm currently in favor of renaming to &my, but I am strongly against renaming it to &only. And I'm just as happy to leave it as &mut as I am to see it renamed to &my.

@thestinger
Copy link

I think it's entirely sensible for mut to mean mutable via inherited mutability. It's fine for there to be a separate library-based concept of inner mutability used by a few types (Cell, RefCell, Mutex, RWLock) as it doesn't make mut a lie. There are mutable fields in C++, and they're only omitted from D because it lacks an equivalent to Share for thread safety.

The existence of inherently mutable types without the requirement of inheriting mutability from the owner isn't hard to explain. It might feel impure (it doesn't to me) but it's not something people struggle with.

An RFC about renaming &mut needs to explain what will happen with mut elsewhere. Even if &mut is renamed, there will still be a mut concept for variables and inherently mutable types where mut is not required. It doesn't accomplish anything if it adds a new keyword and yet leaves the mutability system elsewhere intact with the same mut keyword.

@thestinger
Copy link

I also don't see the & &mut T issue as a compelling one. It's really no different than let mut x = 5; let y = &x preventing the mutation of x. The borrow checker rules are something layered on top of the ownership-based mutability system, and the & &mut T rule is one of the simplest bits to learn.

@bstrie
Copy link
Contributor

bstrie commented May 14, 2014

Rumblings suggest that @nikomatsakis is preparing his own RFC that would supersede this one, yes? If so, then we should probably close this one to save us all from wasting our breath.

@DaGenix
Copy link
Author

DaGenix commented May 14, 2014

@bstrie I assume that the RFC being worked on by @nikomatsakis will likely be much more comprehensive that what I have here. I'm also going to make the guess that its going to advocate significantly more invasive changes that just a simple renaming. So, I suspect that there could be reasons that his RFC might not pan out that don't necessarily invalidate the argument to do the renaming I'm proposing. That being said, I agree that his RFC supersedes this one until its fate is decided.

@DaGenix
Copy link
Author

DaGenix commented May 14, 2014

@kballard You can't simultaneous say my argument is nonsense because my subjective claims aren't substantiated (which I fully admitted when I made them) and then follow them up with equally unsubstantiated but significantly more inflammatory claims of your own. I'm happy to continue discussing this topic, but if you are going to argue

Surely that cannot be what you actually believe, because that is preposterous. But that's what you're arguing here.

maybe its time we stop talking about this.

@lilyball
Copy link
Contributor

@DaGenix @nikomatsakis has posted his proposal as a blog post. Presumably it will become an RFC if it has support following discussion.

As for nonsense, I called your argument nonsense because you fabricated it purely to disagree with me. My claim has significant anecdotal evidence. Your claim has none.

If you want to not talk about your proposal to rename &mut, then maybe you shouldn't have one. I recognize that I have strong beliefs on this subject and are arguing quite loudly about it, but I don't think I'm being unreasonable in my arguments. I do, however, think that the arguments you put forth that I called nonsense and preposterous are unreasonable, and I think you're making them up to try and justify your subjective beliefs that you can't properly quantify.

@liigo
Copy link
Contributor

liigo commented May 14, 2014

Don't like &only, -1
2014年5月14日 上午11:51于 "DaGenix" notifications@github.com写道:

@kballard https://github.com/kballard You can't simultaneous say my
argument is nonsense because my subjective claims aren't substantiated
(which I fully admitted when I made them) and then follow them up with
equally unsubstantiated but significantly more inflammatory claims of your
own. I'm happy to continue discussing this topic, but if you are going to
argue

Surely that cannot be what you actually believe, because that is
preposterous. But that's what you're arguing here.

maybe its time we stop talking about this.


Reply to this email directly or view it on GitHubhttps://github.com//pull/58#issuecomment-43039661
.

@liigo
Copy link
Contributor

liigo commented May 14, 2014

To be clear, I like &mut or &my. Nothing need to be changed currently, IMO.
2014年5月14日 下午1:06于 "Liigo Zhuang" com.liigo@gmail.com写道:

Don't like &only, -1
2014年5月14日 上午11:51于 "DaGenix" notifications@github.com写道:

@kballard https://github.com/kballard You can't simultaneous say my
argument is nonsense because my subjective claims aren't substantiated
(which I fully admitted when I made them) and then follow them up with
equally unsubstantiated but significantly more inflammatory claims of your
own. I'm happy to continue discussing this topic, but if you are going to
argue

Surely that cannot be what you actually believe, because that is
preposterous. But that's what you're arguing here.

maybe its time we stop talking about this.


Reply to this email directly or view it on GitHubhttps://github.com//pull/58#issuecomment-43039661
.

@pnkfelix
Copy link
Member

@DaGenix @kballard I have seen hyperbolic and inflammatory remarks in your dialogue above.

I recommend both of you review the conduct section of the community participation guidelines before writing another response to each other.

https://github.com/mozilla/rust/wiki/Note-development-policy#conduct

@gulbanana
Copy link

On the topic of shallow vs deep immutability- surely this is not a confusing concept to anyone other than novices? Any programmer familiar with popular VM languages, such as Java and C#, will understand the distinction between a reference and a value that is a copy of that reference.

We are all used to passing a Foo to f(Foo x), in the understanding that x is a unique local copy of the reference, not of the Foo - that updating Foo's fields will be modifying memory shared between the outer and inner stack frames. Languages with references like C++ and Rust add the concept of passing a Foo&, but this is not complex in and of itself; it is simply a conceptual shorthand for passing a Bar that contains a Foo (and a useful performance optimisation, since the Bar/"&" does not actually exist).

When writing languages like Scala I find the val/var distinction extremely useful and almost all my bindings are vals; this is despite the fact that Scala is a JVM language with deep mutability of everything. The shallow immutability of local bindings - and the corresponding shallow immutability of &mut in Rust - is useful completely independent of its effect on aliasing. Or rather, Rust's aliasing prevention is useful primarily as a way to guarantee it.

@thestinger
Copy link

See #78 (comment). If you want transitive immutability, then &uniq is is the opposite of what you're looking for. It was proposed by @nikomatsakis to allow mutation through immutable references to immutable variables containing mutable references in order to permit marking fewer variables as mutable.

@pnkfelix
Copy link
Member

pnkfelix commented Jun 5, 2014

closing; team decided there is not enough interest on going down this route.

@brson
Copy link
Contributor

brson commented Jun 5, 2014

If we decide to do something like this, it will probably take a slightly different form.

@Pzixel
Copy link

Pzixel commented Jul 31, 2018

Any plans to revisit this decision due to Rust 2018? Seems to be the right time to make such a change.

Every meeting I have visited references focusing on ownership article. Every speeker say "Don't trust &mut, it's misleading" and so on. Maybe this issue should be addressed? @alexcrichton

wycats pushed a commit to wycats/rust-rfcs that referenced this pull request Mar 5, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.