PS: I got correct output when method had value receivers, i.e when I replaced thisfunc (l *Liters) String() string by this func (l Liters) String() string.
Only a pointer type has access to methods with pointer receivers. See Method Sets in the language spec for details on this, especially:
The method set of a defined typeT consists of all methods declared with receiver type T.
The method set of a pointer to a defined type T (where T is neither a pointer nor an interface) is the set of all methods declared with receiver *T or T
vol is not a pointer type, hence its method set does not include String() methods that are defined on pointer receivers. Thus, the standard string representation of float64(2) applies.
A String() method that is available for both Milliliters and *Milliliters therefore needs a value receiver.
Hi! Thanks for the reply. Didn’t really get this point.
The method set of a defined typeT consists of all methods declared with receiver type T.
In my example, Liters and Milliliters are defined types. But ain’t I still able to call ToMilliliters() that has pointer receiver on vol which is Liters type without use of pointer?
You have a good point here. This playground code demonstrates that String() works fine on a variable of type Milliliters but not on a return value of type Milliliters.
mvol := vol.ToMilliliters()
fmt.Println(mvol.String()) // 2000.00 ml
fmt.Println(vol.ToMilliliters().String()) // error: cannot call pointer method String on Milliliters
So my initial analysis was too quick.
The problem is that the variable is addressable but a return value isn‘t.
The . operator is smart and works on both value types and pointer types. For pointers, it automatically dereferences the pointer.
This does not work with a non-addressable return value.
This is what happens when a language contains convenience features. These features can help but also can introduce unexpected behavior like this one.
Your particular code has a quite straightforward solution: use value receivers. Your methods do not modify the receiver in any way; hence, a value receiver is perfectly fine.