From 51b8da7ec93a4bb4b9bf1c712be941b4f157c5f6 Mon Sep 17 00:00:00 2001 From: Taylor Cramer Date: Sun, 6 Aug 2017 20:12:59 -0700 Subject: [PATCH 1/5] Autoref copy --- text/0000-autoref-copy.md | 157 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 text/0000-autoref-copy.md diff --git a/text/0000-autoref-copy.md b/text/0000-autoref-copy.md new file mode 100644 index 00000000000..0d92da5bfca --- /dev/null +++ b/text/0000-autoref-copy.md @@ -0,0 +1,157 @@ +- Feature Name: autoref_copy +- Start Date: (fill me in with today's date, YYYY-MM-DD) +- RFC PR: (leave this empty) +- Rust Issue: (leave this empty) + +# Summary +[summary]: #summary + +This RFC allows autoreferencing of `T` where `T: Copy`. + +Example: + +```rust +[derive(Copy, Clone)] +struct S(u8); + +fn by_val(_: S) {} +fn by_ref(_: &S) {} +fn by_mut_ref(_: &mut S) {} + +fn main() { + let s = S(5); + by_val(s); + by_ref(s); + // by_mut_ref(s); // ERROR -- expected `&mut S`, found `S` +} +``` + +# Motivation +[motivation]: #motivation + +When working with `Copy` data, the distinction between borrowed and owned data +is often unimportant. However, generic code often results in function which +expect a reference rather than an owned value. In these cases, users have to +add manually create a reference by adding `&` at the call site. + +```rust +use std::collections::HashMap; +fn main() { + let mut map = HashMap::new(); + map.insert(1, "hello!"); + + println!("{:?}", map.get(1)); // ERROR: expected `&{integer}` + + // Instead, users have to write this: + println!("{:?}", map.get(&1)); +} +``` + +This is an unnecessary frustration. This RFC proposes to prevent this issue +by auto-referencing `Copy` types. + +# Guide-level explanation +[guide-level-explanation]: #guide-level-explanation + +When calling a function which expects a reference to a `Copy` type, you can +pass an owned `Copy` type and a reference will be created automatically: + +```rust +fn print_num(x: &usize) { println!("{:?}", x); } + +fn main() { + let x = 5; + print_num(x); +} +``` + +This is particularly convenient when working with some generic types whose +methods take references as arguments: + +```rust +use std::collections::HashMap; +fn main() { + let mut map = HashMap::new(); + map.insert(1, "hello!"); + + // We can write this: + println!("{:?}", map.get(1)); + + // Instead of having to write this: + println!("{:?}", map.get(&1)); +} +``` + +# Reference-level explanation +[reference-level-explanation]: #reference-level-explanation + +`Copy` types will autoreference: at +[coercion sites](https://github.com/rust-lang/rfcs/blob/master/text/0401-coercions.md#coercions), +when a reference is expected, but an owned +`Copy` type is found, an `&` will be automatically inserted. + +If the `Copy` value is a temporary, its lifetime will be promoted to meet the +required lifetime of the reference (where possible). +This behavior is specified in detail in +[RFC 66](https://github.com/rust-lang/rust/issues/15023) and @nikomatsakis's +[amendment](https://github.com/nikomatsakis/rfcs/blob/rfc66-amendment/text/0066-better-temporary-lifetimes.md). + +When it is not possible to promote the `Copy` value's lifetime to the lifetime +required by the reference, a customized error will be issued: + +```rust +struct u8Ref<'a>(&'a u8); + +fn new_static(x: u8) -> u8Ref<'static> { + u8Ref(x) // ERROR: borrowed value does not live long enough + // ^autoreference occurs here, but `x` does not live long enough +} +``` + +If the lifetime of the reference would overlap with a mutable reference or a +mutation of the referenced value, a custom error will be issued: + +```rust +struct u8Ref<'a>(&'a u8); + +fn main() { + let mut x = 5; + let y = u8Ref(x); + // ^ autoreference of `x` occurs here + x = 7; // ERROR: cannot assign to `x` because it is borrowed +} +``` + +# Drawbacks +[drawbacks]: #drawbacks + +- This increases the special behavior of `Copy` types, making it potentially +confusing to new users. +- The existing coercion mechanism doesn't allow for coercion of generic +arguments, nor does it use coercions in trait lookup. Because of this, users +will still need to manually add `&` when trying to, for example, index into +a `HashMap` +(users will still need to write `x[&5]` instead of `x[5]`). +- Autoreferencing may produce surprising errors when attempting to mutate data. + +# Rationale and Alternatives +[alternatives]: #alternatives + +One alternative would be to do nothing. However, this issue is frequently +annoying for new users, and the solution is relatively simple. + +Another alternative would be to also add the following conversions for +`T: Copy`: +-`T` to `&mut T`: This conversion has the potential to introduce hidden +mutations. With this change, passing a variable to a function could allow +the function to change the value of the variable, even if no `&mut` is present. +-`&mut T` and `&T` to `T`: This conversion would cause extra copies to occur +which could be difficult to identify when attempting to optimize code. + +# Unresolved questions +[unresolved]: #unresolved-questions +- Can we make it easier to use copy values where references are expected when +working with traits and generic code? In particular, it would be nice to make +operators such as `Index` auto-reference. One possible way of doing this would +be to introduce default implementations such as +`Index<&Idx>` for `T: Index`. From 079ca6ef45250a0ceae3a4211ef4352595bc684a Mon Sep 17 00:00:00 2001 From: Taylor Cramer Date: Wed, 16 Aug 2017 00:20:26 -0700 Subject: [PATCH 2/5] Cleanup and additional examples --- text/0000-autoref-copy.md | 59 +++++++++++++++++++++++++++++++++------ 1 file changed, 51 insertions(+), 8 deletions(-) diff --git a/text/0000-autoref-copy.md b/text/0000-autoref-copy.md index 0d92da5bfca..1b9661dcfa1 100644 --- a/text/0000-autoref-copy.md +++ b/text/0000-autoref-copy.md @@ -21,7 +21,7 @@ fn by_mut_ref(_: &mut S) {} fn main() { let s = S(5); by_val(s); - by_ref(s); + by_ref(s); // This works after this RFC. // by_mut_ref(s); // ERROR -- expected `&mut S`, found `S` } ``` @@ -30,9 +30,50 @@ fn main() { [motivation]: #motivation When working with `Copy` data, the distinction between borrowed and owned data -is often unimportant. However, generic code often results in function which -expect a reference rather than an owned value. In these cases, users have to -add manually create a reference by adding `&` at the call site. +is often unimportant. However, generic code often results in code which will +only accept a particular variant of a `Copy` type (`T`, `&T`, `&&T`, ...). +This is a frustration in many places: + +```rust +// Comparisons: +let v = vec![0, 1, 2, 3]; +v.iter().filter(|x| x > 1); // ERROR: expected &&_, found integral variable +// These work: +0 > 1; +&0 > &1; +// But these don't: +&0 > 1; +0 > &1; + +// Trait instantiations: +let mut map: HashMap::new(); +map.insert(0, "Hello"); +map.insert(1, "world!"); +map[0]; // ERROR: expected &{integer}, found integral variable +// or +map.get(1); // ERROR: expected `&{integer}`, found integral variable + +// Numeric operators: +// These work: +&0 + &1; +0 + &1; +&0 + 1; +// But these don't: +&&0 + 1; +&&0 + &1; +&&0 + &&1; +``` + +These interactions confuse both new and experienced users without providing +any significant value. It's clear what's intended by `map.get(1)` or +`vec.iter().filter(|x| x > 1)`. When users encounter these errors in practice, +the only reasonable thing they can do is add `&` and `*` as necessary to make +their code compile. + +This RFC seeks to address one particular variant of this problem: passing +owned `Copy` data where a reference was expected. + +Example: ```rust use std::collections::HashMap; @@ -126,7 +167,8 @@ fn main() { [drawbacks]: #drawbacks - This increases the special behavior of `Copy` types, making it potentially -confusing to new users. +confusing to new users. Some simple, non-generic code becomes more confusing +because `fn foo(x: &i32) { ... }` can now be called like `foo(5)`; - The existing coercion mechanism doesn't allow for coercion of generic arguments, nor does it use coercions in trait lookup. Because of this, users will still need to manually add `&` when trying to, for example, index into @@ -152,6 +194,7 @@ which could be difficult to identify when attempting to optimize code. [unresolved]: #unresolved-questions - Can we make it easier to use copy values where references are expected when working with traits and generic code? In particular, it would be nice to make -operators such as `Index` auto-reference. One possible way of doing this would -be to introduce default implementations such as -`Index<&Idx>` for `T: Index`. +operators such as `Index` or `Add` auto-reference and auto-dereference. +It would also be great if we could find some way to trigger existing deref +coercions in generic cases such as passing `&Rc` to +`fn foo(t: &T)`. From 1bb073ef169a0224fd3873fdd6d0e8549b31416d Mon Sep 17 00:00:00 2001 From: Taylor Cramer Date: Wed, 16 Aug 2017 17:19:28 -0700 Subject: [PATCH 3/5] Fix bullet formatting --- text/0000-autoref-copy.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/text/0000-autoref-copy.md b/text/0000-autoref-copy.md index 1b9661dcfa1..e77efbfa7fc 100644 --- a/text/0000-autoref-copy.md +++ b/text/0000-autoref-copy.md @@ -184,9 +184,11 @@ annoying for new users, and the solution is relatively simple. Another alternative would be to also add the following conversions for `T: Copy`: + -`T` to `&mut T`: This conversion has the potential to introduce hidden mutations. With this change, passing a variable to a function could allow the function to change the value of the variable, even if no `&mut` is present. + -`&mut T` and `&T` to `T`: This conversion would cause extra copies to occur which could be difficult to identify when attempting to optimize code. From 149ab01e99a745b1faeb4c1990d187650b3ad535 Mon Sep 17 00:00:00 2001 From: Taylor Cramer Date: Wed, 16 Aug 2017 17:20:39 -0700 Subject: [PATCH 4/5] Update 0000-autoref-copy.md --- text/0000-autoref-copy.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/text/0000-autoref-copy.md b/text/0000-autoref-copy.md index e77efbfa7fc..e4e61711eaf 100644 --- a/text/0000-autoref-copy.md +++ b/text/0000-autoref-copy.md @@ -184,12 +184,10 @@ annoying for new users, and the solution is relatively simple. Another alternative would be to also add the following conversions for `T: Copy`: - --`T` to `&mut T`: This conversion has the potential to introduce hidden +- `T` to `&mut T`: This conversion has the potential to introduce hidden mutations. With this change, passing a variable to a function could allow the function to change the value of the variable, even if no `&mut` is present. - --`&mut T` and `&T` to `T`: This conversion would cause extra copies to occur +- `&mut T` and `&T` to `T`: This conversion would cause extra copies to occur which could be difficult to identify when attempting to optimize code. # Unresolved questions From 4487bcaa24ddb95a5b905b59881e9a9c0fac0c37 Mon Sep 17 00:00:00 2001 From: Taylor Cramer Date: Thu, 31 Aug 2017 04:14:19 -0700 Subject: [PATCH 5/5] Limit Copy autoreferencing scope and interior mutability --- text/0000-autoref-copy.md | 53 +++++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/text/0000-autoref-copy.md b/text/0000-autoref-copy.md index e4e61711eaf..051d02d5ec4 100644 --- a/text/0000-autoref-copy.md +++ b/text/0000-autoref-copy.md @@ -126,40 +126,34 @@ fn main() { # Reference-level explanation [reference-level-explanation]: #reference-level-explanation -`Copy` types will autoreference: at +`Copy` types autoreference: at [coercion sites](https://github.com/rust-lang/rfcs/blob/master/text/0401-coercions.md#coercions), when a reference is expected, but an owned `Copy` type is found, an `&` will be automatically inserted. -If the `Copy` value is a temporary, its lifetime will be promoted to meet the -required lifetime of the reference (where possible). -This behavior is specified in detail in -[RFC 66](https://github.com/rust-lang/rust/issues/15023) and @nikomatsakis's -[amendment](https://github.com/nikomatsakis/rfcs/blob/rfc66-amendment/text/0066-better-temporary-lifetimes.md). +Some types have interior mutability through `UnsafeCell`, so passing them +via a hidden reference could allow surprising mutations to occur. +To prevent this, the autoreferencing coercion is limited to types which do not +directly contain an internal `UnsafeCell`. +Types which reference an `UnsafeCell` through an indirection can still be +coerced (e.g. `&MyCopyCell` to `&&MyCopyCell`). -When it is not possible to promote the `Copy` value's lifetime to the lifetime -required by the reference, a customized error will be issued: +This coercion will not occur if the lifetime of the resulting reference would +last longer than the call site. For example: ```rust -struct u8Ref<'a>(&'a u8); +// `foo`'s argument can be coerced to from `u8` because it only lasts for the +// lifetime of the function call: +fn foo(x: &u8) { ... } -fn new_static(x: u8) -> u8Ref<'static> { - u8Ref(x) // ERROR: borrowed value does not live long enough - // ^autoreference occurs here, but `x` does not live long enough -} -``` - -If the lifetime of the reference would overlap with a mutable reference or a -mutation of the referenced value, a custom error will be issued: - -```rust -struct u8Ref<'a>(&'a u8); +// `bar`'s argument cannot be coerced to from `u8` because the lifetime of the +// required reference outlives the scope of `bar`. +fn bar<'a>(x: &'a u8) -> &'a u8 { ... } fn main() { - let mut x = 5; - let y = u8Ref(x); - // ^ autoreference of `x` occurs here - x = 7; // ERROR: cannot assign to `x` because it is borrowed + foo(1); // OK + + let x = bar(5); // ERROR: expected `&u8`, found `u8`. } ``` @@ -174,13 +168,18 @@ arguments, nor does it use coercions in trait lookup. Because of this, users will still need to manually add `&` when trying to, for example, index into a `HashMap` (users will still need to write `x[&5]` instead of `x[5]`). -- Autoreferencing may produce surprising errors when attempting to mutate data. # Rationale and Alternatives [alternatives]: #alternatives +One alternative would be to do nothing. However, this issue is frequently annoying for new users, and the solution is relatively simple. + +We could also allow borrows which outlive function call sites, but these could +produce surprising errors (for example, if the user attempted to mutate a +variable while it was implicitly borrowed). -One alternative would be to do nothing. However, this issue is frequently -annoying for new users, and the solution is relatively simple. +We could make the autoreferencing coercion more general and allow `T -> &T` +for all types which don't contain an `UnsafeCell`. However, this could harm +users' ability to reason about when variables are `move`d. Another alternative would be to also add the following conversions for `T: Copy`: