Reputation: 3535
I have a variable tokens: &[AsRef<str>]
and I want to concatenate it into single string:
// tokens: &[AsRef<str>]
let text = tokens.join("") // Error
let text = tokens.iter().map(|x|x.as_ref()).collect::<Vec<_>>().join("") // Ok, but...
The second is awkward and inefficient because it reallocates items to a new Vec
.
According to the source code, join
can be applied to tokens
if its type is &[Borrow<str>]
:
// if tokens: &[Borrow<str>]
let text = tokens.join("") // OK
// so I want to convert &[AsRef<str>] to &[Borrow<str>]
let text = convert_to_borrow(tokens).join("")
How should I do that? Why is Join
implemented for types that implement Borrow
but not AsRef
?
Upvotes: 1
Views: 423
Reputation: 58785
Trentcl's answer gives you a solution to your actual problem by relying only on the AsRef<str>
implementation. What follows is more of an answer to the more general question in your title.
Certain traits carry with them invariants which implementations must enforce. In particular, if implementations of Borrow<T>
also implement Eq
, Hash
, and Ord
then the implementations of those traits for T
must behave identically. This requirement is a way of saying that the borrowed value is "the same" as the original value, but just viewed in a different way. For example the String: Borrow<str>
implementation must return the entire string slice; it would be incorrect to return a subslice.
AsRef
does not have this restriction. An implementation of AsRef<T>
can implement traits like Hash
and Eq
in a completely different way from T
. If you need to return a reference to just a part of a struct, then AsRef
can do it, while Borrow
cannot.
All this means that you cannot derive a valid Borrow<T>
implementation from an arbitrary AsRef<T>
implementation: the AsRef
implementation may not enforce the invariants that Borrow
requires.
However, the other way around does work. You can create an implementation of AsRef<T>
, given an arbitrary Borrow<T>
:
use std::borrow::Borrow;
use std::convert::AsRef;
use std::marker::PhantomData;
pub struct BorrowAsRef<'a, T: ?Sized, U: ?Sized>(&'a T, PhantomData<U>);
impl<'a, T, U> AsRef<U> for BorrowAsRef<'a, T, U>
where
T: Borrow<U> + ?Sized,
U: ?Sized,
{
fn as_ref(&self) -> &U {
self.0.borrow()
}
}
pub trait ToAsRef<T: ?Sized, U: ?Sized> {
fn to_as_ref(&self) -> BorrowAsRef<'_, T, U>;
}
impl<T, U> ToAsRef<T, U> for T
where
T: ?Sized + Borrow<U>,
U: ?Sized,
{
fn to_as_ref(&self) -> BorrowAsRef<'_, T, U> {
BorrowAsRef(self, PhantomData)
}
}
fn borrowed(v: &impl Borrow<str>) {
needs_as_ref(&v.to_as_ref())
}
fn needs_as_ref(v: &impl AsRef<str>) {
println!("as_ref: {:?}", v.as_ref())
}
Why is
Join
implemented for types that implementBorrow
but notAsRef
?
This is a blanket implementation, for all types that implement Borrow<str>
, which means it can't also be implemented for types that implement AsRef<str>
. Even with the unstable feature min_specialization
enabled, it wouldn't work because having an implementation of AsRef
is not more "specific" than having an implementation of Borrow
. So they had to pick one or the other.
It could be argued that AsRef
would have been a better choice because it covers more types. But unfortunately I don't think this can be changed now because it would be a breaking change.
Upvotes: 4
Reputation: 28005
It may be slightly slower, but you can collect
an iterator of &str
s directly into a String
.
let text: String = tokens.iter().map(|s| s.as_ref()).collect();
This is possible because String
implements FromIterator<&'_ str>
. This method grows the String
by repeatedly calling push_str
, which may mean it has to be reallocated a number of times, but it does not create an intermediate Vec<&str>
. Depending on the size of the slices and strings being used this may be slower (although in some cases it could also be slightly faster). If the difference would be significant to you, you should benchmark both versions.
There is no way to treat a slice of T: AsRef<str>
as if it were a slice of T: Borrow<str>
because not everything that implements AsRef
implements Borrow
, so in generic code the compiler can't know what Borrow
implementation to apply.
Upvotes: 4