在 Rust 中实现通用的可增量特征

问题描述 投票:0回答:4

我正在尝试了解如何在 Rust 中实现通用特征。

虽然我已经看过很多示例,但这些示例与特定用途(例如基因组变异器)太过紧密相关,以至于我目前无法在 Rust 开发中理解。

相反,这是一个基于相当普遍的东西的简单示例——增量:

trait Incrementable {
    fn post_inc(&mut self) -> Self;
    fn post_inc_by(&mut self, n: usize) -> Self;
}

impl Incrementable for usize {
    fn post_inc(&mut self) -> Self {
        let tmp = *self;
        *self += 1;
        tmp
    }

    //"Overload" for full generalizability
    fn post_inc_by(&mut self, n: usize) -> Self {
        let tmp = *self;
        *self += n;
        tmp
    }
}

fn main() {
    let mut result = 0;
    assert!(result.post_inc() == 0);
    assert!(result == 1);

    assert!(result.post_inc_by(3) == 1);
    assert!(result == 4);
}

上面的代码可以工作,但是有所欠缺,因为如果不编写大量样板代码,它就无法推广到所有数字类型。

在我尝试泛化上述代码时,我陷入了与类型系统的斗争,借用了检查器,或者被迫走上了一条为我想要在通用版本中支持的每种类型实现

FromPrimitive
的道路(有效地让我回到了到平方一)。

你能帮我理解如何一般地实现

Incrementable
吗? 这样
post_inc()
post_inc_by()
至少适用于所有整数和浮点类型,理想情况下无需为每种类型编写实现?

我希望答案能帮助我了解特征、实现、类型和关联类型如何在比我遇到的更简单的用例中协同工作。

我使用的是 Rust 1.16.0。

rust traits generic-programming
4个回答
5
投票

@Simon Whitehead 的示例可以轻松适应稳定的 Rust:

trait Incrementable: Copy + std::ops::AddAssign<Self> {
    fn one() -> Self;

    fn post_inc(&mut self) -> Self {
        self.post_inc_by(Self::one())
    }

    fn post_inc_by(&mut self, n: Self) -> Self {
        let tmp = *self;
        *self += n;
        tmp
    }
}

impl Incrementable for u8  { fn one() -> Self {1} }
impl Incrementable for u16 { fn one() -> Self {1} }
impl Incrementable for u32 { fn one() -> Self {1} }
impl Incrementable for u64 { fn one() -> Self {1} }
impl Incrementable for i8  { fn one() -> Self {1} }
impl Incrementable for i16 { fn one() -> Self {1} }
impl Incrementable for i32 { fn one() -> Self {1} }
impl Incrementable for i64 { fn one() -> Self {1} }
impl Incrementable for f32 { fn one() -> Self {1.0} }
impl Incrementable for f64 { fn one() -> Self {1.0} }

虽然您需要对每种类型进行实现,但每种类型都非常简单。

您还可以使用宏来隐藏重复的实现:

macro_rules! impl_Incrementable{
    ($($m:ty),*) => {$( impl Incrementable for $m  { fn one() -> Self { 1 as $m } })*}
}

impl_Incrementable!{u8, u16, u32, u64, i8, i16, i32, i64, f32, f64}

3
投票

您可以使用宏来做到这一点,遵循 std 的做法

trait Incrementable {
    fn post_inc(&mut self) -> Self;
    fn post_inc_by(&mut self, n: Self) -> Self;
}

macro_rules! post_inc_impl {
    ($($t:ty)*) => ($(
        impl Incrementable for $t {
            fn post_inc(&mut self) -> Self {
                self.post_inc_by(1 as Self)
            }

            fn post_inc_by(&mut self, n: Self) -> Self {
                let tmp = *self;
                *self += n;
                tmp
            }
        }
    )*)
}

post_inc_impl! { usize u8 u16 u32 u64 isize i8 i16 i32 i64 f32 f64 }

fn main() {
    let mut result = 0;
    assert!(result.post_inc() == 0);
    assert!(result == 1);

    assert!(result.post_inc_by(3) == 1);
    assert!(result == 4);
}

