我正在尝试学习Rust,但遇到了瓶颈,我想做一些我认为相对简单的事情。我正在尝试为ESP 32 c3 MCU编写一个简单的闪烁示例。我得到了一个基本的示例,但在尝试扩展/概括该示例时开始遇到编译错误。
我的项目由一个货物工作区和两个板条箱组成-entrypoint
和blink
。
我能够得到以下基本版本的工作没有问题:
第一个
然后,我想改进错误处理(停止在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)有着巨大的不同。
如果您在上面的代码中发现任何不寻常的地方,我也会非常感谢您提供任何最佳实践建议/更正。
1条答案
按热度按时间6pp0gazn1#
错误的基本要点可以用一个更简单的例子来重现:
现在的情况是:
self
具有&Foo
类型,因为参数是用&self
声明的,&self
是self: &Self
的简写self.bar
具有String
类型,因为bar
声明为String
s
移出self
,但我们只有对self的引用,而没有对self的拥有访问权限你需要找到一种方法来使它在没有拥有权限的情况下工作。免责声明,我以前没有用过这个板条箱(安装说明有点疯狂),但这里是我可以从
PinDriver::output
的文档中拼凑出来的PinDriver::output
接受impl Peripheral
,即实现特征Peripheral
的任何类型Peripheral
,我们可以看到实现它的结构体列表。Gpio8
确实实现了它,但这并不好,因为我们会再次遇到“无法移出共享引用”的问题.clone()
gpio pin,但是,没有克隆方法(因为它代表物理资源的唯一句柄,除非您有一些可以生成新pin的板:p)DerefMut<Target = T>
的任何类型实现Periperal
,只要T
也实现Peripheral
&mut Gpio8
。这是有意义的,因为你要改变灯的状态,所以你需要一个可变的句柄。如果你把blink()
改为&mut self
,你应该可以写PinDriver::output(&mut self.peripherals.pins.gpio8)
FWIW,嵌入式Rust经常使用聪明的类型系统技巧在编译时验证更多的行为(要么是为了保存宝贵的CPU周期,要么是为了更好的可靠性)。