`char::EscapeDebug` displays a char it's already yielded

Open Beginner friendly
#165,615 0 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

Nobody has claimed this yet.

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
78/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
rust
Domain
backend

Research direction

The issue points to library/core/src/escape.rs around the EscapeDebug display logic. Read how next() updates the iterator state and how Display writes printable characters, then add a regression test for consuming the character before formatting the exhausted iterator. Done when the exhausted iterator formats as an empty string while existing escaping behavior remains covered.

Written by the indexing model from the issue text.

Description

A-fmt A-iterators A-str C-bug I-prioritize regression-from-stable-to-stable T-libs
Code

I tried this code:

fn main() {
    let mut e = 'a'.escape_debug();
    assert_eq!(e.next(), Some('a'));
    assert_eq!(e.len(), 0);
    println!("{:?}", e.to_string());
}

I expected to see this happen: "" (the iterator is exhausted).

Instead, this happened: "a"

scope:

  • It only affects chars that char::escape_debug prints as-is (e.g. 'a', 'é'). Escaped characters like \n work correctly
  • str::escape_debug works correctly. For example, calling next() on "a".escape_debug() correctly exhausts the iterator

cause:

  • I think this came from #138237
  • Before, EscabeDebug::next called clear() after yielding a printable char
  • Now, the char is stored in EscapeIterInner, and next() only advances alive.start. alive.end still marks the char as present, so Display writes it again.

how i found this:

  • I found this while writing Kani proofs for core::escape as part of verify-rust-std

proposed fix:

-  return f.write_char(c);
+  return if self.alive.is_empty() { Ok(()) } else { f.write_char(c) };

https://github.com/rust-lang/rust/blob/41089d07a0a2afc16adfe28a760d94d73d7298a4/library/core/src/escape.rs#L346-L357

Version it worked on

It most recently worked on: 1.88.0

Version with regression

rustc --version --verbose: 1.89.0

rustc 1.89.0 (29483883e 2025-08-04)
binary: rustc
commit-hash: 29483883eed69d5fb4db01964cdf2af4d86e9cb2
commit-date: 2025-08-04
host: aarch64-apple-darwin
release: 1.89.0
LLVM version: 20.1.7

@rustbot modify labels: +regression-from-stable-to-stable -regression-untriaged

Dominant language
Rust
Stars
121k
Forks
17.3k
Avg merge
2d 20h
Merged PRs (30d)
534

Getting set up

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from rust-lang/rust

All issues in rust-lang/rust

Similar issues

More Rust issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.