Skip to content

Add explicit Iterator::count impl for str::EncodeUtf16#159467

Merged
rust-bors[bot] merged 1 commit into
rust-lang:mainfrom
SimonSapin:encode_utf16-count
Jul 18, 2026
Merged

Add explicit Iterator::count impl for str::EncodeUtf16#159467
rust-bors[bot] merged 1 commit into
rust-lang:mainfrom
SimonSapin:encode_utf16-count

Conversation

@SimonSapin

Copy link
Copy Markdown
Contributor

In the early days of Unicode, many systems adopted it by storing in-memory strings in (roughly) Vec<u16>, encoded as UCS-2 / UTF-16 / WTF-16. The “natural” way to index such strings or measure their length is counting UTF-16 code units. In some cases, such lengths or offsets found their way in protocols or APIs, such that even a Rust implementation that uses &str internally needs to count 16-bit code units for compatibility. For example this comes up in Servo’s implementation of some DOM APIs.

One way to convert from an &str index (counted 8-bit code units) is:

let index_16bit = string[..i].encode_utf16().count();

Before this PR, the default Iterator::count relies on Iterator::next which decodes UTF-8 and encodes to UTF-16, doing more work than needed. Instead, this PR matches on bit patterns in the underlying UTF-8 string.

For comparison, str::Chars already has a sophisticated impl for Iterator::count in library/core/src/str/count.rs.

@rustbot rustbot added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Jul 17, 2026
@rustbot rustbot added the T-libs Relevant to the library team, which will review and decide on the PR/issue. label Jul 17, 2026
@rustbot

rustbot commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator

r? @Darksonn

rustbot has assigned @Darksonn.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

Why was this reviewer chosen?

The reviewer was selected based on:

  • Owners of files modified in this PR: libs
  • libs expanded to 12 candidates
  • Random selection from 6 candidates

