Rollup merge of #59663 - matklad:borrow, r=dtolnay

Be more direct about borrow contract

I always was confused by the difference between Borrow and AsRef, despite the fact that I've read all available docs at least a dozen of times.

I finally grokked the difference between the two when I realized the Borrow invariant:

> If you implement Borrow, you **must** make sure that Eq, Ord and Hash implementations are equivalent for borrowed and owned data

My problem was that this invariant is not stated explicitly in documentation, and instead some  vague and philosophical notions are used.

So I suggest to mention the requirements of `Borrow` very explicitly: instead of "use Borrow when X and use AsRef when Y", let's phrase this as `Borrow` differs from `AsRef` in `W`, so that's why `Borrow` is for `X` and `AsRef` is for `Y`.

Note that this change could be seen as tightening contract of the Borrow. Let's say Alice has written the following code:

```rust
#[derive(PartialEq, Eq, Hash, PartialOrd, Ord)]
struct Person {
    first_name: String,
    last_name: String,
}

impl Borrow<str> for Person {
      fn borrow(&self) -> &str { self.first_name.as_str() }
}
```

Now Bob uses this `Person` struct, puts it into `HashMap` and tries to look it up using `&str` for the first name. Bob's code naturally fails.

The question is, who is to blame: Alice, who has written the impl, or Bob, who uses the HashMap. If I read the current docs literally, I would say that `Bob` is to blame: `Eq` and `Hash` bounds appear on HashMap, so it is the HashMap which requires that they are consistent. By using a type for which the `Borrow` impl does not yield well-behaved `Eq`, Bob is violating contract of HashMap.

If, as this PR proposes, we unconditionally require that Eq & friends for borrow should be valid, then the blame shifts to Alice, which I think is more reasonable.

closes https://github.com/rust-lang/rust/issues/44868
This commit is contained in:
Mazdak Farrokhzad 2019-04-04 01:49:08 +02:00 committed by GitHub
commit b78cbe6ddd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 10 additions and 4 deletions

View File

@ -32,6 +32,10 @@
/// on the identical behavior of these additional trait implementations.
/// These traits will likely appear as additional trait bounds.
///
/// In particular `Eq`, `Ord` and `Hash` must be equivalent for
/// borrowed and owned values: `x.borrow() == y.borrow()` should give the
/// same result as `x == y`.
///
/// If generic code merely needs to work for all types that can
/// provide a reference to related type `T`, it is often better to use
/// [`AsRef<T>`] as more types can safely implement it.

View File

@ -105,11 +105,13 @@ pub const fn identity<T>(x: T) -> T { x }
/// `&T` or write a custom function.
///
///
/// `AsRef` is very similar to, but serves a slightly different purpose than [`Borrow`]:
/// `AsRef` has the same signature as [`Borrow`], but `Borrow` is different in few aspects:
///
/// - Use `AsRef` when the goal is to simply convert into a reference
/// - Use `Borrow` when the goal is related to writing code that is agnostic to
/// the type of borrow and whether it is a reference or value
/// - Unlike `AsRef`, `Borrow` has a blanket impl for any `T`, and can be used to accept either
/// a reference or a value.
/// - `Borrow` also requires that `Hash`, `Eq` and `Ord` for borrowed value are
/// equivalent to those of the owned value. For this reason, if you want to
/// borrow only a single field of a struct you can implement `AsRef`, but not `Borrow`.
///
/// [`Borrow`]: ../../std/borrow/trait.Borrow.html
///