提问人:Roman Liutko 提问时间:10/26/2023 最后编辑:Roman Liutko 更新时间:10/26/2023 访问量:127
从匹配表达式返回切片(或未知大小的数组)
Return slice (or array of unknown size) from match expression
问:
我正在尝试实现以下想法:
let command = ...;
let request = match subcommand {
Some(x) => [command, x as u8, (x >> 8) as u8],
None => [command],
};
request
应为 1 的数组(如果没有子命令)或 3(如果有 2 字节的子命令)。显然,这种方法不起作用,因为匹配表达式的分支“返回”不同的类型([u8;1] 或 [u8;3]);
我的逻辑跟进是使用切片:
let request: &[u8] = match subcommand {
Some(x) => &[...],
None => &[...],
};
这在常量值(例如)上效果很好,但是当我尝试使用我的变量构建数组时,它失败了。&[0, 0, 0]
temporary value dropped while borrowed
我明白了,在这种情况下,我的引用是转义到高级代码块,但是有没有办法解决它(即一些生命周期注释?
事先创建缓冲区并使用切片确实有效,但感觉不是最佳的。
UPD:这是我尝试调试的函数:
// Read the data from the device.
async fn read<R>(&mut self, command: u8, subcommand: Option<u16>) -> Result<R, ChipError<E>>
where
R: TryFrom<u16>,
{
// If the caller has provided a subcommand, build the request with command and subcommand.
// If there is no subcommand, use the plain command instead
let request: &[u8] = match subcommand {
Some(x) => &[command, x as u8, (x >> 8) as u8],
None => &[command],
};
// Send the bytes to the device
self.i2c.write(self.addr, &request).await?;
// And read the response...
let mut response = [0, 0];
self.i2c
.write_read(self.addr, &[commands::CONTROL], &mut response)
.await?;
match u16::from_le_bytes(response).try_into() {
Ok(value) => Ok(value),
Err(_) => Err(ChipError::Value),
}
}
这就是编译器输出:
error[E0716]: temporary value dropped while borrowed
--> xxxx/src/lib.rs:69:25
|
68 | let request: &[u8] = match subcommand {
| ______________________________-
69 | | Some(x) => &[command, x as u8, (x >> 8) as u8],
| | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-
| | | |
| | | temporary value is freed at the end of this statement
| | creates a temporary value which is freed while still in use
70 | | None => &[command],
71 | | };
| |_________- borrow later used here
|
= note: consider using a `let` binding to create a longer lived value
答:
有多种方法可以做到这一点。以下是一些建议。
- 只需使用 (或 )。是的,它的效率较低,但这对你重要吗?这可能是最明确的解决方案。
Vec
Box<[u8]>
- 使用堆栈分配的向量,例如
ArrayVec
。 - 如果类型具有简单的默认值,则在外部作用域中有一个数组并初始化其中的一部分,然后返回该部分的切片:
let mut scratch_space = [0; 3];
let request = match subcommand {
Some(x) => {
let data = &mut scratch_space[..3];
data.copy_from_slice(&[command, x as u8, (x >> 8) as u8]);
data
},
None => {
scratch_space[0] = command;
&scratch_space[..1]
},
};
- 如果无法做到这一点,请声明多个这样的数组,并且只初始化一个。那是:
let scratch_space1;
let scratch_space2;
let request = match subcommand {
Some(x) => {
scratch_space1 = [command, x as u8, (x >> 8) as u8];
&scratch_space1[..]
},
None => {
scratch_space2 = [command];
&scratch_space2[..]
},
};
评论
这并不能直接回答标题中的问题,但假设你的编写者是 std::io::write(我知道它是 i2c,但你应该能够适应),我会做一个简单的枚举,如下所示:
use std::io::{Write, Error};
enum Request {
Command(u8),
CommandWithSubcommand(u8, u16)
}
impl Request {
fn write_to(&self, mut writer: impl Write) -> Result<(), Error> {
match self {
Self::Command(command) => {
writer.write_all(&[*command])?
}
Self::CommandWithSubcommand(command, subcommand) => {
let [b1, b2] = subcommand.to_le_bytes(); //maybe to_be_bytes()?
writer.write_all(&[*command, b1, b2])?
}
};
Ok(())
}
}
...
let request = match subcommand {
Some(subcommand) => {
Request::CommandWithSubcommand(command, subcommand)
}
None => {
Request::Command(command)
}
};
request.write_to(your_std_writer)?;
...
因为我们对每个分支分别使用 write_all() 方法,并且它们都返回相同的结果类型,所以我们不必担心生存期。这可能不优雅,但有利。
从技术上讲,您可以在 中使用单独的写入调用,但我建议使用枚举来帮助推理 1-of-N-known-implementation 问题。match subcommand
评论
read()
上面的两个答案列出了很好的解决方案。正如他们的回答中提到的:
- Rust 必须知道返回值的大小,因为它存在于堆栈上。返回两个不同大小的值不起作用,因为它不是确切的大小。
- 您不能返回对堆栈上存在的数据的引用,因为这会导致释放后使用。因此,您不能返回切片。
这是另一个不在堆上分配的解决方案。
use either::Either;
fn split(yes: bool) -> Either<[u8; 2], [u8; 4]> {
if yes {
Either::Left([1, 2])
} else {
Either::Right([1, 2, 3, 4])
}
}
任何一个都是任意两种类型的有用求和类型。作为求和类型,大小是两个变体中较大的一个加上判别式。在这种情况下,返回值的总大小为 5 个字节,因为 是两个值中较大的一个,判别式为 1 个字节。如果你不想再添加一个板条箱,你可以简单地编写自己的板条箱,因为该类型不涉及任何魔法。[u8; 4]
Either
pub enum Either<L, R> {
Left(L),
Right(R)
}
评论
Box<[u8]>