Repository: gonum/gonum (language: go)

Issue:
unit: Inconsistency in Uniter interface
https://github.com/gonum/gonum/blob/8cb5c961a936de0edcb9e0a8fb825d79c0c725e3/unit/unittype.go#L287

```go
func Cube(u unit.Uniter) *unit.Unit {
	u1 := u.Unit()  // make copy of value if u is not *unit.Unit
	return u1.Mul(u).Mul(u)
}

func test() {
	a := unit.Area(2)
	fmt.Println(Cube(a))
	fmt.Println(Cube(a.Unit())) // if argument to Cube is Unit struct, result is incorrect
}
```
output is
```
8 m^6
16 m^8  // here it compute u^4 because of side effect 
```

Hi, i think there is Inconsistency, for all types Unit() call make copy of value,  only for Unit struct it doesn't.
Coping value make sense, else i think there is need for Copy method. 
Also in doc comment for Unit, may be mentioned that this struct must not be copied, because it contain map.

Regards.


Discussion / hints:

Fix patch:
diff --git a/unit/unittype.go b/unit/unittype.go
index c365ea7a1..60c5f6d1c 100644
--- a/unit/unittype.go
+++ b/unit/unittype.go
@@ -245,7 +245,7 @@ func (u unitPrinters) Swap(i, j int) {
 // Unit represents a dimensional value. The dimensions will typically be in SI
 // units, but can also include dimensions created with NewDimension. The Unit type
 // is most useful for ensuring dimensional consistency when manipulating types
-// with different units, for example, by multiplying  an acceleration with a
+// with different units, for example, by multiplying an acceleration with a
 // mass to get a force. See the package documentation for further explanation.
 type Unit struct {
 	dimensions Dimensions
@@ -283,11 +283,21 @@ func (u *Unit) Add(uniter Uniter) *Unit {
 	return u
 }
 
-// Unit implements the Uniter interface
+// Unit implements the Uniter interface, returning the receiver. If a
+// copy of the receiver is required, use the Copy method.
 func (u *Unit) Unit() *Unit {
 	return u
 }
 
+// Copy returns a copy of the Unit that can be mutated without the change
+// being reflected in the original value.
+func (u *Unit) Copy() *Unit {
+	return &Unit{
+		dimensions: u.dimensions.clone(),
+		value:      u.value,
+	}
+}
+
 // Mul multiply the receiver by the input changing the dimensions
 // of the receiver as appropriate. The input is not changed.
 func (u *Unit) Mul(uniter Uniter) *Unit {


Test patch:
diff --git a/unit/unit_test.go b/unit/unit_test.go
index c87b02305..fb9dfd0f6 100644
--- a/unit/unit_test.go
+++ b/unit/unit_test.go
@@ -10,7 +10,7 @@ import (
 	"testing"
 )
 
-// ether is a non-existant unit used for testing.
+// ether is a non-existent unit used for testing.
 type ether float64
 
 func (e ether) Unit() *Unit {
@@ -110,6 +110,21 @@ func TestFormat(t *testing.T) {
 	}
 }
 
+func TestCopy(t *testing.T) {
+	t.Parallel()
+	for _, ts := range formatTests {
+		u := ts.unit.Unit()
+		if len(u.Dimensions()) == 0 {
+			continue
+		}
+		c := u.Copy()
+		c.Mul(c)
+		if DimensionsMatch(u, c) {
+			t.Errorf("dimensions match after mutating copy of %v: %v == %v", u, u.Dimensions(), c.Dimensions())
+		}
+	}
+}
+
 func TestGoStringFormat(t *testing.T) {
 	t.Parallel()
 	expect1 := `&unit.Unit{dimensions:unit.Dimensions{4:2, 7:-1}, value:6.62606957e-34}`