-
Notifications
You must be signed in to change notification settings - Fork 12.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
AsRef/Borrow/BorrowMut need better documentation #44868
Comments
I'm willing to work on this soon, updating both the book and the rustdoc. I think that the main thing that isn't conveyed right now is the fact that |
I’ve been planning to write some text for the documentation of So, for Conversely, perhaps: ‘By implementing |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This is |
Improve documentation for Borrow This is the first step in improving the documentation for all the reference conversion traits. It proposes new text for the trait documentation of `Borrow`. Since I feel it is a somewhat radical rewrite and includes a stricter contract for `Borrow` then the previous text—namely that *all* shared traits need to behave the same, not just a select few—, I wanted to get some feedback before continuing. Apart from the ‘normative’ description, the new text also includes a fairly extensive explanation of how the trait is used in the examples section. I included it because every time I look at how `HashMap` uses the trait, I need to think for a while as the use is a bit twisted. So, I thought having this thinking written down as part of the trait itself might be useful. One could argue that this should go into The Book, and, while I really like having everything important in the docs, I can see the text moved there, too. So, before I move on: is this new text any good? Do we feel it is correct, useful, comprehensive, and understandable? (This PR is in response to rust-lang#44868 and rust-lang#24140.)
I continuously get confused between A related discussion to this (I think) is what a user should do if they want to take any type that can be borrowed into an |
@jonhoo, we’ve tried to build this sort of mental model for As for the missing blanket You can’t use |
@partim The new documentation is certainly better! I think the confusion stems mostly from the name of the trait though. When I read I think one thing the documentation could use is an explicit "which should I implement and why" section. It's currently mixed in with the I also found the statement "For instance, a For the lack of a |
I've taken a stab at this at #59663. It seems like explicitly mentioning that every impl of Borrow must maintain Eq, Ord, Hash would do the trick. |
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 rust-lang#44868
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 rust-lang#44868
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 rust-lang#44868
I wanted to say that despite this issue being closed and the docs presumably improved, I still found the table, explanation and examples here to be clearer and more illuminating than any of the material in the docs. |
I agree, and it took me a while to find this thread. I'd argue the docs should be updated with a lot of the information here. I didn't get from the docs that As a Rust newbie, this also leaves me wondering why I shouldn't just use |
I think that regardless, "this needs better documentation" is a pretty hard issue to track. Documentation can pretty much always be improved, and without concrete things that need to be done, it's better to just encourage people to do further revisions until everyone is satisfied rather than keeping an issue open to track it. |
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
andBorrow
. 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...
Borrow
AsRef
&T -> &T
&Vec<T> -> &[T]
&String -> &str
&str -> &Path
&Path -> &OsStr
&OsStr -> &Path
&Path -> &Path
(*) 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?Answer: In order to go from
&str
to&Path
, we have to create a value of different typePath
(&str
is more primitive - it cannot be borrowed asPath
). SinceAsRef
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 typeHashSet<String>
, we'd like to be able to just dos.contains("foo")
(passing a&str
). Which one of the four method signatures is the right one?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 itT: Borrow<Q>
orQ: Borrow<T>
? Well, ifT
isString
and we're passing a&str
to the method, we want to borrowT
asQ
. So the right bound isT: Borrow<Q>
.The text was updated successfully, but these errors were encountered: