[英]Idioms for DRY code inside trait implementations (rustlings example)
I am currently making my way through the Rust book and rustlings .我目前正在阅读Rust 书和rustlings 。
try_from_into.rs
asks us to implement TryFrom
for a tuple, and array, and a slice. try_from_into.rs
要求我们为元组、数组和切片实现TryFrom
。 The array and slice versions fit nicely into an iter/map/collect
pattern, eg as follows:数组和切片版本非常适合
iter/map/collect
模式,例如如下:
// Array implementation
impl TryFrom<[i16; 3]> for Color {
type Error = IntoColorError;
fn try_from(arr: [i16; 3]) -> Result<Self, Self::Error> {
match arr
.iter()
.map(|x| u8::try_from(*x))
.collect::<Result<Vec<_>, _>>()
{
Ok(v) => Ok(Color {
red: v[0],
green: v[1],
blue: v[2],
}),
_ => Err(IntoColorError::IntConversion),
}
}
}
Possibly this is not idiomatic either, so I'd appreciate any corrections.可能这也不是惯用的,所以我很感激任何更正。
However, the tuple implementation seems to leave me with two choices:然而,元组实现似乎让我有两个选择:
Put the tuple (of 3 i16
s) into an array and then use the map
pattern above.将元组(3 个
i16
s)放入一个数组中,然后使用上面的map
模式。 This seems wasteful.这似乎很浪费。
Repeat myself by converting each value to u8
, checking the result, and assigning to a local variable, eg通过将每个值转换为
u8
,检查结果并分配给局部变量来重复自己,例如
fn try_from(tuple: (i16, i16, i16)) -> Result<Self, Self::Error> { let red = match u8::try_from(val) { Ok(v) => Ok(v), _ => return Err(IntoColorError::IntConversion), }; let green = ... let blue = ... Ok(Color { red, green, blue }) }
My first instinct is to put the match
code into a helper and inline it if the language supported doing so, but Rust seems to have some barriers to that:我的第一直觉是将
match
代码放入一个帮助程序中,如果语言支持这样做,则将其内联,但 Rust 似乎对此有一些障碍:
Self::Error
is outside of the implementation.Self::Error
失去了任何概念。Self::Error
in the try_from
definition, eg liketry_from
定义中使用Self::Error
的内部函数,例如fn try_from(tuple: (i16, i16, i16)) -> Result<Self, Self::Error> { #[inline] fn i16_to_u8(val: i16) -> Result<u8, Self::Error> { match u8::try_from(val) { Ok(v) => Ok(v), _ => return Err(IntoColorError::IntConversion), } } let red = i16_to_u8(tuple.0)?; ... }
What are the Rust idioms for avoiding repeated code inside trait implementations, especially where a helper method seems like the obvious choice to someone coming from languages where these are commonplace?避免在 trait 实现中重复代码的 Rust 习语是什么,特别是在辅助方法似乎是来自那些很常见的语言的人的明显选择的地方?
You could change the match
block to a ?
您可以将
match
块更改为?
if you map the error into an IntoColorError
.如果您将错误映射到
IntoColorError
。 That will allow you to continue chaining methods so you can call try_into()
to convert the Vec<u8>
into [u8; 3]
这将允许您继续链接方法,以便您可以调用
try_into()
将Vec<u8>
转换为[u8; 3]
[u8; 3]
, which can in turn be destructured into separate red
, green
, and blue
variables. [u8; 3]
,可以依次分解为单独的red
、 green
和blue
变量。
impl TryFrom<[i16; 3]> for Color {
type Error = IntoColorError;
fn try_from(arr: [i16; 3]) -> Result<Self, Self::Error> {
let [red, green, blue]: [u8; 3] = arr
.iter()
.map(|x| u8::try_from(*x))
.collect::<Result<Vec<_>, _>>()
.map_err(|_| IntoColorError::IntConversion)?
.try_into()
.unwrap();
Ok(Color{ red, green, blue })
}
}
I'd probably break that up into two or three separate statements myself, but you get the idea.我自己可能会把它分成两到三个单独的陈述,但你明白了。
If you apply the same error mapping idea to the tuple case you can get it fairly compact.如果您将相同的错误映射思想应用于元组案例,您可以获得相当紧凑。 It's still repetitive but the repetition isn't too bad thanks to
?
它仍然是重复的,但由于
?
: :
impl TryFrom<(i16, i16, i16)> for Color {
type Error = IntoColorError;
fn try_from(tuple: (i16, i16, i16)) -> Result<Self, Self::Error> {
let (red, green, blue) = tuple;
let red: u8 = red.try_into().map_err(|_| IntoColorError::IntConversion)?;
let green: u8 = green.try_into().map_err(|_| IntoColorError::IntConversion)?;
let blue: u8 = blue.try_into().map_err(|_| IntoColorError::IntConversion)?;
Ok(Color{ red, green, blue })
}
}
You can see both implementations on the Playground .您可以在Playground上看到这两种实现。
It is possible to eliminate the repetitive map_err
calls.可以消除重复的
map_err
调用。 If you're on nightly then you could use a try
block to capture the TryFromIntError
s and convert them to IntoColorError
s.如果您每晚都在,那么您可以使用
try
块来捕获TryFromIntError
s 并将它们转换为IntoColorError
s。
#![feature(try_blocks)]
impl TryFrom<(i16, i16, i16)> for Color {
type Error = IntoColorError;
fn try_from(tuple: (i16, i16, i16)) -> Result<Self, Self::Error> {
let result: Result<_, TryFromIntError> = try {
let (red, green, blue) = tuple;
let red: u8 = red.try_into()?;
let green: u8 = green.try_into()?;
let blue: u8 = blue.try_into()?;
Color{ red, green, blue }
};
result.map_err(|_| IntoColorError::IntConversion)
}
}
On stable you could achieve the same effect with an immediately-invoked function expression, or IIFE :在 stable 上,您可以使用立即调用的函数表达式或IIFE实现相同的效果:
impl TryFrom<(i16, i16, i16)> for Color {
type Error = IntoColorError;
fn try_from(tuple: (i16, i16, i16)) -> Result<Self, Self::Error> {
(|| {
let (red, green, blue) = tuple;
let red: u8 = red.try_into()?;
let green: u8 = green.try_into()?;
let blue: u8 = blue.try_into()?;
Ok(Color{ red, green, blue })
})()
.map_err(|_: TryFromIntError| IntoColorError::IntConversion)
}
}
What are the Rust idioms for avoiding repeated code inside trait implementations, especially where a helper method seems like the obvious choice to someone coming from languages where these are commonplace?
避免在 trait 实现中重复代码的 Rust 习语是什么,特别是在辅助方法似乎是来自那些很常见的语言的人的明显选择的地方?
Free functions.免费功能。 You can make helper methods outside the
impl
block.您可以在
impl
块之外创建辅助方法。
You seem to have a little bit of a misconception of how associated types in traits work, because您似乎对特征中的关联类型的工作方式有一点误解,因为
However, I lose any notion of what Self::Error is outside of the implementation.
但是,我对实现之外的 Self::Error 失去了任何概念。
is not strictly true.不是严格正确的。 When you leave the trait's impl block, you don't lose the notion of
Self::Error
, you lose the notion of Self
.当你离开 trait 的 impl 块时,你不会失去
Self::Error
的概念,你会失去Self
的概念。 You can create a helper function outside the the impl that refers to the type:您可以在引用类型的 impl 之外创建一个辅助函数:
fn i16_to_u8(n:i16) -> Result<u8, <Color as TryFrom<(i16,i16,i16)>>::Error>{
n.try_into().map_err(|_| IntoColorError::IntConversion)
}
You can also make an associated function in a separate impl block for Color
:您还可以在单独的 impl 块中为
Color
制作关联函数:
impl Color{
fn i16_to_u8(n:i16) -> Result<u8, <Self as TryFrom<(i16,i16,i16)>>::Error>{
n.try_into().map_err(|_| IntoColorError::IntConversion)
}
}
The associated type for a trait exists in all scopes the trait is in. In many cases, you do need the fully qualified syntax, <Type as Trait>::Name
so the compiler knows which implementation's type to use (this occurs for TryFrom
because of a blanket impl over From
).特征的关联类型存在于特征所在的所有作用域中。在许多情况下,您确实需要完全限定的语法
<Type as Trait>::Name
以便编译器知道要使用哪个实现的类型(这发生在TryFrom
因为在From
上的一揽子实现。
You can also manually desugar the type alias yourself, but it does reduce maintainability:您也可以自己手动对类型别名进行脱糖,但这确实会降低可维护性:
fn i16_to_u8(n:i16) -> Result<u8, IntoColorError>{
n.try_into().map_err(|_| IntoColorError::IntConversion)
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.