Rust中函数之间的共享值

sz81bmfz  于 2022-12-04  发布在  其他
关注(0)|答案(1)|浏览(145)

我正在尝试学习Rust,但遇到了瓶颈,我想做一些我认为相对简单的事情。我正在尝试为ESP 32 c3 MCU编写一个简单的闪烁示例。我得到了一个基本的示例,但在尝试扩展/概括该示例时开始遇到编译错误。
我的项目由一个货物工作区和两个板条箱组成-entrypointblink
我能够得到以下基本版本的工作没有问题:
第一个
然后,我想改进错误处理(停止在blink机箱中使用unwrap()),并使blink()函数可重用(如果Peripherals::take()调用被多次执行,它会死机)。
我做了以下的修改来改进错误处理。这个版本也运行得很好,我把它包括进来只是为了得到反馈,了解我的方法有多习惯/你会有什么不同?我猜制作一个自定义的错误类型会是更好的实践,或者即使在生产代码中,返回一个字符串切片作为错误是可以接受的/常见的吗?

pub fn blink(count: i32) -> Result<(), &'static str> {
    let peripherals = Peripherals::take().ok_or("Failed to take peripherals")?; 
    let mut led = gpio::PinDriver::output(peripherals.pins.gpio8).map_err(|_: EspError| "Failed to set pin to output")?;
    for _ in 0..count {
        led.set_high().map_err(|_: EspError| "Failed to set pin high")?;
        thread::sleep(Duration::from_secs(1));
        led.set_low().map_err(|_: EspError| "Failed to set pin low")?;
        thread::sleep(Duration::from_secs(1));
    }
    Ok(())
}

接下来,我尝试通过将Peripherals::take()调用与blink()函数的其余部分分离,使blink()函数可重用,这样它在 Boot 时只能被调用一次。但是我想让blink板条箱负责进行Peripherals::take()调用,这就是我开始遇到问题的地方。

**第一次尝试:**我的第一个方法是尝试使用全局变量Peripherals。我很快发现这行不通,除非我用thread_local宏 Package 全局变量,或者将对全局变量的操作 Package 到一个我想避免的unsafe块中。我尝试了很多方法,但是在使用thread_local时无法编译代码。

带和不带RefCell(我发现一些文章建议使用RefCell,但是在尝试和阅读文档之后,我没有看到一个很好的理由将其用于我的用例),thread_local似乎将我的全局变量 Package 到LocalKey中。我不知道如何使用LocalKey,除了with()函数之外--如果可能的话,我希望避免使用with(),因为我需要将代码移到闭包中,我也不知道如何将for循环保留在闭包之外,而只从闭包内部初始化led变量-通常我会在闭包中使用d将变量声明移出闭包,初始化为null,但据我所知,null似乎不是Rust中存在的概念。

thread_local! {
    static PERIPHERALS: Option<Peripherals> = Peripherals::take();
}

pub fn blink(count: i32) -> Result<(), &'static str> {
    PERIPHERALS.with(| p | {
        let peripherals = match p {
            Some(peripherals) => peripherals,
            None => return Err("Failed to take peripherals")
        };
        let mut led = gpio::PinDriver::output(peripherals.pins.gpio8).map_err(|_: EspError| "Failed to set pin to output")?;
        for _ in 0..count {
            led.set_high().map_err(|_: EspError| "Failed to set pin high")?;
            thread::sleep(Duration::from_secs(1));
            led.set_low().map_err(|_: EspError| "Failed to set pin low")?;
            thread::sleep(Duration::from_secs(1));
        }
        Ok(())
    })
}

上述代码导致以下编译器错误:

error[E0507]: cannot move out of `peripherals.pins.gpio8` which is behind a shared reference
  --> blink/src/lib.rs:19:47
   |
19 |         let mut led = gpio::PinDriver::output(peripherals.pins.gpio8).map_err(|_: EspError| "Failed to set pin to output")?;
   |                                               ^^^^^^^^^^^^^^^^^^^^^^ move occurs because `peripherals.pins.gpio8` has type `Gpio8`, which does not implement the `Copy` trait

For more information about this error, try `rustc --explain E0507`.
error: could not compile `blink` due to previous error

如果我尝试先取消引用peripherals变量,也会出现同样的错误:

...
let mut led = gpio::PinDriver::output((*peripherals).pins.gpio8).map_err(|_: EspError| "Failed to set pin to output")?;
...

**第二次尝试:**作为我的下一个方法,我尝试写一个结构体,其中有两个函数,它们可以作为一个类。不幸的是,我遇到了完全相同的编译器错误。

第一个
我对Rust中的借用、引用和/或变量移动/复制的工作方式还没有足够好的理解,只是还没有能够解决这个问题。它似乎与我熟悉的其他(更传统的)语言(C、C++、Java、JS/TS、Python、Dart)有着巨大的不同。
如果您在上面的代码中发现任何不寻常的地方,我也会非常感谢您提供任何最佳实践建议/更正。

6pp0gazn

6pp0gazn1#

错误的基本要点可以用一个更简单的例子来重现:

struct Foo {
  bar: String
}

impl Foo {
  fn baz(&self) {
    let s = self.bar;
  }
}

现在的情况是:

  • self具有&Foo类型,因为参数是用&self声明的,&selfself: &Self的简写
  • self.bar具有String类型,因为bar声明为String
  • 这会导致一个问题,我们正在尝试 * 将s移出self,但我们只有对self的引用,而没有对self的拥有访问权限

你需要找到一种方法来使它在没有拥有权限的情况下工作。免责声明,我以前没有用过这个板条箱(安装说明有点疯狂),但这里是我可以从PinDriver::output的文档中拼凑出来的

  • PinDriver::output接受impl Peripheral,即实现特征Peripheral的任何类型
  • 查看docs for Peripheral,我们可以看到实现它的结构体列表。
  • Gpio8确实实现了它,但这并不好,因为我们会再次遇到“无法移出共享引用”的问题
  • 在大多数Rust代码中,您可能希望.clone() gpio pin,但是,没有克隆方法(因为它代表物理资源的唯一句柄,除非您有一些可以生成新pin的板:p)
  • 然而,在最下面,有一个impl:
impl<T: DerefMut> Peripheral for T
where
    T::Target: Peripheral { ... }
  • 即实现DerefMut<Target = T>的任何类型实现Periperal,只要T也实现Peripheral
  • 这意味着你可以使用&mut Gpio8。这是有意义的,因为你要改变灯的状态,所以你需要一个可变的句柄。如果你把blink()改为&mut self,你应该可以写PinDriver::output(&mut self.peripherals.pins.gpio8)

FWIW,嵌入式Rust经常使用聪明的类型系统技巧在编译时验证更多的行为(要么是为了保存宝贵的CPU周期,要么是为了更好的可靠性)。

相关问题