
Description
Links: AsRef
, Borrow
, BorrowMut
.
Those docs have plenty of text. I've read it dozens of times and felt "uhhh, what" every single time. Well, the text sort of makes sense, but, in the end, it doesn't help me in deciding between AsRef
and Borrow
. So I just had to dig myself through the APIs and try figuring the mystery by myself. :)
Here are some notes I've collected afterwards. I wonder if we could get at least some of the following stuff into the official docs...
Conversion | Using Borrow |
Using AsRef |
---|---|---|
&T -> &T |
borrows | (*) |
&Vec<T> -> &[T] |
borrows | borrows |
&String -> &str |
borrows | borrows |
&str -> &Path |
converts | |
&Path -> &OsStr |
converts | |
&OsStr -> &Path |
converts | |
&Path -> &Path |
borrows | borrows |
(*) should be 'borrows', but cannot be implemented yet due to coherence issues (I believe?)
Key takeaways:
Borrow
is simple and strict. The hash of the borrowed reference must stay the same.AsRef
converts to a wider range of different types. The hash of the new reference is allowed to change.
Exercise 1
We want to implement a function that creates a directory. It must accept both &str
and &Path
as the argument. Which signature are we looking for?
fn mkdir<P: AsRef<Path>>(path: P);
fn mkdir<P: Borrow<Path>>(path: P);
Answer: In order to go from &str
to &Path
, we have to create a value of different type Path
(&str
is more primitive - it cannot be borrowed as Path
). Since AsRef
can borrow and convert, it is the correct option here.
Exercise 2
We want to check whether a value exists in a HashSet
. Even if we have a set of type HashSet<String>
, we'd like to be able to just do s.contains("foo")
(passing a &str
). Which one of the four method signatures is the right one?
impl<T: Eq + Hash> HashSet<T> {
fn contains<Q: Hash + Eq>(&self, value: &Q) -> bool where T: AsRef<Q>;
fn contains<Q: Hash + Eq>(&self, value: &Q) -> bool where Q: AsRef<T>;
fn contains<Q: Hash + Eq>(&self, value: &Q) -> bool where T: Borrow<Q>;
fn contains<Q: Hash + Eq>(&self, value: &Q) -> bool where Q: Borrow<T>;
}
Answer: We don't want to convert between totally different types, so the hash and structural equality of the value must be preserved after reference conversion. In other words, we only want simple borrowing. Conversion to a different type might potentially change the hash and is thus out of the question.
So Borrow
is the right one here. But is it T: Borrow<Q>
or Q: Borrow<T>
? Well, if T
is String
and we're passing a &str
to the method, we want to borrow T
as Q
. So the right bound is T: Borrow<Q>
.