从重载运算符中返回所有权,如果无法使用Copy特性。

huangapple go评论89阅读模式
英文:

Return ownership from an overloaded operator if Copy trait is unavailable

问题

在包含String的结构体中(从而阻止了Copy特性的实现),如果我使用重载的运算符与该结构体一起使用,我将无法再次使用该结构体变量(这是预期的)。但是,我无法弄清楚如何实现自己的Copy特性或以其他方式解决这个问题。我在运算符中没有修改字符串。

如何从运算符中返回结构体的所有权,以便可以再次使用该结构体?

最小示例:

如果我尝试编译以下代码:

use std::ops;

struct Value {
    val: i32,
    name: String
}

impl std::fmt::Display for Value {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(f, "{}({})", self.name, self.val)
    }
}

impl ops::Add<Value> for Value {
    type Output = Value;

    fn add(self, _rhs: Value) -> Value {
        Value { val: self.val + _rhs.val, 
                name: format!("({}+{})", self.name, _rhs.name) }
    }
}

fn main() {
    let a = Value {val: 5, name: "a".to_string()};
    let b = Value {val: 6, name: "b".to_string()};
    
    let mut c = a + b;
    
    c = c + a;
    
    println!("{}", c);
}

我会得到以下错误:

error[E0382]: use of moved value: `a`
  --> src/main.rs:29:13
   |
24 |     let a = Value {val: 5, name: "a".to_string()};
   |         - move occurs because `a` has type `Value`, which does not implement the `Copy` trait
...
27 |     let mut c = a + b;
   |                 ----- `a` moved due to usage in operator
28 |     
29 |     c = c + a;
   |             ^ value used here after move
   |
note: calling this operator moves the left-hand side
英文:

With a struct that contains a String (thus preventing implementation of Copy trait), if I use the struct with an overloaded operator, I am unable to use the struct variable again (which is expected). However, I am unable to figure out how to implement my own Copy trait or work around this in some other way. I am not modifying the string in the operator at all.

How do I return the ownership of a struct from an operator such that the struct can be used again?

Minimal Case:

If I try to compile the following code:

use std::ops;

struct Value {
    val: i32,
    name: String
}

impl std::fmt::Display for Value {
    fn fmt(&amp;self, f: &amp;mut std::fmt::Formatter) -&gt; std::fmt::Result {
        write!(f, &quot;{}({})&quot;, self.name, self.val)
    }
}

impl ops::Add&lt;Value&gt; for Value {
    type Output = Value;

    fn add(self, _rhs: Value) -&gt; Value {
		Value { val: self.val + _rhs.val, 
		        name: format!(&quot;({}+{})&quot;, self.name, _rhs.name) }
    }
}

fn main() {
    let a = Value {val: 5, name: &quot;a&quot;.to_string()};
    let b = Value {val: 6, name: &quot;b&quot;.to_string()};
    
    let mut c = a + b;
    
    c = c + a;
    
    println!(&quot;{}&quot;, c);
}

I get the following error:

error[E0382]: use of moved value: `a`
  --&gt; src/main.rs:29:13
   |
24 |     let a = Value {val: 5, name: &quot;a&quot;.to_string()};
   |         - move occurs because `a` has type `Value`, which does not implement the `Copy` trait
...
27 |     let mut c = a + b;
   |                 ----- `a` moved due to usage in operator
28 |     
29 |     c = c + a;
   |             ^ value used here after move
   |
note: calling this operator moves the left-hand side

答案1

得分: 1

Add 可以用于对类型的引用进行实现。所以,例如,你可以编写如下代码:

impl ops::Add<&Value> for &Value {
    type Output = Value;

    fn add(self, _rhs: &Value) -> Value {
        Value { val: self.val + _rhs.val, 
                name: format!("({}+{})", self.name, _rhs.name) }
    }
}

并且像这样使用它:

fn main() {
    let a = Value {val: 5, name: "a".to_string()};
    let b = Value {val: 6, name: "b".to_string()};
    
    let c = &a + &b;
    println!("{}", c);
    let c = &c + &a;
    println!("{}", c);
}

你也可以实现 Add<&Value> for Value(或者反过来),这将会消耗其中一个操作数。例如,在标准库中有 impl Add<&str> for String。你必须选择你想要的语义(你是想拥有两个操作数中的哪一个、只有一个,还是都不要)。

然而,我建议仅在只有一个逻辑实现时才覆盖运算符。作为一条经验法则,尽量只在只有一个逻辑实现时“覆盖”运算符,这样当有人尝试使用 Value 时就不会出现意外情况。

英文:

Add can be implemented for references to types too. So for example you could write something like this:

impl ops::Add&lt;&amp;Value&gt; for &amp;Value {
    type Output = Value;

    fn add(self, _rhs: &amp;Value) -&gt; Value {
        Value { val: self.val + _rhs.val, 
                name: format!(&quot;({}+{})&quot;, self.name, _rhs.name) }
    }
}

and use it like this:

fn main() {
    let a = Value {val: 5, name: &quot;a&quot;.to_string()};
    let b = Value {val: 6, name: &quot;b&quot;.to_string()};
    
    let c = &amp;a + &amp;b;
    println!(&quot;{}&quot;, c);
    let c = &amp;c + &amp;a;
    println!(&quot;{}&quot;, c);
}

You can also implement Add&lt;&amp;Value&gt; for Value (or vice versa) which will consume one operand. For example in standard library there is impl Add<&str> for String. You have to choose which semantics you want (do you want to take ownership of both operands, only one, or neither).

However I would suggest to just use normal methods as it won't result in surprises when someone tries to use Value. As a rule of thumb try to "override" operators only when there is only one logical implementation.

huangapple
  • 本文由 发表于 2023年7月31日 20:16:53
  • 转载请务必保留本文链接:https://go.coder-hub.com/76803536.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定