- 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)
This RFC allows autoreferencing of T
where T: Copy
.
Example:
[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); // This works after this RFC.
// by_mut_ref(s); // ERROR -- expected `&mut S`, found `S`
}
When working with Copy
data, the distinction between borrowed and owned data
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:
// 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:
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.
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:
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:
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));
}
Copy
types autoreference: at
coercion sites,
when a reference is expected, but an owned
Copy
type is found, an &
will be automatically inserted.
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<u8>
to &&MyCopyCell<u8>
).
This coercion will not occur if the lifetime of the resulting reference would last longer than the call site. For example:
// `foo`'s argument can be coerced to from `u8` because it only lasts for the
// lifetime of the function call:
fn foo(x: &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() {
foo(1); // OK
let x = bar(5); // ERROR: expected `&u8`, found `u8`.
}
- This increases the special behavior of
Copy
types, making it potentially confusing to new users. Some simple, non-generic code becomes more confusing becausefn foo(x: &i32) { ... }
can now be called likefoo(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 aHashMap<usize, T>
(users will still need to writex[&5]
instead ofx[5]
).
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).
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
:
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
toT
: This conversion would cause extra copies to occur which could be difficult to identify when attempting to optimize code.
- 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
orAdd
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<MyT>
tofn foo<T: SomeTrait>(t: &T)
.