如何修复Rust的char_indices_from函数以满足借用规则?
如何修改Rust函数以满足借用规则?
问题背景
我们希望实现一个从指定偏移量返回字符索引迭代器的函数,以下是两段Rust代码:
触发借用检查错误的代码
use std::borrow::Cow; fn char_indices_from(s: &str, offset: usize) -> impl Iterator<Item=(usize, char)> + '_ { s[offset..].char_indices().map(move |(i,c)| (i+offset,c)) } fn main() { let mut m = Cow::from("watermelons and stuff"); let offset = 2; for (i, c) in char_indices_from(&m, offset) { if i == 3 { m = Cow::from("clouds and the sky"); break } } }
这段代码触发了Rust借用检查器的错误:
error[E0506]: cannot assign to `m` because it is borrowed --> src/main.rs:12:13 | 10 | for (i, c) in char_indices_from(&m, offset) { | ----------------------------- | | | | | borrow of `m` occurs here | a temporary with access to the borrow is created here ... 11 | if i == 3 { 12 | m = Cow::from("clouds and the sky"); | ^ assignment to borrowed `m` occurs here ... 15 | } | - ... and the borrow might be used here, when that temporary is dropped and runs the destructor for type `impl Iterator<Item = (usize, char)>`
可正常运行的内联写法
use std::borrow::Cow; fn main() { let mut m = Cow::from("watermelons and stuff"); let offset = 2; for (i, c) in m[offset..].char_indices().map(|(i,c)| (i+offset, c)) { if i == 3 { m = Cow::from("clouds and the sky"); break } } }
rustc给出了详细的错误诊断,但仍不清楚如何修改char_indices_from函数,使其满足Rust的借用规则,让第一段代码可以正常编译运行?
解决方案
问题根源在于Rust借用检查器对封装后迭代器的生命周期推断过于保守:当迭代器逻辑被封装到函数中时,编译器无法看到循环内部break后迭代器会立即被丢弃,因此认为对m的借用会持续到整个for循环结束,导致无法在循环内修改m。而内联写法中,编译器能精确分析到break后迭代器不再被使用,因此允许提前释放借用。
以下是两种可行的修复方式:
方式一:强制内联函数
给char_indices_from添加#[inline(always)]属性,让编译器将函数逻辑直接展开到调用处,和内联写法的分析逻辑一致:
use std::borrow::Cow; #[inline(always)] fn char_indices_from(s: &str, offset: usize) -> impl Iterator<Item=(usize, char)> + '_ { s[offset..].char_indices().map(move |(i,c)| (i+offset,c)) } fn main() { let mut m = Cow::from("watermelons and stuff"); let offset = 2; for (i, c) in char_indices_from(&m, offset) { if i == 3 { m = Cow::from("clouds and the sky"); break; } } }
方式二:返回具体迭代器类型
避免使用impl Iterator的抽象返回类型,而是返回具体的迭代器组合类型,让编译器更精确地跟踪生命周期:
use std::borrow::Cow; use std::str::CharIndices; use std::iter::Map; fn char_indices_from(s: &str, offset: usize) -> Map<CharIndices<'_>, impl FnMut((usize, char)) -> (usize, char)> { s[offset..].char_indices().map(move |(i,c)| (i+offset,c)) } fn main() { let mut m = Cow::from("watermelons and stuff"); let offset = 2; for (i, c) in char_indices_from(&m, offset) { if i == 3 { m = Cow::from("clouds and the sky"); break; } } }
两种方式都能让借用检查器正确识别:break后迭代器不再访问原借用,因此允许修改m。
内容的提问来源于stack exchange,提问作者elskrrrrt
相关产品推荐
相关产品推荐

