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.
Why is "inverse Borrow
" less useful than Borrow
?
Functions that need references
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.1
Functions that need owned values
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.
What about 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.
- For integer types, because generics mess with type inference, using them usually makes it less ergonomic to use literals; you may end up having to explicitly annotate the types.
- Since
&u32
doesn't implement Into<u32>
, that particular trick wouldn't work here anyway.
- Since
Copy
types are readily available as owned values, it's less common to use them by reference in the first place.
- Finally, turning a
&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.
See also
1 For this particular function you'd probably want AsRef<str>
, because you're not relying on the extra guarantees of Borrow
, and the fact that all T
implements Borrow<T>
isn't usually relevant for unsized types such as str
. But that is beside the point.