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

Rust编译错误E0499:返回值变更为何引发self多次可变借用冲突

DS18B20 Rust驱动中Result返回值引发的可变借用冲突问题

问题现象

开发DS18B20的Rust驱动时,将onewire_reset方法的返回值从bool改为Result<(), &str>后,出现如下编译错误:

error[E0499]: cannot borrow `*self` as mutable more than once at a time
  --> src/component/ds18b20.rs:60:9
   |
37 |     pub fn temperature_read(&mut self) -> Result<f32, &str> {
   |                             - let's call the lifetime of this reference `'1`
38 |         self.onewire_reset()?;
   |         ---------------------
   |         |
   |         first mutable borrow occurs here
   |         returning this value requires that `*self` is borrowed for `'1`
39 |
40 |         self.onewire_byte_write(CMD::Skip as u8);
   |         ^^^^ second mutable borrow occurs here

注释掉temperature_read中第一个self.onewire_reset()?;后,第二个self.onewire_reset()?;下方的self.onewire_byte_write仍会触发相同错误。

相关核心代码如下:

use esp_idf_svc::hal::delay::*;
use esp_idf_svc::hal::gpio::*;
use esp_idf_svc::hal::peripheral::*;

enum CMD {
    Skip = 0xcc,
    Write = 0x4e,
    Temp = 0x44,
    Read = 0xbe,
}

pub struct Ds18b20<'a, OnewirePin>
where
    OnewirePin: Peripheral<P = OnewirePin> + InputPin + OutputPin,
{
    pin: PinDriver<'a, OnewirePin, InputOutput>,
    delay: Delay,
}

impl<'a, OnewirePin> Ds18b20<'a, OnewirePin>
where
    OnewirePin: Peripheral<P = OnewirePin> + InputPin + OutputPin,
{
    pub fn temperature_read(&mut self) -> Result<f32, &str> {
        self.onewire_reset()?;

        self.onewire_byte_write(CMD::Skip as u8);
        self.onewire_byte_write(CMD::Temp as u8);

        self.onewire_reset()?;

        self.onewire_byte_write(CMD::Skip as u8);
        self.onewire_byte_write(CMD::Read as u8);

        let temp_l = self.onewire_byte_read();
        let temp_h = self.onewire_byte_read();

        Ok((((temp_h as i16) << 8 | (temp_l as i16)) as f32) * 0.0625)
    }

    fn onewire_reset(&mut self) -> Result<(), &str> {
        self.pin.set_low().unwrap();
        self.delay.delay_us(600);
        self.pin.set_high().unwrap();
        self.delay.delay_us(80);

        if self.pin.is_low() {
            self.delay.delay_us(300);

            if self.pin.is_high() {
                Ok(())
            } else {
                Err("reset err")
            }
        } else {
            Err("reset err")
        }
    }

    fn onewire_byte_write(&mut self, mut byte: u8) {
        for _ in 0..8 {
            let bit = byte & 0x01 != 0;
            byte >>= 1;

            self.pin.set_low().unwrap();
            self.delay.delay_us(if bit { 5 } else { 60 });

            self.pin.set_high().unwrap();
            self.delay.delay_us(if bit { 60 } else { 5 });
        }
    }
}

问题原因

这是Rust生命周期推导的典型问题:

  • 当onewire_reset返回Result<(), &str>时,编译器会默认推导错误字符串的生命周期与&mut self的生命周期'1绑定(编译器无法自动识别错误字面量是'static的)。
  • 使用?运算符时,这个绑定会导致self的可变借用被延长到整个函数周期,后续调用self.onewire_byte_write时就会触发二次可变借用的冲突。
  • 之前返回bool时没有这个问题,因为bool不涉及生命周期绑定,借用会在语句结束后立即释放。

解决方案

有两种简单的修复方式:

方法一:明确错误字符串的生命周期为'static

修改onewire_reset和temperature_read的返回类型,标注错误字符串的生命周期为'static,让编译器知道错误信息不依赖self的生命周期:

pub fn temperature_read(&mut self) -> Result<f32, &'static str> {
    // 函数内容不变
}

fn onewire_reset(&mut self) -> Result<(), &'static str> {
    self.pin.set_low().unwrap();
    self.delay.delay_us(600);
    self.pin.set_high().unwrap();
    self.delay.delay_us(80);

    if self.pin.is_low() {
        self.delay.delay_us(300);

        if self.pin.is_high() {
            Ok(())
        } else {
            Err("reset err") // 字面量默认是'static生命周期
        }
    } else {
        Err("reset err")
    }
}

方法二:将错误信息改为String类型(适用于动态错误场景)

如果后续需要动态生成错误信息,可以改用拥有所有权的String,完全避免生命周期绑定问题:

pub fn temperature_read(&mut self) -> Result<f32, String> {
    // 函数内容不变
}

fn onewire_reset(&mut self) -> Result<(), String> {
    self.pin.set_low().unwrap();
    self.delay.delay_us(600);
    self.pin.set_high().unwrap();
    self.delay.delay_us(80);

    if self.pin.is_low() {
        self.delay.delay_us(300);

        if self.pin.is_high() {
            Ok(())
        } else {
            Err("reset err".to_string())
        }
    } else {
        Err("reset err".to_string())
    }
}

原理说明

  • 方法一利用了Rust字符串字面量默认拥有'static生命周期的特性,明确标注后,编译器不会将错误信息与self的生命周期绑定,self的可变借用会在onewire_reset调用结束后立即释放,后续操作不再触发借用冲突。
  • 方法二通过使用String,摆脱了对引用生命周期的依赖,代价是少量内存分配,对于嵌入式场景来说这种开销几乎可以忽略。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 12:35:03