Skip to content

AsRef/Borrow/BorrowMut need better documentationΒ #44868

Closed
@ghost

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>.

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-docsArea: Documentation for any part of the project, including the compiler, standard library, and toolsC-enhancementCategory: An issue proposing an enhancement or a PR with one.E-hardCall for participation: Hard difficulty. Experience needed to fix: A lot.P-mediumMedium priority

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions