-
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
Rename &mut to &only #58
Conversation
This would allow mutation through an arbitrary It seems particularly peculiar that one of the following is valid ( let x = 1;
x = 2;
let y = 1;
let z = &only y;
*z = 2; |
This does have a benefit of a consistent pattern syntax: |
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 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 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 If 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. |
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 - What does What does So, in this case, I think the word 'mut' in |
I do have a tendency to write extremely long comments. So, to put is more succinctly: The real guarantees provided by |
It seems more like the issue is with |
I wonder if there is some value in types like However, this is adding more complication to our pointers. As a point of comparison, does C++ allow |
C++ doesn't have |
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 |
@bstrie I don't think that |
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). |
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. |
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 Regarding how Putting it another way - let's assume that |
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. |
@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 |
@DaGenix I'll try to read the RFC today, but the interaction between |
@nikomatsakis "Orthogonal" wasn't a good word. What I meant is that I hope I could create a reasonable argument for the renaming of |
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):
|
argh, phone commented early. "... called |
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.
|
|
The I don't think that |
@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 |
I think its significantly more confusing that the "immutable reference" type,
At the most basic level, the author uses the types
All I claim is that the only guarantee that
I completely agree. I think the current naming confuses the issue, though. The name
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.
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
Let's say I went and read every line of documentation for
I don't think we're in significant disagreement here. All I'm saying is that |
@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
Why is deep immutability expected?
That makes no sense. You don't need methods to mutate let mut x = 3i
let y = &mut x;
*y = 4i; This mutates the value without using methods. And
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 In the end, I expect that renaming to |
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 |
They may want mutability, but what they are getting is non-aliasability.
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.
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?
What about structs with all private fields? How can you mutate them without methods?
The
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
|
To be clear, I'm not saying that anything is wrong with the semantics of Rust. All I'm saying is that @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. |
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
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.
No, but that's not what's going on here. To compare to C again, if I have a So basically, the problem is when you and others say that
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);
And if it were called
See previous answer. I can absolutely mutate the value being pointed to, merely by reassigning another What I'm trying to prove here is that every valid argument you have against However, the more we argue about it, the more I think 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 |
I like |
Not necessarily; locals are always unique by dint of being locals, while |
@pcwalton: I mean that we're using |
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 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. |
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. |
The goal is often mutability, but I believe there are cases where non-aliasability is the goal, and
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.
Nobody is confused by this? This is a very strong statement. I'll make the also unsubstantiated claim that
I don't say that
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.
The
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 |
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 Surely that cannot be what you actually believe, because that is preposterous. But that's what you're arguing here.
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 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.
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
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
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 *_);
}
I'm currently in favor of renaming to |
I think it's entirely sensible for 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 |
I also don't see the |
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. |
@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. |
@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
maybe its time we stop talking about this. |
@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 |
Don't like &only, -1
|
To be clear, I like &mut or &my. Nothing need to be changed currently, IMO.
|
@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 |
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. |
See #78 (comment). If you want transitive immutability, then |
closing; team decided there is not enough interest on going down this route. |
If we decide to do something like this, it will probably take a slightly different form. |
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 |
Helper listing (dashless helpers)
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 asCell
andMutex
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&
.