Comment on lines +1537 to +1538
#[inline]
fn count(self) -> usize {

@SimonSapin SimonSapin Jul 17, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don’t know if this should be #[inline], for now it follows the example of other methods in this impl block

View changes since the review

In the early days of Unicode, many systems adopted it by storing in-memory
strings in (roughly) `Vec<u16>`, encoded as UCS-2 / UTF-16 / WTF-16.
The “natural” way to index such strings or measure their length is counting
UTF-16 code units. In some cases, such lengths or offsets found their way
in protocols or APIs, such that even a Rust implementation that uses `&str`
internally needs to count 16-bit code units for compatibility.
For example this comes up in Servo’s implementation of some DOM APIs.

One way to convert from an `&str` index (counted 8-bit code units) is:

```rust
let index_16bit = string[..i].encode_utf16().count();
```

Before this PR, the default `Iterator::count` relies on `Iterator::next`
which decodes UTF-8 and encodes to UTF-16, doing more work than needed.
Instead, this PR matches on bit patterns in the underlying UTF-8 string.

For comparison, `str::Chars` already has a sophisticated impl
for `Iterator::count` in `library/core/src/str/count.rs`.
@SimonSapin
SimonSapin force-pushed the encode_utf16-count branch from 981fca9 to 630793c Compare July 18, 2026 07:56
@joboet

joboet commented Jul 18, 2026

Copy link
Copy Markdown
Member

Fantastic, thank you!
@bors r+
r? joboet

@rust-bors

rust-bors Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

📌 Commit 630793c has been approved by joboet

It is now in the queue for this repository.

@rust-bors rust-bors Bot added the S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. label Jul 18, 2026
@rust-bors rust-bors Bot removed the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Jul 18, 2026
Comment on lines +1551 to +1552
self.chars.as_str().as_bytes().iter().fold(extra, |acc, &byte| {
acc.wrapping_add({

@Darksonn Darksonn Jul 18, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This fold() looks like a manual reimplementation of sum().

View changes since the review

@SimonSapin SimonSapin Jul 18, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is indeed. I accidentally removed it at some point but I had a comment saying this manually inlines <usize as Sum>::sum in order to use extra as the initial accumulator value, instead of using zero then adding extra

I also changed + with #[rustc_inherit_overflow_checks] to wrapping_add since this can never overflow

If you think that's better I can change it back to .map(...).sum() + extra

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose it's fine as-is.

}

#[test]
fn test_utf16_count() {

@Darksonn Darksonn Jul 18, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about iterating through all unicode codepoints and verifying that count() gives the same answer as manually counting with a for loop? There are only one million codepoints, so it shouldn't take long.

View changes since the review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That feels a bit excessive? There's only four kinds of UTF-8 byte sequences / relevant ranges of code points, and the tests cover each of them

@rust-bors

rust-bors Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

⌛ Testing commit 630793c with merge ecb69f1...

Workflow: https://github.com/rust-lang/rust/actions/runs/29641888078

rust-bors Bot pushed a commit that referenced this pull request Jul 18, 2026
Add explicit `Iterator::count` impl for `str::EncodeUtf16`

In the early days of Unicode, many systems adopted it by storing in-memory strings in (roughly) `Vec<u16>`, encoded as UCS-2 / UTF-16 / WTF-16. The “natural” way to index such strings or measure their length is counting UTF-16 code units. In some cases, such lengths or offsets found their way in protocols or APIs, such that even a Rust implementation that uses `&str` internally needs to count 16-bit code units for compatibility. For example this comes up in Servo’s implementation of some DOM APIs.

One way to convert from an `&str` index (counted 8-bit code units) is:

```rust
let index_16bit = string[..i].encode_utf16().count();
```

Before this PR, the default `Iterator::count` relies on `Iterator::next` which decodes UTF-8 and encodes to UTF-16, doing more work than needed. Instead, this PR matches on bit patterns in the underlying UTF-8 string.

For comparison, `str::Chars` already has a sophisticated impl for `Iterator::count` in `library/core/src/str/count.rs`.
JonathanBrouwer added a commit to JonathanBrouwer/rust that referenced this pull request Jul 18, 2026
…oboet

Add explicit `Iterator::count` impl for `str::EncodeUtf16`

In the early days of Unicode, many systems adopted it by storing in-memory strings in (roughly) `Vec<u16>`, encoded as UCS-2 / UTF-16 / WTF-16. The “natural” way to index such strings or measure their length is counting UTF-16 code units. In some cases, such lengths or offsets found their way in protocols or APIs, such that even a Rust implementation that uses `&str` internally needs to count 16-bit code units for compatibility. For example this comes up in Servo’s implementation of some DOM APIs.

One way to convert from an `&str` index (counted 8-bit code units) is:

```rust
let index_16bit = string[..i].encode_utf16().count();
```

Before this PR, the default `Iterator::count` relies on `Iterator::next` which decodes UTF-8 and encodes to UTF-16, doing more work than needed. Instead, this PR matches on bit patterns in the underlying UTF-8 string.

For comparison, `str::Chars` already has a sophisticated impl for `Iterator::count` in `library/core/src/str/count.rs`.
@JonathanBrouwer

Copy link
Copy Markdown
Contributor

@bors yield
Yielding to enclosing rollup

@rust-bors

rust-bors Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Auto build was cancelled. Cancelled workflows:

The next pull request likely to be tested is #159510.

rust-bors Bot pushed a commit that referenced this pull request Jul 18, 2026
…uwer

Rollup of 5 pull requests

Successful merges:

 - #159467 (Add explicit `Iterator::count` impl for `str::EncodeUtf16`)
 - #158545 (Move `std::io::read_to_string` to `alloc::io`)
 - #159328 (tests/assembly-llvm: pin frame pointer in issue-141649 aarch64 test)
 - #159470 (Fix string indexing in diagnostic format strings)
 - #159500 (Move compiletest CLI parsing to `cli.rs`)
rust-bors Bot pushed a commit that referenced this pull request Jul 18, 2026
…uwer

Rollup of 8 pull requests

Successful merges:

 - #159425 (Windows: add context when opening NUL for child stdio fails)
 - #159467 (Add explicit `Iterator::count` impl for `str::EncodeUtf16`)
 - #157860 (Move rustdoc run-make tests into new `tests/run-make/rustdoc/`)
 - #158545 (Move `std::io::read_to_string` to `alloc::io`)
 - #159328 (tests/assembly-llvm: pin frame pointer in issue-141649 aarch64 test)
 - #159459 (Detect when trait bound requires closure to return itself)
 - #159470 (Fix string indexing in diagnostic format strings)
 - #159500 (Move compiletest CLI parsing to `cli.rs`)
@rust-bors
rust-bors Bot merged commit ae70887 into rust-lang:main Jul 18, 2026
13 of 14 checks passed
@rustbot rustbot added this to the 1.99.0 milestone Jul 18, 2026
rust-timer added a commit that referenced this pull request Jul 18, 2026
Rollup merge of #159467 - SimonSapin:encode_utf16-count, r=joboet

Add explicit `Iterator::count` impl for `str::EncodeUtf16`

In the early days of Unicode, many systems adopted it by storing in-memory strings in (roughly) `Vec<u16>`, encoded as UCS-2 / UTF-16 / WTF-16. The “natural” way to index such strings or measure their length is counting UTF-16 code units. In some cases, such lengths or offsets found their way in protocols or APIs, such that even a Rust implementation that uses `&str` internally needs to count 16-bit code units for compatibility. For example this comes up in Servo’s implementation of some DOM APIs.

One way to convert from an `&str` index (counted 8-bit code units) is:

```rust
let index_16bit = string[..i].encode_utf16().count();
```

Before this PR, the default `Iterator::count` relies on `Iterator::next` which decodes UTF-8 and encodes to UTF-16, doing more work than needed. Instead, this PR matches on bit patterns in the underlying UTF-8 string.

For comparison, `str::Chars` already has a sophisticated impl for `Iterator::count` in `library/core/src/str/count.rs`.
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