Skip to content
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

Implement default methods for io::Empty and io::Sink #137051

Merged
merged 1 commit into from
Mar 20, 2025

Conversation

thaliaarchi
Copy link
Contributor

@thaliaarchi thaliaarchi commented Feb 14, 2025

Implements default methods of io::Read, io::BufRead, and io::Write for io::Empty and io::Sink. These implementations are equivalent to the defaults, except in doing less unnecessary work.

Read::read_to_string and BufRead::read_line both have a redundant call to str::from_utf8 which can't be inlined from core and Write::write_all_vectored has slicing logic which can't be simplified (See on Compiler Explorer). The rest are optimized to the minimal with -C opt-level=3, but this PR gives that benefit to unoptimized builds.

This includes an implementation of Write::write_fmt which just ignores the fmt::Arguments<'_>. This could be problematic whenever a user formatting impl is impure, but the docs do not guarantee that the args will be expanded.

Tracked in #136756.

r? @m-ou-se

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-libs Relevant to the library team, which will review and decide on the PR/issue. labels Feb 14, 2025
@thaliaarchi thaliaarchi force-pushed the io-optional-impls/empty branch from 854723e to bb69541 Compare February 14, 2025 23:37
@m-ou-se
Copy link
Member

m-ou-se commented Feb 19, 2025

Would it be a valid implementation of Write::write_fmt if the fmt::Arguments<'_> were just ignored? This would be problematic whenever a user formatting impl is impure. But is it actually guaranteed that the args will be expanded?

As far as I know, we technically don't guarantee that the write_fmt method uses the fmt::Arguments exactly once. Skipping it entirely when the output is ignored anyway seems entirely reasonable to me.

ping @rust-lang/libs-api - Please speak up if you don't think it's okay that write!(io::sink(), "{x}") just skips calling Display::fmt on x.

@m-ou-se m-ou-se added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Feb 19, 2025
@thaliaarchi thaliaarchi force-pushed the io-optional-impls/empty branch from bb69541 to 8250dbe Compare February 19, 2025 17:54
@thaliaarchi
Copy link
Contributor Author

I've added nop Write::write_fmt.

@rustbot label -S-waiting-on-author +S-waiting-on-review

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Feb 19, 2025
@rust-log-analyzer

This comment has been minimized.

@thaliaarchi
Copy link
Contributor Author

thaliaarchi commented Feb 19, 2025

Test write-fmt-error expects a panic for Sink::write_fmt, given an Err-returning fmt impl:

let res = catch_unwind(|| write!(sink(), "{} {} {}", 1, ErrorDisplay, "bar"));
let err = res.expect_err("formatter error did not lead to panic").downcast::<&str>().unwrap();
assert!(
err.contains("formatting trait implementation returned an error"),
"unexpected panic: {}", err
);

Should I change the test or revert the write_fmt changes?

@m-ou-se
Copy link
Member

m-ou-se commented Feb 20, 2025

I don't think the purpose of that test is to test the behaviour of io::sink() specifically. It should be fine to change it to something that actually writes. (E.g. &mut Vec<u8>.)

@thaliaarchi thaliaarchi force-pushed the io-optional-impls/empty branch 2 times, most recently from 6e053fc to d846e72 Compare February 20, 2025 22:55
@thaliaarchi
Copy link
Contributor Author

thaliaarchi commented Feb 20, 2025

I've added tests for the new impls.

I also decided to keep Empty::is_read_vectored as the default false. I see it this way: Empty has nothing to read, so we don't need to read that nothing in larger batches; Sink can write large amounts cheaply, so we should batch those writes as large as possible.

In the standard library, only Chain<T, U> non-trivially uses is_read_vectored. It returns true when either of its inner Readers returns true. With Empty in a chain, if Empty::is_read_vectored() == true, it would force the other to use vectored reads:

rust/library/std/src/io/mod.rs

Lines 2729 to 2731 in f04bbc6

fn is_read_vectored(&self) -> bool {
self.first.is_read_vectored() || self.second.is_read_vectored()
}

This could be somewhat improved by making it conditional on whether the first has finished (but then constant propagation is probably worsened):

fn is_read_vectored(&self) -> bool {
    if !self.done_first { self.first.is_read_vectored() } else { self.second.is_read_vectored() }
}

In the standard library, only BufWriter<W> and LineWriterShim<'a, W> non-trivially use is_write_vectored. They select a writing strategy based on the inner writer's is_write_vectored. With Sink::is_write_vectored() == true (as currently), it does less work, because it batches more (nop) writes together. But, vectored or not, a buffered sink is admittedly dumb.

