Help with Stringer interface

 //convert.go
    package convert
    
    type Liters float64
    type Milliliters float64
    
    func (l *Liters) ToMilliliters() Milliliters{
        return Milliliters(*l * 1000)
    }
    
    func (m *Milliliters) ToLiters() Liters{
        return Liters(*m / 1000)
    }
    
    func (m *Milliliters) String() string {
        return fmt.Sprintf("%0.2f ml", *m)
    }
    
    func (l *Liters) String() string {
        return fmt.Sprintf("%0.2f l", *l)
    }
    
    //main.go
    package main
    
    import (
        "fmt"
        "convert"
    )
    
    func main() {
        vol := Liters(2)
        fmt.Println(vol.ToMilliliters())  //output 2000, i want 2000.00 ml
    }

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.

Hi @void5253,

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 type T 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.

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.

See also: go - "cannot take the address of" and "cannot call pointer method on" - Stack Overflow

Edited to add:

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.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.