用于修改标量和切片的通用函数

时间:2015-12-25 23:11:36

标签: rust

我不懂Rust中的一些基础知识。我想计算一个函数sinc(x),其中x是标量或切片,它会修改值。我可以为这两种类型实现方法,用x.sinc()调用它们,但我发现它更方便(并且更容易在长公式中读取)来创建一个函数,例如sinc(&mut x)。那么你如何正确地做到这一点?

pub trait ToSinc<T> {
    fn sinc(self: &mut Self) -> &mut Self;
}

pub fn sinc<T: ToSinc<T>>(y: &mut T) -> &mut T {
    y.sinc()
}

impl ToSinc<f64> for f64 {
    fn sinc(self: &mut Self) -> &mut Self {
        *self = // omitted
        self
    }
}

impl<'a> ToSinc<&'a mut [f64]> for &'a mut [f64] {
    fn sinc(self: &mut Self) -> &mut Self {
        for yi in (**self).iter_mut() { ... }
        self
    }
}

这似乎有效,但不是最后impl昂贵的“双重间接”?我也想过做

pub trait ToSinc<T> {
    fn sinc(self: Self) -> Self;
}

pub fn sinc<T: ToSinc<T>>(y: T) -> T {
    y.sinc()
}

impl<'a> ToSinc<&'a mut f64> for &'a mut f64 {
    fn sinc(self) -> Self {
        *self = ...
        self
    }
}

impl<'a> ToSinc<&'a mut [f64]> for &'a mut [f64] {
    fn sinc(self) -> Self {
        for yi in (*self).iter_mut() { ... }
        self
    }
}

这也有效,不同之处在于如果x&mut [f64]切片,我可以拨打sinc(x)而不是sinc(&mut x)。所以我觉得第二个方面的间接性较少,我觉得这很好。我在这里错了吗?

1 个答案:

答案 0 :(得分:3)

我发现双重间接的任何差异都不太可能在这种情况下,但你是正确的,第二个是首选。

您有ToSinc<T>,但请勿使用T。删除模板参数。

尽管如此,ToSinc几乎肯定是f64 s的值:

impl ToSinc for f64 {
    fn sinc(self) -> Self {
        ...
    }
}

您可能还需要ToSinc for &mut [T] where T: ToSinc

你可能会说,“啊 - 其中一个是有价值的,另一个是可变的引用;是不是不一致?”

答案取决于你实际上打算使用什么特性。

sinc - 能力类型

的界面

如果你的界面代表那些你可以运行sinc的类型,因为打算使用这种特征,目标就是编写函数

fn do_stuff<T: ToSinc>(value: T) { ... }

现在请注意界面是按值ToSinc获取self并返回Self:这是一个值到价值的函数。实际上,即使T被实例化为某些可变引用,例如&mut [f64],该函数也无法观察任何突变到底层内存

本质上,这些函数将底层内存视为分配源,并对这些分配中保存的数据进行值转换,就像Box → Box操作是堆内存的按值转换一样。只有调用者能够观察到内存的突变,但即使这样,将其输入视为值类型的实现也将返回一个指针,该指针阻止需要访问该内存中的数据。调用者可以像分配器一样将源数据视为不透明。

依赖于可变性的操作,比如写入缓冲区,可能不应该使用这样的接口。有时为了支持这些情况,构建变异基础和方便的按值访问器是有意义的。 ToString是一个有趣的例子,因为它只是Display的包装。

pub trait ToSinc: Sized {
    fn sinc_in_place(&mut self);
    fn sinc(mut self) -> Self {
        self.sinc_in_place();
        self
    }
}

其中impl主要是实施sinc_in_place,用户倾向于sinc

作为ad-hoc重载的伪造

在这种情况下,人们不关心特征是否实际上可以普遍使用,或者甚至是否一致。 sinc("foo")可能会为我们所关心的所有人唱歌跳舞。

因此,虽然需要特质,但应尽可能将其定义为弱:

pub trait Sincable {
    type Out;
    fn sinc(self) -> Self::Out;
}

然后你的功能更通用:

pub fn sinc<T: Sincable>(val: T) -> T::Out {
    val.sinc()
}

要实现按值函数

impl Sincable for f64 {
    type Out = f64;
    fn sinc(self) -> f64 {
        0.4324
    }
}

和by-mut-reference只是

impl<'a, T> Sincable for &'a mut [T]
    where T: Sincable<Out=T> + Copy
{
    type Out = ();
    fn sinc(self) {
        for i in self {
            *i = sinc(*i);
        }
    }
}

因为()是默认的空类型。这就像ad-hoc重载一样。

Playpen example of emulated ad-hoc overloading.