简体   繁体   English

trait 实现中 DRY 代码的习语(rustlings 示例)

[英]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),
        }
    }
}

Playground 操场

Possibly this is not idiomatic either, so I'd appreciate any corrections.可能这也不是惯用的,所以我很感激任何更正。

However, the tuple implementation seems to leave me with two choices:然而,元组实现似乎让我有两个选择:

  1. Put the tuple (of 3 i16 s) into an array and then use the map pattern above.将元组(3 个i16 s)放入一个数组中,然后使用上面的map模式。 This seems wasteful.这似乎很浪费。

  2. 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 似乎对此有一些障碍:

  • Private trait methods are not allowed , so I can't just add a helper inside the implementation. 不允许使用私有 trait 方法,所以我不能只在实现中添加一个帮助器。 However, I lose any notion of what Self::Error is outside of the implementation.但是,我对实现之外的Self::Error失去了任何概念。
  • We can't use generic parameters from outer functions , so neither can I create an inner function which uses Self::Error in the try_from definition, eg like我们不能使用外部函数的泛型参数,所以我也不能创建在try_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] ,可以依次分解为单独的redgreenblue变量。

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)
    }
}

Playground 操场

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)
    }
}

Playground 操场


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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM