I\'ve seen the Borrow
trait used to define functions that accept both an owned type or a reference, e.g. T
or &T
. The borrow
There is not really an inverse trait for Borrow
, because it's not really useful as a bound on functions the same way Borrow
is. The reason has to do with ownership.
Borrow
" less useful than Borrow
?Consider a function that only needs to reference its argument:
fn puts(arg: &str) {
println!("{}", arg);
}
Accepting String
would be silly here, because puts
doesn't need to take ownership of the data, but accepting &str
means we might sometimes force the caller to keep the data around longer than necessary:
{
let output = create_some_string();
output.push_str(some_other_string);
puts(&output);
// do some other stuff but never use `output` again
} // `output` isn't dropped until here
The problem being that output
isn't needed after it's passed to puts
, and the caller knows this, but puts
requires a reference, so output
has to stay alive until the end of the block. Obviously you can always fix this in the caller by adding more blocks and sometimes a let
, but puts
can also be made generic to let the caller delegate the responsibility of cleaning up output
:
fn puts<T: Borrow<str>>(arg: T) {
println!("{}", arg.borrow());
}
Accepting T: Borrow
for puts
gives the caller the flexibility to decide whether to keep the argument around or to move it into the function.
Now consider the case of a function that actually needs to take ownership:
struct Wrapper(String);
fn wrap(arg: String) -> Wrapper {
Wrapper(arg)
}
In this case accepting &str
would be silly, because wrap
would have to call to_owned()
on it. If the caller has a String
that it's no longer using, that would needlessly copy the data that could have just been moved into the function. In this case, accepting String
is the more flexible option, because it allows the caller to decide whether to make a clone or pass an existing String
. Having an "inverse Borrow
" trait would not add any flexibility that arg: String
does not already provide.
But String
isn't always the most ergonomic argument, because there are several different kinds of string: &str
, Cow<str>
, Box<str>
... We can make wrap
a little more ergonomic by saying it accepts anything that can be converted into
a String
.
fn wrap<T: Into<String>>(arg: T) -> Wrapper {
Wrapper(arg.into())
}
This means you can call it like wrap("hello, world")
without having to call .to_owned()
on the literal. Which is not really a flexibility win -- the caller can always call .into()
instead without loss of generality -- but it is an ergonomic win.
Copy
types?Now, you asked about Copy
types. For the most part the arguments above still apply. If you're writing a function that, like puts
, only needs a &A
, using T: Borrow<A>
might be more flexible for the caller; for a function like wrap
that needs the whole A
, it's more flexible to just accept A
. But for Copy
types the ergonomic advantage of accepting T: Into<A>
is much less clear-cut.
&u32
doesn't implement Into<u32>
, that particular trick wouldn't work here anyway.Copy
types are readily available as owned values, it's less common to use them by reference in the first place.&A
into an A
when A: Copy
is as simple as just adding *
; being able to skip that step is probably not a compelling enough win to counterbalance the added complexity of using generics in most cases.In conclusion, foo
should almost certainly just accept value: u32
and let the caller decide how to get that value.
With the function you have you can only use a u32
or a type that can be borrowed as u32
.
You can make your function more generic by using a second template argument.
fn foo<T: Copy, N: Borrow<T>>(value: N) -> T {
*value.borrow()
}
This is however only a partial solution as it will require type annotations in some cases to work correctly.
For example, it works out of the box with usize
:
let v = 0usize;
println!("{}", foo(v));
There is no problem here for the compiler to guess that foo(v)
is a usize
.
However, if you try foo(&v)
, the compiler will complain that it cannot find the right output type T
because &T
could implement several Borrow
traits for different types. You need to explicitly specify which one you want to use as output.
let output: usize = foo(&v);