You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Rust中调用结构体不可变方法后如何修改结构体?

问题:Rust Cursor的take方法编译报错处理

我尝试编译以下Rust代码:

pub enum Lexem<'a> {
    Identifier(&'a str),
}

pub struct Cursor<'a> {
    lexems: Vec<&'a str>,
    offset: usize
}

impl<'a> Cursor<'a> {
    pub fn new(lexems: Vec<&str>) -> Cursor {
        Cursor {
            lexems,
            offset: 0
        }
    }

    pub fn peek(&self) -> Option<&str> {
        if self.offset < self.lexems.len() {
            Some(&self.lexems[self.offset])
        } else {
            None
        }
    }

    pub fn take(&mut self) -> Option<&str> {
        if let Some(lexem) = self.peek() {
            self.offset += 1;
            Some(lexem)
        } else {
            None
        }
    }
}

编译器报错如下:

error[E0506]: cannot assign to `self.offset` because it is borrowed
  --> <source>:28:13
   |
26 |     pub fn take(&mut self) -> Option<&str> {
   |                 - let's call the lifetime of this reference `'1`
27 |         if let Some(lexem) = self.peek() {
   |                              ---- `self.offset` is borrowed here
28 |             self.offset += 1;
   |             ^^^^^^^^^^^^^^^^ `self.offset` is assigned to here but it was already borrowed
29 |             Some(lexem)
   |             ----------- returning this value requires that `*self` is borrowed for `'1`

我清楚错误原因:编译器认为修改offset会导致peek返回的引用失效,但实际不会。请问如何让这段代码通过编译?


解决方案

方法1:直接在take中实现peek逻辑

避免调用peek带来的借用冲突,把判断逻辑直接写在take里:

pub fn take(&mut self) -> Option<&str> {
    if self.offset < self.lexems.len() {
        let lexem = &self.lexems[self.offset];
        self.offset += 1;
        Some(lexem)
    } else {
        None
    }
}

这种方式简单直接,适合逻辑不复杂的场景,唯一缺点是存在少量代码重复。

方法2:拆分引用,明确互不干扰

先单独获取lexems的不可变引用,再处理offset,让编译器识别到两个操作不会互相影响:

pub fn take(&mut self) -> Option<&str> {
    let lexems = &self.lexems;
    if self.offset < lexems.len() {
        let lexem = &lexems[self.offset];
        self.offset += 1;
        Some(lexem)
    } else {
        None
    }
}

这里lexems的借用和offset的可变借用被拆分,编译器能正确判断安全性。

方法3:显式指定peek返回的生命周期

将peek返回的引用生命周期绑定到'a(即lexems中字符串的生命周期),而非self的借用周期:

pub fn peek(&self) -> Option<&'a str> {
    if self.offset < self.lexems.len() {
        Some(self.lexems[self.offset])
    } else {
        None
    }
}

pub fn take(&mut self) -> Option<&'a str> {
    if let Some(lexem) = self.peek() {
        self.offset += 1;
        Some(lexem)
    } else {
        None
    }
}

这样编译器会明白返回的引用来自lexems中的字符串,修改offset不会让这个引用失效。

方法4:使用unsafe代码(不推荐)

若你能确保逻辑绝对安全,可以用unsafe绕过借用检查,但这会失去Rust的内存安全保障,仅在必要时使用:

pub fn take(&mut self) -> Option<&str> {
    if let Some(lexem) = self.peek() {
        unsafe {
            let ptr = &mut self.offset as *mut usize;
            *ptr += 1;
        }
        Some(lexem)
    } else {
        None
    }
}

内容的提问来源于stack exchange,提问作者Poseydon

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 06:33:35