如果您使用

num
crate:

则无需宏也是可以的
extern crate num;

use num::Num;

trait Incrementable<T: Num> {
    fn post_inc(&mut self) -> Self;
    fn post_inc_by(&mut self, n: T) -> Self;
}

impl<T: Num + std::ops::AddAssign<T> + Copy> Incrementable<T> for T {
    fn post_inc(&mut self) -> T {
        let tmp = *self;
        *self += T::one();
        tmp
    }

    fn post_inc_by(&mut self, n: T) -> Self {
        let tmp = *self;
        *self += n;
        tmp
    }
}

3
投票

我们可以增加的类型需要

  1. 认识操作员并
    +=
    (
    AddAssign
    )
  2. 为“one”元素定义一个值
  3. 可复制,因为我们想保留旧的未递增值。

第 1 点和第 3 点。我们可以通过使用特征界限来保证,对于第 2 点,我们可以设置一个具有函数

one() -> self
的特征。

这是一个有效的示例:

// We need to know the operator "+="
use std::ops::AddAssign;

// The trait needs a type parameter
trait Incrementable<T> {
    fn post_inc(&mut self) -> Self;
    fn post_inc_by(&mut self, n: T) -> Self;
}

// We need a trait which tells us the "one" value for a type
trait Increment {
    fn one() -> Self;
}

// We need to implement the Increment trait for every type
// we want to increment.
impl Increment for usize {
    fn one() -> usize {
        1
    }
}

// Finally we implement the Increment trait generically for all types that
// * know the operator "+=" AddAssign
// * are copyable
// * implement our Increment trait, so that we know their "one" value
impl<T: AddAssign + Increment + Copy> Incrementable<T> for T {
    fn post_inc(&mut self) -> Self {
        let tmp = *self;
        *self += T::one();
        tmp
    }

    //"Overload" for full generalizability
    fn post_inc_by(&mut self, n: T) -> Self {
        let tmp = *self;
        *self += n;
        tmp
    }
}

fn main() {
    let mut result = 0;
    assert!(result.post_inc() == 0);
    assert!(result == 1);

    assert!(result.post_inc_by(3) == 1);
    assert!(result == 4);
}

您不必为每种类型编写

Incrementable
的实现,但您必须实现提供
one()
函数的特征。没有它你就无法逃脱,因为对于非数字类型,“加一”的含义并不明显。

我将所有内容保留在可以通用实现的通用实现中。例外是

T::one()
,因此除了每种类型的这个简单函数之外,不需要样板代码。


0
投票

我知道这已经很老了,但我认为有一个更干净的解决方案(也许在原始帖子发布时它不起作用)。

其他解决方案通常使用宏来生成所有

one()
,或者如果在递增(或逆)时使用某种
i8
,则无法使用
From<u8>

诀窍在于,所有数字类型似乎都是

From<bool>
,而
true
实际上是
1
:-)

所以这很有效:

trait Incrementable {
    fn post_inc(&mut self) -> Self;
    fn post_inc_by(&mut self, n: Self) -> Self;
}

impl<T: Copy + std::ops::AddAssign<Self> + From<bool>> Incrementable for T {

    fn post_inc(&mut self) -> Self {
        self.post_inc_by(Self::from(true))
    }

    fn post_inc_by(&mut self, n: Self) -> Self {
        let tmp = *self;
        *self += n;
        tmp
    }
}

pub fn test_inc() {
    let mut x= 42i8; x.post_inc(); println!("{x}"); // 43
    let mut x = 42u32; x.post_inc(); println!("{x}"); // 43
    let mut x= 42usize; x.post_inc(); println!("{x}"); // 43
    let mut x = 4.2f64; x.post_inc(); println!("{x}"); // 5.2
    //...
}

无需

num
、宏或其他任何东西。我看到的唯一问题是
from(true)=1
已记录但不是很明确。

© www.soinside.com 2019 - 2024. All rights reserved.