From aa390571af714bddc8cddb0f71e84fabb3b85ab0 Mon Sep 17 00:00:00 2001 From: Richard Zhang Date: Sat, 13 Sep 2014 12:36:59 +0800 Subject: [PATCH 01/12] Initial comment of the RFC to refine the semantics of immutables. --- active/0000-refine-immutables.md | 149 +++++++++++++++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 active/0000-refine-immutables.md diff --git a/active/0000-refine-immutables.md b/active/0000-refine-immutables.md new file mode 100644 index 00000000000..17fad45e192 --- /dev/null +++ b/active/0000-refine-immutables.md @@ -0,0 +1,149 @@ +- Start Date: 2014-09-13 +- RFC PR: (leave this empty) +- Rust Issue: (leave this empty) + + +# Summary + +This RFC proposes that the semantics of immutable variables be refined by forbidding partial outbound moves, so: + +1. immutable variables in Rust become more immutable; +2. guaranteed scoped lifetimes for values with move semantics ("movable values") can be achieved. + +# Motivation + +This RFC is motivated by two problems in Rust today: + +## "Immutable" variables are not immutable enough. + +Rust's "immutable" variables do *not* provide *strict immutability*. There are three exceptions: + +1. it is legal to have internal mutability even inside "immutable" variables, via `UnsafeSell`; +2. it is legal to move values from "immutable" variables as a whole; +3. it is legal to move parts of compound values from "immutable" variables. + +So, "immutable" is not exactly accurate, but is it good *enough*? + +Exception 1 can be justified because well, `UnsafeCell` *is* unsafe. +Exception 2 can be justified because the only thing that changes after a *full outbound move* is the value's location, not the value itself. + +But there is a problem: Exception 3 is very hard to justify, as after a *partial outbound move*, the value itself is mutated. + +Consider the following snippet: + +```rust +#[deriving(Show)] +enum Gender { Male, Female } + +#[deriving(Show)] +struct Person { + name: String, + gender: Gender, +} + +fn main() { + // not supposed to change: + let person = Person { name: "Mike".to_string(), gender: Male }; + + // person.name = "Clark"; // compile error + // person.gender = Female; // compile error + + // seemed innocent: + match person { + Person { name: n, gender: Male } => println!("I am {}, a man!", n), + Person { name: n, gender: Female } => println!("I am {}, a woman.", n), + } + + // but the name was moved: + // println!("Oh yes, I am {}!", person.name); // compile error + + // the value as a whole, was rendered unusable: + // println!("Hey! I am {}!", person); // compile error + + // though the other part can still be used: + println!("What?! I am still a {}!", person.gender); +} +``` + +This snippet compiles and runs - and that's the problem. + +There were partial outbound moves happening in the match arms, which was unexpected for two reasons: + +1. `person` was supposed to be *immutable*; +2. even if the programmer knew that `person` is not strictly immutable, it was likely that he/she may not expect the innocent looking matches to move his name - he should have used `ref n`, not bare `n`. + +So, forbidding partial moves from immutable variables can have the following benefits: + +1. the semantics of immutable variables will be less against programmer intuitions; +2. certain kinds of unexpected moves will be prevented on spot. + +## Guaranteed lifetimes for movable values + +In [RFC PR 210: Static drop semantics](https://github.com/rust-lang/rfcs/pull/210), `NoisyDrop`/`QuietDrop` are proposed to help programmers identify unwanted implicit drops introduced by the new semantics. The reason that some so-called "early" drops (or "implicit balancing drops") are unwanted is because the programmer, for whatever reason, wants to ensure that some movable values have certain guaranteed lifetimes. + +`NoisyDrop`/`QuietDrop` can help, but: + +1. the necessity of guaranteed lifetimes depends heavily on context, and should be decided by the application programmer on a case-by-case basis, not by library types; +2. for all their troubles, `NoisyDrop`/`QuietDrop` still cannot guard against all the possibilities that would threaten the guarantee. + +Because moves transfer ownership and lifetime control to the receiver, and the original owner can guarantee nothing once ownership is transferred. This is true regardless of where the moves happen or if they are balanced or not, or if they are implicit or not (a drop can be seen as a special kind of move - a move into oblivion). + +Therefore: + +**The only way to guarantee that a value has a certain lifetime is to maintain ownership and do not move it before the intended drop point.** + +The above is true regardless of which drop semantics is used. Dynamic drops? Static drops? Even eager drops? Doesn't matter. + +Then, how can this be done? + +It is actually quite simple: if guaranteed lifetime is necessary, then explicitly call `drop` at the intended drop point. + +Because if unexpected moves happen before the explicit drop, a compile error is guaranteed happen, though the compiler will complain about the explicit drop, not the unexpected moves. However in practice this is not a problem. + +Let's call this *value pinning*. + +There is still a problem with this form of pinning, as it is *shallow* in that only the *root* value is pinned, but partial moves from the value is still allowed. Shallow pinning cannot guard against the possibility of losing lifetime control of parts of a compound value. + +That's when our refined immutable variables come into play: + +By combining explicit drops and refined immutable variables, *deep* pinning and truly guaranteed lifetime for movable values can be achieved. + +# Detailed Design + +To forbid partial outbound moves from immutable variables, only one rule is needed to be added to Rust's mutation control semantics: + +* Outbound moves from inherently immutable struct fields or enum variant fields are forbidden. + +After the change, a programmer will be required to use mutable variables if he/she truly wants partial outbound moves. + +# Drawbacks + +Breaking change. `mut`s and `ref`s may have to be added. + +The RFC author (@CloudiDust) believes this to be generally a plus. Rust prefers doing things (reasonably) explicitly after all. Partial outbound moves are mutations to the parent values, and should have been labelled as such. Currently they just slip past the radar. + +The problem with using more `mut` is that: `mut` dictates no mutation restrictions at all, and it is not clear what kind of mutation the programmer actually wants when he/she writes `mut`. Inbound copies? Or inbound moves? Or outbound moves? Fully or partially? + +Note this problem already exists in Rust today. + +Actually, immutable and mutable variables each codify a commonly used mutation control policy, bBut there are many more. It may be beneficial to support more fine grained mutation control, but this is outside the scope of this RFC. The RFC author already has some ideas on the design for that feature, the design is backwards-compatible (other than that it requires adding a `pin` or `pinned` keyword), so it can be postponed, but it relies on this RFC being accepted. + +# Alternatives + +Alternative 1. Instead of forbidding partial moves from immutable variables, forbid reading any remaining part of a partially moved value (either mutable or immutable), but still allow the remaining parts to be moved. + +This was suggested when this RFC was still a pre-RFC. This new rule would help finding out unexpected partial outbound moves in some cases. However, a value that can be moved but not read doesn't make sense. + +Alternative 2. Maintain the status quo. + +It can be argued that, because reading partially-moved values (as a whole) or empty slots are compile errors, many of the bugs caused by unexpected partial moves from immutable variables will "eventually" be caught, so the status quo may not be *that* bad in practice. But it is still better to catch more bugs on spot, and calling partially moved values "not mutated", is hardly justifiable. Also there will be no way to deeply pin a value. + +Alternative 3. Go all the way and forbid full outbound moves from immutable variables as well. + +This would make immutable variables even more true to their names, and effectively this is deeply pinning all immutable values. But this is too restrictive and unnecessary. Having to throw `mut` everywhere defeats the purpose of the mutable/immutable distinction. Also, unexpected full outbound moves are easier to catch than unexpected partial outbound moves as compile errors will happen more often. + +The RFC author considers the proposed change in this RFC to be a reasonable compromise between the alternatives 2 and 3. + +# Unsolved Questions + +None. From 8125456fc09c0b30964b7948f6d707d15b0d8b55 Mon Sep 17 00:00:00 2001 From: Richard Zhang Date: Sat, 13 Sep 2014 12:43:20 +0800 Subject: [PATCH 02/12] Some formatting refinements. --- active/0000-refine-immutables.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/active/0000-refine-immutables.md b/active/0000-refine-immutables.md index 17fad45e192..1760355d717 100644 --- a/active/0000-refine-immutables.md +++ b/active/0000-refine-immutables.md @@ -112,7 +112,7 @@ By combining explicit drops and refined immutable variables, *deep* pinning and To forbid partial outbound moves from immutable variables, only one rule is needed to be added to Rust's mutation control semantics: -* Outbound moves from inherently immutable struct fields or enum variant fields are forbidden. +**Outbound moves from inherently immutable struct fields or enum variant fields are forbidden.** After the change, a programmer will be required to use mutable variables if he/she truly wants partial outbound moves. @@ -124,21 +124,21 @@ The RFC author (@CloudiDust) believes this to be generally a plus. Rust prefers The problem with using more `mut` is that: `mut` dictates no mutation restrictions at all, and it is not clear what kind of mutation the programmer actually wants when he/she writes `mut`. Inbound copies? Or inbound moves? Or outbound moves? Fully or partially? -Note this problem already exists in Rust today. +Note this problem *already exists* in Rust today. Actually, immutable and mutable variables each codify a commonly used mutation control policy, bBut there are many more. It may be beneficial to support more fine grained mutation control, but this is outside the scope of this RFC. The RFC author already has some ideas on the design for that feature, the design is backwards-compatible (other than that it requires adding a `pin` or `pinned` keyword), so it can be postponed, but it relies on this RFC being accepted. # Alternatives -Alternative 1. Instead of forbidding partial moves from immutable variables, forbid reading any remaining part of a partially moved value (either mutable or immutable), but still allow the remaining parts to be moved. +**Alternative 1.** Instead of forbidding partial moves from immutable variables, forbid reading any remaining part of a partially moved value (either mutable or immutable), but still allow the remaining parts to be moved. This was suggested when this RFC was still a pre-RFC. This new rule would help finding out unexpected partial outbound moves in some cases. However, a value that can be moved but not read doesn't make sense. -Alternative 2. Maintain the status quo. +**Alternative 2.** Maintain the status quo. It can be argued that, because reading partially-moved values (as a whole) or empty slots are compile errors, many of the bugs caused by unexpected partial moves from immutable variables will "eventually" be caught, so the status quo may not be *that* bad in practice. But it is still better to catch more bugs on spot, and calling partially moved values "not mutated", is hardly justifiable. Also there will be no way to deeply pin a value. -Alternative 3. Go all the way and forbid full outbound moves from immutable variables as well. +**Alternative 3.** Go all the way and forbid full outbound moves from immutable variables as well. This would make immutable variables even more true to their names, and effectively this is deeply pinning all immutable values. But this is too restrictive and unnecessary. Having to throw `mut` everywhere defeats the purpose of the mutable/immutable distinction. Also, unexpected full outbound moves are easier to catch than unexpected partial outbound moves as compile errors will happen more often. From 13c5f897a48c35b4259249d07eebe489a13e9266 Mon Sep 17 00:00:00 2001 From: Richard Zhang Date: Sat, 13 Sep 2014 12:49:55 +0800 Subject: [PATCH 03/12] Adjust wording of the second motivating problem. --- active/0000-refine-immutables.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/active/0000-refine-immutables.md b/active/0000-refine-immutables.md index 1760355d717..e66e2088509 100644 --- a/active/0000-refine-immutables.md +++ b/active/0000-refine-immutables.md @@ -77,7 +77,7 @@ So, forbidding partial moves from immutable variables can have the following ben 1. the semantics of immutable variables will be less against programmer intuitions; 2. certain kinds of unexpected moves will be prevented on spot. -## Guaranteed lifetimes for movable values +## Guaranteed lifetimes for movable values cannot be expressed. In [RFC PR 210: Static drop semantics](https://github.com/rust-lang/rfcs/pull/210), `NoisyDrop`/`QuietDrop` are proposed to help programmers identify unwanted implicit drops introduced by the new semantics. The reason that some so-called "early" drops (or "implicit balancing drops") are unwanted is because the programmer, for whatever reason, wants to ensure that some movable values have certain guaranteed lifetimes. From fa02e3911e296a177cc8bbe5116749ec4a41ae8c Mon Sep 17 00:00:00 2001 From: Richard Zhang Date: Sat, 13 Sep 2014 12:58:07 +0800 Subject: [PATCH 04/12] The guaranteed lifetimes need not to be scoped. --- active/0000-refine-immutables.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/active/0000-refine-immutables.md b/active/0000-refine-immutables.md index e66e2088509..9cb1557e55e 100644 --- a/active/0000-refine-immutables.md +++ b/active/0000-refine-immutables.md @@ -8,7 +8,7 @@ This RFC proposes that the semantics of immutable variables be refined by forbidding partial outbound moves, so: 1. immutable variables in Rust become more immutable; -2. guaranteed scoped lifetimes for values with move semantics ("movable values") can be achieved. +2. guaranteed lifetimes for values with move semantics ("movable values") can be achieved. # Motivation @@ -106,7 +106,7 @@ There is still a problem with this form of pinning, as it is *shallow* in that o That's when our refined immutable variables come into play: -By combining explicit drops and refined immutable variables, *deep* pinning and truly guaranteed lifetime for movable values can be achieved. +By combining explicit drops and refined immutable variables, *deep* pinning and truly guaranteed lifetimes for movable values can be achieved. # Detailed Design From 00958c2ab3e06b289e28ae3fddc9720eabd2ffb8 Mon Sep 17 00:00:00 2001 From: Richard Zhang Date: Sat, 13 Sep 2014 13:10:54 +0800 Subject: [PATCH 05/12] Typo correction. --- active/0000-refine-immutables.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/active/0000-refine-immutables.md b/active/0000-refine-immutables.md index 9cb1557e55e..1d6108d5da4 100644 --- a/active/0000-refine-immutables.md +++ b/active/0000-refine-immutables.md @@ -126,7 +126,7 @@ The problem with using more `mut` is that: `mut` dictates no mutation restrictio Note this problem *already exists* in Rust today. -Actually, immutable and mutable variables each codify a commonly used mutation control policy, bBut there are many more. It may be beneficial to support more fine grained mutation control, but this is outside the scope of this RFC. The RFC author already has some ideas on the design for that feature, the design is backwards-compatible (other than that it requires adding a `pin` or `pinned` keyword), so it can be postponed, but it relies on this RFC being accepted. +Actually, immutable and mutable variables each codify a commonly used mutation control policy, but there are many other possibilities. It may be beneficial to support more fine grained mutation control, but this is outside the scope of this RFC. The RFC author already has some ideas on the design for that feature, the design is backwards-compatible (other than that it requires adding a `pin` or `pinned` keyword), so it can be postponed, but it relies on this RFC being accepted. # Alternatives From 12b87292a1945348d958ce88b8a88b4729488d26 Mon Sep 17 00:00:00 2001 From: Richard Zhang Date: Sat, 13 Sep 2014 13:12:42 +0800 Subject: [PATCH 06/12] UnsafeSell -> UnsafeCell. --- active/0000-refine-immutables.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/active/0000-refine-immutables.md b/active/0000-refine-immutables.md index 1d6108d5da4..6ceb80bdbd7 100644 --- a/active/0000-refine-immutables.md +++ b/active/0000-refine-immutables.md @@ -18,7 +18,7 @@ This RFC is motivated by two problems in Rust today: Rust's "immutable" variables do *not* provide *strict immutability*. There are three exceptions: -1. it is legal to have internal mutability even inside "immutable" variables, via `UnsafeSell`; +1. it is legal to have internal mutability even inside "immutable" variables, via `UnsafeCell`; 2. it is legal to move values from "immutable" variables as a whole; 3. it is legal to move parts of compound values from "immutable" variables. From d32020245eebec2b6c7e7198e8123eed8c156126 Mon Sep 17 00:00:00 2001 From: Richard Zhang Date: Sat, 13 Sep 2014 14:51:42 +0800 Subject: [PATCH 07/12] Corrections and clarfications. --- active/0000-refine-immutables.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/active/0000-refine-immutables.md b/active/0000-refine-immutables.md index 6ceb80bdbd7..8fa7f71191b 100644 --- a/active/0000-refine-immutables.md +++ b/active/0000-refine-immutables.md @@ -50,7 +50,7 @@ fn main() { // seemed innocent: match person { - Person { name: n, gender: Male } => println!("I am {}, a man!", n), + Person { name: n, gender: Male } => println!("I am {}, a man.", n), Person { name: n, gender: Female } => println!("I am {}, a woman.", n), } @@ -70,7 +70,7 @@ This snippet compiles and runs - and that's the problem. There were partial outbound moves happening in the match arms, which was unexpected for two reasons: 1. `person` was supposed to be *immutable*; -2. even if the programmer knew that `person` is not strictly immutable, it was likely that he/she may not expect the innocent looking matches to move his name - he should have used `ref n`, not bare `n`. +2. even if the programmer knew that `person` is not strictly immutable, it was likely that he/she may not expect the innocent looking matches to move the name - he/she should have used `ref n`, not bare `n`. So, forbidding partial moves from immutable variables can have the following benefits: @@ -100,7 +100,7 @@ It is actually quite simple: if guaranteed lifetime is necessary, then explicitl Because if unexpected moves happen before the explicit drop, a compile error is guaranteed happen, though the compiler will complain about the explicit drop, not the unexpected moves. However in practice this is not a problem. -Let's call this *value pinning*. +Let's call this *value pinning*. This form of pinning would merely be an idiom, and the language itself would not have a concept of pinning. There is still a problem with this form of pinning, as it is *shallow* in that only the *root* value is pinned, but partial moves from the value is still allowed. Shallow pinning cannot guard against the possibility of losing lifetime control of parts of a compound value. @@ -114,7 +114,7 @@ To forbid partial outbound moves from immutable variables, only one rule is need **Outbound moves from inherently immutable struct fields or enum variant fields are forbidden.** -After the change, a programmer will be required to use mutable variables if he/she truly wants partial outbound moves. +After this change, a programmer will be required to use mutable variables if he/she truly wants partial outbound moves. # Drawbacks @@ -140,7 +140,7 @@ It can be argued that, because reading partially-moved values (as a whole) or em **Alternative 3.** Go all the way and forbid full outbound moves from immutable variables as well. -This would make immutable variables even more true to their names, and effectively this is deeply pinning all immutable values. But this is too restrictive and unnecessary. Having to throw `mut` everywhere defeats the purpose of the mutable/immutable distinction. Also, unexpected full outbound moves are easier to catch than unexpected partial outbound moves as compile errors will happen more often. +This would make immutable variables even more true to their names, and effectively this would deeply pin all immutable values and guarantee that they all have scoped lifetimes. But this is too restrictive and unnecessary. Having to throw `mut` everywhere defeats the purpose of the mutable/immutable distinction. Also, unexpected full outbound moves are easier to catch than unexpected partial outbound moves, as compile errors will happen more often. The RFC author considers the proposed change in this RFC to be a reasonable compromise between the alternatives 2 and 3. From f0e502caddcf728283f8cfe0d40b5f1a22977e92 Mon Sep 17 00:00:00 2001 From: Richard Zhang Date: Sat, 13 Sep 2014 16:22:42 +0800 Subject: [PATCH 08/12] More discussions about the ergonomic issues. --- active/0000-refine-immutables.md | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/active/0000-refine-immutables.md b/active/0000-refine-immutables.md index 8fa7f71191b..0c88fb5ae3b 100644 --- a/active/0000-refine-immutables.md +++ b/active/0000-refine-immutables.md @@ -118,15 +118,35 @@ After this change, a programmer will be required to use mutable variables if he/ # Drawbacks -Breaking change. `mut`s and `ref`s may have to be added. +Breaking change. `mut`s and `ref`s may have to be used in more places. -The RFC author (@CloudiDust) believes this to be generally a plus. Rust prefers doing things (reasonably) explicitly after all. Partial outbound moves are mutations to the parent values, and should have been labelled as such. Currently they just slip past the radar. +Particularly, if for some reason, partial outbound moves from immutable values are intentionally requested by a programmer, and he/she needs the values to again be immutable after the moves, then he/she has to use this idiom: + +```rust +let foo = Foo {...}; +... +let mut foo = foo; +move(foo.bar); +let foo = foo; +... +``` + +Instead of simply: + +```rust +let foo = Foo {...}; +... +move(foo.bar); +... +``` + +There may be some ergonomic issues. But the RFC author (@CloudiDust) believes this change to be generally a plus. Rust prefers doing things (reasonably) explicitly after all. Partial outbound moves are mutations to the parent values, and should have been labelled as such. Currently they just slip past the radar. The problem with using more `mut` is that: `mut` dictates no mutation restrictions at all, and it is not clear what kind of mutation the programmer actually wants when he/she writes `mut`. Inbound copies? Or inbound moves? Or outbound moves? Fully or partially? Note this problem *already exists* in Rust today. -Actually, immutable and mutable variables each codify a commonly used mutation control policy, but there are many other possibilities. It may be beneficial to support more fine grained mutation control, but this is outside the scope of this RFC. The RFC author already has some ideas on the design for that feature, the design is backwards-compatible (other than that it requires adding a `pin` or `pinned` keyword), so it can be postponed, but it relies on this RFC being accepted. +Actually, immutable and mutable variables each codify a commonly used mutation control policy, but there are many other possibilities. It may be beneficial to support more fine grained mutation control, but this is outside the scope of this RFC. The RFC author already has some ideas on the design for that feature, *which also solves the ergonomic issues mentioned above*. The design is backwards-compatible (other than that it requires adding a `pin` or `pinned` keyword), so it can be postponed, but it relies on this RFC being accepted. # Alternatives From ffeeb3f7aea4fdc78d8104c3b7a1882c3ab2e3ed Mon Sep 17 00:00:00 2001 From: Richard Zhang Date: Sun, 14 Sep 2014 20:22:59 +0800 Subject: [PATCH 09/12] Revises the RFC by incorporating the suggestions. --- active/0000-refine-immutables.md | 69 ++++++++++---------------------- 1 file changed, 21 insertions(+), 48 deletions(-) diff --git a/active/0000-refine-immutables.md b/active/0000-refine-immutables.md index 0c88fb5ae3b..603336d7130 100644 --- a/active/0000-refine-immutables.md +++ b/active/0000-refine-immutables.md @@ -5,26 +5,24 @@ # Summary -This RFC proposes that the semantics of immutable variables be refined by forbidding partial outbound moves, so: - -1. immutable variables in Rust become more immutable; -2. guaranteed lifetimes for values with move semantics ("movable values") can be achieved. +This RFC proposes that the semantics of immutable variables be refined by forbidding partial outbound moves, so immutable variables in Rust become more immutable. # Motivation -This RFC is motivated by two problems in Rust today: +This RFC is motivated by the following problem in Rust today: ## "Immutable" variables are not immutable enough. Rust's "immutable" variables do *not* provide *strict immutability*. There are three exceptions: -1. it is legal to have internal mutability even inside "immutable" variables, via `UnsafeCell`; +1. it is legal to have internal mutability even inside "immutable" variables, via `UnsafeCell` and the likes; 2. it is legal to move values from "immutable" variables as a whole; 3. it is legal to move parts of compound values from "immutable" variables. So, "immutable" is not exactly accurate, but is it good *enough*? -Exception 1 can be justified because well, `UnsafeCell` *is* unsafe. +Exception 1 can be justified because well, `UnsafeCell` and the likes are explicitly designed for *internal* mutability, and must be opted-in by the programmer. (Also, see the discussion for Alternative 2 below, which explains this one further.) + Exception 2 can be justified because the only thing that changes after a *full outbound move* is the value's location, not the value itself. But there is a problem: Exception 3 is very hard to justify, as after a *partial outbound move*, the value itself is mutated. @@ -33,35 +31,35 @@ Consider the following snippet: ```rust #[deriving(Show)] -enum Gender { Male, Female } +enum Status { Living, Deceased } #[deriving(Show)] struct Person { name: String, - gender: Gender, + status: Status, } fn main() { // not supposed to change: - let person = Person { name: "Mike".to_string(), gender: Male }; + let person = Person { name: "Mike".to_string(), status: Living}; // person.name = "Clark"; // compile error - // person.gender = Female; // compile error + // person.status = Deceased; // compile error // seemed innocent: match person { - Person { name: n, gender: Male } => println!("I am {}, a man.", n), - Person { name: n, gender: Female } => println!("I am {}, a woman.", n), + Person { name: n, status: Living } => println!("{} is alive.", n), + Person { name: n, status: Deceased } => println!("{} is dead.", n), } // but the name was moved: - // println!("Oh yes, I am {}!", person.name); // compile error + // println!("Hi, {}!", person.name); // compile error // the value as a whole, was rendered unusable: - // println!("Hey! I am {}!", person); // compile error + // println!("{}", person); // compile error // though the other part can still be used: - println!("What?! I am still a {}!", person.gender); + println!("The nameless person is still {}.", person.status); } ``` @@ -77,36 +75,7 @@ So, forbidding partial moves from immutable variables can have the following ben 1. the semantics of immutable variables will be less against programmer intuitions; 2. certain kinds of unexpected moves will be prevented on spot. -## Guaranteed lifetimes for movable values cannot be expressed. - -In [RFC PR 210: Static drop semantics](https://github.com/rust-lang/rfcs/pull/210), `NoisyDrop`/`QuietDrop` are proposed to help programmers identify unwanted implicit drops introduced by the new semantics. The reason that some so-called "early" drops (or "implicit balancing drops") are unwanted is because the programmer, for whatever reason, wants to ensure that some movable values have certain guaranteed lifetimes. - -`NoisyDrop`/`QuietDrop` can help, but: - -1. the necessity of guaranteed lifetimes depends heavily on context, and should be decided by the application programmer on a case-by-case basis, not by library types; -2. for all their troubles, `NoisyDrop`/`QuietDrop` still cannot guard against all the possibilities that would threaten the guarantee. - -Because moves transfer ownership and lifetime control to the receiver, and the original owner can guarantee nothing once ownership is transferred. This is true regardless of where the moves happen or if they are balanced or not, or if they are implicit or not (a drop can be seen as a special kind of move - a move into oblivion). - -Therefore: - -**The only way to guarantee that a value has a certain lifetime is to maintain ownership and do not move it before the intended drop point.** - -The above is true regardless of which drop semantics is used. Dynamic drops? Static drops? Even eager drops? Doesn't matter. - -Then, how can this be done? - -It is actually quite simple: if guaranteed lifetime is necessary, then explicitly call `drop` at the intended drop point. - -Because if unexpected moves happen before the explicit drop, a compile error is guaranteed happen, though the compiler will complain about the explicit drop, not the unexpected moves. However in practice this is not a problem. - -Let's call this *value pinning*. This form of pinning would merely be an idiom, and the language itself would not have a concept of pinning. - -There is still a problem with this form of pinning, as it is *shallow* in that only the *root* value is pinned, but partial moves from the value is still allowed. Shallow pinning cannot guard against the possibility of losing lifetime control of parts of a compound value. - -That's when our refined immutable variables come into play: - -By combining explicit drops and refined immutable variables, *deep* pinning and truly guaranteed lifetimes for movable values can be achieved. +(Here used to be a section about how this RFC will help providing guaranteed lifetimes. But it turns out that explicit drops *alone* can achieve that. Please refer to the comments of [RFC PR 210](https://github.com/rust-lang/rfcs/pull/210) for more details.) # Detailed Design @@ -156,11 +125,15 @@ This was suggested when this RFC was still a pre-RFC. This new rule would help f **Alternative 2.** Maintain the status quo. -It can be argued that, because reading partially-moved values (as a whole) or empty slots are compile errors, many of the bugs caused by unexpected partial moves from immutable variables will "eventually" be caught, so the status quo may not be *that* bad in practice. But it is still better to catch more bugs on spot, and calling partially moved values "not mutated", is hardly justifiable. Also there will be no way to deeply pin a value. +It can be argued that, because using partially-moved values (as a whole) or empty slots are compile errors, many of the bugs caused by unexpected partial moves from immutable variables will eventually be caught. Also, types implementing `Drop` are *always* not partially-movable already, so the status quo may not be *that* bad in practice. + +Also, there is a reason that Rust's mutation control semantics are designed the way it is. `mut`/`&mut` are not actually designed around *mutability*, but *exclusive accessibility*. What Rust has is not *mutation control* but *uniqueness and aliasing control*, and `mut` actually means "this is a variable that you can request exclusive access of", not *necessary* "this is a variable that is mutable". So conversely, a variable without the `mut` keyword, is just a variable that "you cannot (statically) request exclusive access of", not *necessary* "a variable that is immutable". (Exclusive/mutable access to immutable values can be requested and checked dynamically with the `UnsafeCell` family of types.) + +But it is still better to catch more bugs on spot, and calling partially moved values "not mutated", is hardly justifiable. The RFC author (@CloudiDust) believes that, uniqueness and aliasing control is but an implementation detail. If the keyword is called `mut`, then "mutable" and "immutable" should fit programmer intuitions. **Alternative 3.** Go all the way and forbid full outbound moves from immutable variables as well. -This would make immutable variables even more true to their names, and effectively this would deeply pin all immutable values and guarantee that they all have scoped lifetimes. But this is too restrictive and unnecessary. Having to throw `mut` everywhere defeats the purpose of the mutable/immutable distinction. Also, unexpected full outbound moves are easier to catch than unexpected partial outbound moves, as compile errors will happen more often. +This would make immutable variables even more true to their names, and effectively this would deeply pin all immutable values and guarantee that they all have scoped lifetimes. But this is too restrictive and unnecessary. Having to throw `mut` everywhere defeats the purpose of the mutable/immutable distinction. The RFC author considers the proposed change in this RFC to be a reasonable compromise between the alternatives 2 and 3. From 6acac056d11c7f110c168066c954fb78ba53b6f7 Mon Sep 17 00:00:00 2001 From: Richard Zhang Date: Sun, 14 Sep 2014 20:39:15 +0800 Subject: [PATCH 10/12] Remove a self-reference in the last commit. --- active/0000-refine-immutables.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/active/0000-refine-immutables.md b/active/0000-refine-immutables.md index 603336d7130..23ea04e3bcd 100644 --- a/active/0000-refine-immutables.md +++ b/active/0000-refine-immutables.md @@ -129,7 +129,7 @@ It can be argued that, because using partially-moved values (as a whole) or empt Also, there is a reason that Rust's mutation control semantics are designed the way it is. `mut`/`&mut` are not actually designed around *mutability*, but *exclusive accessibility*. What Rust has is not *mutation control* but *uniqueness and aliasing control*, and `mut` actually means "this is a variable that you can request exclusive access of", not *necessary* "this is a variable that is mutable". So conversely, a variable without the `mut` keyword, is just a variable that "you cannot (statically) request exclusive access of", not *necessary* "a variable that is immutable". (Exclusive/mutable access to immutable values can be requested and checked dynamically with the `UnsafeCell` family of types.) -But it is still better to catch more bugs on spot, and calling partially moved values "not mutated", is hardly justifiable. The RFC author (@CloudiDust) believes that, uniqueness and aliasing control is but an implementation detail. If the keyword is called `mut`, then "mutable" and "immutable" should fit programmer intuitions. +But it is still better to catch more bugs on spot, and calling partially moved values "not mutated", is hardly justifiable. The RFC author believes that, uniqueness and aliasing control is but an implementation detail. If the keyword is called `mut`, then "mutable" and "immutable" should fit programmer intuitions. **Alternative 3.** Go all the way and forbid full outbound moves from immutable variables as well. From 91099c78a3b8d0bd4d3ff6544f4893ae9b387af7 Mon Sep 17 00:00:00 2001 From: Richard Zhang Date: Sun, 14 Sep 2014 20:48:30 +0800 Subject: [PATCH 11/12] Replace a tab with four spaces. --- active/0000-refine-immutables.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/active/0000-refine-immutables.md b/active/0000-refine-immutables.md index 23ea04e3bcd..24e78240685 100644 --- a/active/0000-refine-immutables.md +++ b/active/0000-refine-immutables.md @@ -36,7 +36,7 @@ enum Status { Living, Deceased } #[deriving(Show)] struct Person { name: String, - status: Status, + status: Status, } fn main() { From 5ad67a06a0d1bf2f515d6d27c1fb5fcc6b3d5266 Mon Sep 17 00:00:00 2001 From: Richard Zhang Date: Sun, 14 Sep 2014 23:35:59 +0800 Subject: [PATCH 12/12] Partially moved values are no longer movable. --- active/0000-refine-immutables.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/active/0000-refine-immutables.md b/active/0000-refine-immutables.md index 24e78240685..a81c578aab1 100644 --- a/active/0000-refine-immutables.md +++ b/active/0000-refine-immutables.md @@ -89,14 +89,13 @@ After this change, a programmer will be required to use mutable variables if he/ Breaking change. `mut`s and `ref`s may have to be used in more places. -Particularly, if for some reason, partial outbound moves from immutable values are intentionally requested by a programmer, and he/she needs the values to again be immutable after the moves, then he/she has to use this idiom: +Particularly, if for some reason, partial outbound moves from immutable values are intentionally requested by a programmer, then he/she has to use this: ```rust let foo = Foo {...}; ... let mut foo = foo; move(foo.bar); -let foo = foo; ... ``` @@ -109,7 +108,7 @@ move(foo.bar); ... ``` -There may be some ergonomic issues. But the RFC author (@CloudiDust) believes this change to be generally a plus. Rust prefers doing things (reasonably) explicitly after all. Partial outbound moves are mutations to the parent values, and should have been labelled as such. Currently they just slip past the radar. +There may be some ergonomic issues, and the programmer can no longer move `foo` back into an immutable variable. (Arguably this can be a good thing, as immutable values will be guaranteed to be without "holes".) The RFC author (@CloudiDust) believes this change to be generally a plus. Rust prefers doing things (reasonably) explicitly after all. Partial outbound moves are mutations to the parent values, and should have been labelled as such. Currently they just slip past the radar. The problem with using more `mut` is that: `mut` dictates no mutation restrictions at all, and it is not clear what kind of mutation the programmer actually wants when he/she writes `mut`. Inbound copies? Or inbound moves? Or outbound moves? Fully or partially?