你能在不使用显式引用或所有权移动的情况下,在结构体上实现数学运算吗?

7

我不知道如何在结构体上实现干净的数学运算,而不需要复制每个结构体值。

如果你想在结构体上执行数学运算,可以编写以下代码:

use std::ops::*;

struct Num {
    i: i32,
}

impl Add for Num {
    type Output = Num;
    fn add(self, other: Num) -> Num {
        Num {
            i: self.i + other.i,
        }
    }
}

(This is a simplified example. An actual example might be doing Vector maths)
这让我们可以编写漂亮的a + (b / (c * d))代码。
由于借用语义,上述代码与a + b + a一样容易出错。一旦使用了a,它就不能再次使用,因为所有权已经转移到了相关函数(即add)。
解决这个问题的简单方法是为结构体实现Copy
#[derive(Copy)]
struct Num {
    i: i32,
}

这意味着当传递给addNum被自动克隆以便能够干净地丢弃它们。
但这似乎是低效的!我们不需要这些结构体在各个地方都被复制:它是只读的,我们只需要引用它来创建我们返回的新结构。
这让我想到我们应该基于引用来实现数学运算:
impl<'a> Add for &'a Num {
    type Output = Num;
    fn add(&'a self, other: &'a Num) -> Num {
        Num {
            i: self.i + other.i,
        }
    }
}

现在我们有了数学运算,我们不需要一遍又一遍地克隆数据,但现在我们的数学看起来很丑陋!a + (b / (c * d))现在必须是&a + &(&b / &(&c * &d))。即使你有你的值类型引用(例如let a = &Num { /* ... */ }),因为add的返回值仍然是一个Num,所以这并没有帮助。

是否有一种简洁的方法来实现结构体的操作,使得数学运算看起来干净整洁,同时结构体的值也不会被无处不在地复制?

相关文章:

1个回答

4
不行,这些特征是按值消耗的,无法避免。
但这似乎效率低下!我们不需要在各个地方复制这些结构体:它是只读的,我们真正需要的是引用来创建我们返回的新结构。
我不会担心复制一个整数的效率。这就是计算机所做的。实际上,引用可能会更慢,因为引用也基本上是一个整数,必须被复制,然后查找一块内存,同时将所指整数复制到寄存器中。
我显然没有复制一个整数!
一个实际的例子可能是进行向量运算。
那么问题就变成了让用户感到困惑。如果没有查看实现,用户怎么知道a + a是否“轻量级”。如果您作为类型的实现者知道它的复制是轻量级的,则标记为Copy。如果不是,则需要使用引用。

这就是今天的情况。有一些实验性工作,可能会使未来变得更好:

想象一下永远不必再写[...] let z = &u * &(&(&u.square() + &(&A * &u)) + &one);

这个实验是从一个现在已被推迟的RFC中产生的。

有趣的是,这种丑陋的语法被称为索伦之眼


如果你在这里试图注意性能,你会怎么做?编译器是否足够聪明,知道它实际上不需要复制数据?#[inline(always)]会改进任何东西吗? - SCdF
3
“我不会担心复制一个整数的效率。” 我显然不是在复制一个单独的整数! 这只是一个用于演示 Stack Overflow 的简短示例。 - SCdF
我想知道复制的值是否有效地是单个 i32,还是结构体 Num 会更大。也就是说,这种特定的抽象是否是自由的。 - 9000
在Rust ABI中,它将是完全自由的(这被称为一个newtype)。每个外部ABI都有其自己的保证,并且它可能会因编译目标的不同而发生变化。 - Shepmaster
@Shepmaster 不错!我以为有人正在处理这个问题。我想知道您是否也可以使用宏来处理它(您需要标记/包装执行数学运算的每个函数),但我对 Rust 的宏能力不够熟悉。 - SCdF
1
@SCdF 有一个叫做unsauron的项目,但似乎没有维护。 - Shepmaster

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接