@thaliaarchi
Copy link
Contributor Author

@m-ou-se Hey, friendly 2-week ping. Would you mind taking a look at the changes since your last review?

@thaliaarchi thaliaarchi force-pushed the io-optional-impls/empty branch from d846e72 to 24978bd Compare March 10, 2025 08:37
Eliminate any redundant, unobservable logic from the their default
method implementations.

The observable changes are that `Write::write_fmt` for both types now
ignores the formatting arguments, so a user fmt impl which has side
effects is not invoked, and `Write::write_all_vectored` for both types
does not advance the borrowed buffers. Neither behavior is guaranteed by
the docs and the latter is documented as unspecified.

`Empty` is not marked as vectored, so that `Chain<Empty, _>` and
`Chain<_, Empty>` are not forced to be vectored.
@m-ou-se
Copy link
Member

m-ou-se commented Mar 18, 2025

@bors r+

@bors
Copy link
Contributor

bors commented Mar 18, 2025

📌 Commit 523b9d9 has been approved by m-ou-se

It is now in the queue for this repository.

@bors
Copy link
Contributor

bors commented Mar 18, 2025

🌲 The tree is currently closed for pull requests below priority 100. This pull request will be tested once the tree is reopened.

@bors bors added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Mar 18, 2025
bors added a commit to rust-lang-ci/rust that referenced this pull request Mar 19, 2025
…iaskrgr

Rollup of 7 pull requests

Successful merges:

 - rust-lang#135394 (`MaybeUninit` inherent slice methods part 2)
 - rust-lang#137051 (Implement default methods for `io::Empty` and `io::Sink`)
 - rust-lang#138001 (mir_build: consider privacy when checking for irrefutable patterns)
 - rust-lang#138540 (core/slice: Mark some `split_off` variants unstably const)
 - rust-lang#138589 (If a label is placed on the block of a loop instead of the header, suggest moving it to the header.)
 - rust-lang#138594 (Fix next solver handling of shallow trait impl check)
 - rust-lang#138613 (Remove E0773 "A builtin-macro was defined more than once.")

Failed merges:

 - rust-lang#138602 (Slim `rustc_parse_format` dependencies down)

r? `@ghost`
`@rustbot` modify labels: rollup
bors added a commit to rust-lang-ci/rust that referenced this pull request Mar 19, 2025
…iaskrgr

Rollup of 7 pull requests

Successful merges:

 - rust-lang#135394 (`MaybeUninit` inherent slice methods part 2)
 - rust-lang#137051 (Implement default methods for `io::Empty` and `io::Sink`)
 - rust-lang#138001 (mir_build: consider privacy when checking for irrefutable patterns)
 - rust-lang#138540 (core/slice: Mark some `split_off` variants unstably const)
 - rust-lang#138589 (If a label is placed on the block of a loop instead of the header, suggest moving it to the header.)
 - rust-lang#138594 (Fix next solver handling of shallow trait impl check)
 - rust-lang#138613 (Remove E0773 "A builtin-macro was defined more than once.")

Failed merges:

 - rust-lang#138602 (Slim `rustc_parse_format` dependencies down)

r? `@ghost`
`@rustbot` modify labels: rollup
@bors bors merged commit ce76292 into rust-lang:master Mar 20, 2025
6 checks passed
@rustbot rustbot added this to the 1.87.0 milestone Mar 20, 2025
rust-timer added a commit to rust-lang-ci/rust that referenced this pull request Mar 20, 2025
Rollup merge of rust-lang#137051 - thaliaarchi:io-optional-impls/empty, r=m-ou-se

Implement default methods for `io::Empty` and `io::Sink`

Implements default methods of `io::Read`, `io::BufRead`, and `io::Write` for `io::Empty` and `io::Sink`. These implementations are equivalent to the defaults, except in doing less unnecessary work.

`Read::read_to_string` and `BufRead::read_line` both have a redundant call to `str::from_utf8` which can't be inlined from `core` and `Write::write_all_vectored` has slicing logic which can't be simplified (See on [Compiler Explorer](https://rust.godbolt.org/z/KK6xcrWr4)). The rest are optimized to the minimal with `-C opt-level=3`, but this PR gives that benefit to unoptimized builds.

This includes an implementation of `Write::write_fmt` which just ignores the `fmt::Arguments<'_>`. This could be problematic whenever a user formatting impl is impure, but the docs do not guarantee that the args will be expanded.

Tracked in rust-lang#136756.

r? `@m-ou-se`
@thaliaarchi thaliaarchi deleted the io-optional-impls/empty branch March 20, 2025 00:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. T-libs Relevant to the library team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants