惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
博客园_首页
G
Google Developers Blog
A
About on SuperTechFans
酷 壳 – CoolShell
酷 壳 – CoolShell
aimingoo的专栏
aimingoo的专栏
Last Week in AI
Last Week in AI
博客园 - 聂微东
T
Tailwind CSS Blog
宝玉的分享
宝玉的分享
V
Visual Studio Blog
美团技术团队
The Cloudflare Blog
量子位
T
The Blog of Author Tim Ferriss
罗磊的独立博客
V
V2EX
S
SegmentFault 最新的问题
小众软件
小众软件
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
MyScale Blog
MyScale Blog
博客园 - Franky

The Rust Programming Language Forum - Latest topics

Beginner building a Rust backend framework (AI-assisted) — feedback appreciated C++ to Rust -- Exceptions Re-exporting a trait with a custom derive_macro Gold linker is deprecated Why is using PhantomData valid in this case? Code review for Static Pool Allocator Looking for a pool based allocator Why is this legal? What does it mean for Rustc to run "out of TLS keys"? Using async/await internally with no internal runtime, but exposing a nonblocking poll API — is this a reasonable design? Testing functions that use randomness `cargo-path`: improve coding agents&#39; ability to find Rust documentation Rust task runners Lifetime weird case Windows - USB device not detected A random rustc-ice-[...].txt file appeared Develop rust where the environment is setup in a docker Undefined Behavior: in-bounds pointer arithmetic failed: attempting to offset pointer by 20 bytes, but got alloc238 which is only 1 byte from the end of the allocation Unbug 0.5 - Runtime debug assertions Is there a tiny error in section 6.2 Reference types? Lifetime woes implementing ratatui::Widget for a reference Rusqlite + Chrono: How do I simplify code to obtain chrono datetime value From OOP to Rust – struggling with code organization and data structure design Way to avoid a self-referential struct Rust RF and audio resources/communities Whyhttp - HTTP mocks that fail where the bug actually is Using tokio channel permits in a tower service Arc::increment_strong_count design question (cross-post) `&T`, `&mut T`, `Pin<&mut T>` and `&Cell<T>`: Ways of Borrowing a `T` Ratatui detect arrow key press and release
Loops with accumulated state confuse me
RobertN · 2026-04-16 · via The Rust Programming Language Forum - Latest topics

While @jdahlstrom’s solution is a really elegant one, I would like to point out a more general perspective.

This is the first half of the problem, and the simpler one.

Basically, this is a nested sequence: a sequence of sequences, an array of arrays, two nested loops, or a generator function returning generators. There are many manifestations of this concept.

They all share the idea of flattening: somewhere in the program, you only see the inner item. In your example, that is the body of the nested for loop. That is one of many possible solutions, and it is perfectly fine.

This is the second half of the problem.

“When it changes” means that you are effectively processing the sequence in pairs. There is some function or logic that takes two elements from the (abstract) sequence as parameters.

And at the end, your implicit requirement is that this function or logic should still be called with the last element of the sequence as the first parameter, even though there is obviously no element available for the second parameter. That missing thing is the root cause of the awkward repeated if statement in your example.

A very old technique, with its origins, I think, in list processing, is the introduction of sentinel values at the end (and sometimes the beginning) of a sequence. These values can be concrete instances of the original element type, or special values.

An extremely widely adopted sentinel value, for example, is the terminating NULL byte in strings in many programming languages. It is so common that virtually nobody realizes that the NULL is a sentinel. :wink:

In a language like Rust, you can define better sentinel values by using enums, or simply use a pre-made enum like Option. So, given a (flattened) sequence, you add sentinels and then process it.

A more general example:

fn main() {
    let sequence = [1, 2, 3];
    let iter = sequence.iter().map(Some).chain([None]);
    let mut last = None;
    for item in iter {
        match (last, item) {
            (None, Some(x)) => println!("first: {x}"),
            (Some(a), Some(b)) => println!("pair: ({a}, {b})"),
            (Some(x), None) => println!("last: {x}"),
            _ => unreachable!(),
        }
        last = item;
    }
}

And more concretely for your example (note: your original code processed an empty buffer at the start; this one does not):

fn process_buffer(buf: &mut Vec<char>) {
    println!("process_buffer: {buf:?}");
    buf.clear();
}

fn char_language(c: char) -> u32 {
    c as u32
}

fn main() {
    let strs = ["aab", "bbc", "dda"];
    let chars = strs.iter().flat_map(|s| s.chars());
    let mut buf = Vec::new();
    let mut prev_lang = None;
    for c in chars.map(Some).chain([None]) {
        let lang = c.map(char_language);
        if lang != prev_lang && prev_lang.is_some() {
            process_buffer(&mut buf);
        }
        if let Some(c) = c {
            buf.push(c);
        }
        prev_lang = lang;
    }
    assert!(buf.is_empty());
}

The concept of sentinels can be generalized even further. Different sentinel-like values can be inserted into a sequence at different points to carry information forward to subsequent operations on that sequence.