-
Notifications
You must be signed in to change notification settings - Fork 12.8k
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
Add forwarding impls for Read, Write, Seek to Arc, Rc #93044
Conversation
r? @m-ou-se (rust-highfive has picked a reviewer for you, use r? to override) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is a good addition, but it desperately needs some documentation! I'm not sure where the best place for that is, but either on the types or the impls or somewhere. I think it needs explaining that these impls exist, what they allow, how they can be used, some examples, etc. There should also be comments (i.e., not user-facing docs) explaining why the bound on the impl is the way it is (why not T: Read, etc) and probably a comment explaining why these are on Arc and Rc, not &Arc and &Rc.
This comment has been minimized.
This comment has been minimized.
@nrc - thanks for the suggestion! I added some doc comments on the impls, which I think also does a reason for the bound, etc. Let me know if you think this is clear enough, and if not I can add more detail. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm, thanks for the docs!
@rfcbot merge |
Team member @m-ou-se has proposed to merge this. The next step is review by the rest of the tagged team members: Concerns:
Once a majority of reviewers approve (and at most 2 approvals are outstanding), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up! See this document for info about what commands tagged team members can give me. |
At first glance seems to have the same issue as pointed out here: #94744 (comment), but the implementation in this PR implicitly requires |
Sounds like this should be explicitly documented as a pitfall as |
It's not clear to me that this PR is less problematic than #94744. The relevant distinction in #94744 was whether the impls mutate the incoming reference or not, not whether the reference is fat. Ruling out fat references as done here doesn't rule out impls which work by mutating the reference, in general. Here is one counterexample: use std::io::{Result, Write};
#[derive(Debug)]
enum Parity {
Even,
Odd,
}
impl Write for &Parity {
fn write(&mut self, buf: &[u8]) -> Result<usize> {
if buf.iter().map(|x| x.count_ones()).sum::<u32>() % 2 == 1 {
match self {
Parity::Even => *self = &Parity::Odd,
Parity::Odd => *self = &Parity::Even,
}
}
Ok(buf.len())
}
fn flush(&mut self) -> Result<()> {
Ok(())
}
}
fn main() {
let mut parity = &Parity::Even;
write!(parity, "###").unwrap();
println!("{:?}", parity); // Odd
write!(parity, "###").unwrap();
println!("{:?}", parity); // Even
} Are we saying these impls are not always correct but still useful enough in practice to include anyway? @rfcbot concern behavior on impls that mutate the reference |
Thanks for the example, @dtolnay. I think that's a really clear illustration of the issue. Here's what it looks like with this PR, going through an ARC: use std::io::{Result, Write};
use std::sync::Arc;
#[derive(Debug)]
enum Parity {
Even,
Odd,
}
impl Write for &Parity {
fn write(&mut self, buf: &[u8]) -> Result<usize> {
if buf.iter().map(|x| x.count_ones()).sum::<u32>() % 2 == 1 {
match self {
Parity::Even => *self = &Parity::Odd,
Parity::Odd => *self = &Parity::Even,
}
}
Ok(buf.len())
}
fn flush(&mut self) -> Result<()> {
Ok(())
}
}
fn main() {
let mut parity = Arc::new(Parity::Even);
write!(parity, "###").unwrap();
println!("{:?}", parity); // Even
write!(parity, "###").unwrap();
println!("{:?}", parity); // Even
} Basically, the This feels like the sort of thing that should require I'd love to find a more generic way of doing this than specializing for One argument in favor of still merging this is that it doesn't do anything users can't already do on their own. That said, I think it makes sense to avoid adding known footguns to the standard library if we can avoid it. |
@rfcbot abort |
@rfcbot cancel |
@dtolnay proposal cancelled. |
Since the proposal was canceled, should this PR be closed? |
This adds forwarding impls for
std::io::Read
,std::io::Write
, andstd::io::Seek
toalloc:sync::Arc
andalloc::rc::Rc
. This is relevant for types such asstd::fs::File
,std::net::TcpStream
, andstd::os::unix::UnixStream
which implementRead
,Write
, andSeek
for&T
.It is currently possible to do this manually through wrappers (See the "Implement a forwarding wrapper by hand" section for an example), but providing forwarding impls makes this pattern nicer to use. In some cases this can also be done with an extra reference, as shown below:
The reason why we want
Arc<T>: Read where &T: Read
is because this enablesArc<T>
to be passed directly into APIs which expectT: Read
:Implement a forwarding wrapper by hand
These trait impls do not allow anything which wasn't allowed before. Dereferencing to the inner type to get
Arc<T>: Read
to work is already possible by creating an intermediate type, as shown here:References
Arc
blog postasync-dup
crateio-arc
crateJoint work with @yoshuawuyts.