150

The strings.Join function takes slices of strings only:

s := []string{"foo", "bar", "baz"}
fmt.Println(strings.Join(s, ", "))

But it would be nice to be able to pass arbitrary objects which implement a ToString() function.

type ToStringConverter interface {
    ToString() string
}

Is there something like this in Go or do I have to decorate existing types like int with ToString methods and write a wrapper around strings.Join?

func Join(a []ToStringConverter, sep string) string
deamon
  • 89,107
  • 111
  • 320
  • 448
  • 10
    Note that such an interface already exists : http://golang.org/pkg/fmt/#Stringer – Denys Séguret Nov 06 '12 at 09:20
  • 1
    See [Related question about a `Join` function that takes `Stringer` objects](http://stackoverflow.com/questions/13249843/duck-typing-in-go) – deamon Nov 06 '12 at 11:18
  • @daemon I don't see the need for this duplicate. The present question was clear enough in my opinion and the fact that there was no real (or complete) answer doesn't mean you have to ask again. – Denys Séguret Nov 06 '12 at 11:26

7 Answers7

257

Attach a String() string method to any named type and enjoy any custom "ToString" functionality:

package main

import "fmt"

type bin int

func (b bin) String() string {
        return fmt.Sprintf("%b", b)
}

func main() {
        fmt.Println(bin(42))
}

Playground: http://play.golang.org/p/Azql7_pDAA


Output

101010
zzzz
  • 87,403
  • 16
  • 175
  • 139
  • 1
    You're right, although the answer doesn't imply that the conversion is the only option. The point is in the String() method attached to a type. Anywhere fmt.* finds that method attached it uses it to obtain the string representation of such type. – zzzz Nov 06 '12 at 09:56
  • 4
    adding `bin(42).String()` as another example will be better for the answer. – Thellimist Aug 22 '15 at 22:09
  • NOTE: functon `Error() string` has higher priority than `String() string` – Geln Yang Jul 05 '18 at 01:55
  • 2
    In other words, implement the `Stringer` interface: https://golang.org/pkg/fmt/#Stringer – tothemario Oct 03 '19 at 20:23
  • This does not work if you use generics. – honestduane Jul 03 '23 at 18:49
29

When you have own struct, you could have own convert-to-string function.

package main

import (
    "fmt"
)

type Color struct {
    Red   int `json:"red"`
    Green int `json:"green"`
    Blue  int `json:"blue"`
}

func (c Color) String() string {
    return fmt.Sprintf("[%d, %d, %d]", c.Red, c.Green, c.Blue)
}

func main() {
    c := Color{Red: 123, Green: 11, Blue: 34}
    fmt.Println(c) //[123, 11, 34]
}
Rio
  • 851
  • 10
  • 5
7

Another example with a struct :

package types

import "fmt"

type MyType struct {
    Id   int    
    Name string
}

func (t MyType) String() string {
    return fmt.Sprintf(
    "[%d : %s]",
    t.Id, 
    t.Name)
}

Be careful when using it,
concatenation with '+' doesn't compile :

t := types.MyType{ 12, "Blabla" }

fmt.Println(t) // OK
fmt.Printf("t : %s \n", t) // OK
//fmt.Println("t : " + t) // Compiler error !!!
fmt.Println("t : " + t.String()) // OK if calling the function explicitly
lgu
  • 2,342
  • 21
  • 29
1
  • This works well
   package main
    
    import "fmt"
    
    
    type Person struct {
        fname, sname string 
        address string 
    }
    
    
    func (p *Person) String() string {
        s:=  fmt.Sprintf("\n %s %s  lives at %s \n", p.fname, p.sname, p.address)
        return s
    }
    
    
    func main(){
        alex := &Person{"Alex", "Smith", "33 McArthur Bvd"}
        fmt.Println(alex)
    
    }

Output:

Alex Smith lives at 33 McArthur Bvd

bruceg
  • 2,433
  • 1
  • 22
  • 29
Go_Runner
  • 11
  • 1
1

If you have a fixed set of possible types for elements that could be converted, then you can define conversion functions for each, and a general conversion function that uses reflection to test the actual type of an element and call the relevant function for that element, eg:

func ToStringint(x int) string { 
  return strconv.Itoa(x)
}

func ToStringlong(x int64) string { 
  return strconv.FormatInt(x,10)
} 

func ToStringdouble(x float64) string { 
  return fmt.Sprintf("%f", x)
} 

func ToStringboolean(x bool) string {
  if x { 
    return "true"
  } 
  return "false"
} 

func ToStringOclAny(x interface{}) string { 
  if reflect.TypeOf(x) == TYPEint { 
    return strconv.Itoa(x.(int)) 
  } 

  if reflect.TypeOf(x) == TYPEdouble { 
      return fmt.Sprintf("%f", x.(float64))
  } 

  if reflect.TypeOf(x) == TYPElong { 
    return strconv.FormatInt(x.(int64),10)
  } 

  if reflect.TypeOf(x) == TYPEString { 
     return x.(string)
  } 

  if reflect.TypeOf(x) == TYPEboolean { 
     return ToStringboolean(x.(bool))
  } 

  if reflect.TypeOf(x) == TYPESequence { 
     return ToStringSequence(x.(*list.List))
  } 

  if reflect.TypeOf(x) == TYPEMap { 
     return ToStringMap(x.(map[interface{}]interface{}))
  }

  return ""
}

func ToStringSequence(col *list.List) string { 
  res := "Sequence{"
  for e := col.Front(); e != nil; e = e.Next() { 
     res = res + ToStringOclAny(e.Value)
     if e.Next() != nil { 
       res = res + ", "
    } 
  } 
  return res + "}" 
}

func ToStringSet(col *list.List) string { 
   res := "Set{"
   for e := col.Front(); e != nil; e = e.Next() { 
      res = res + ToStringOclAny(e.Value)
      if e.Next() != nil { 
         res = res + ", "
      }
   } 
   return res + "}" 
 }

func ToStringMap(m map[interface{}]interface{}) string { 
  res := "Map{"
  for i, v := range m { 
    res = res + ToStringOclAny(i) + " |-> " + ToStringOclAny(v) + " "
  }
  return res + "}"
} 
Kevin Lano
  • 81
  • 2
-2

Here is a simple way to handle this:

package main

import (
    "fat"
    "strconv"
)

type Person struct {
    firstName, lastName string
    age int
}

func (p Person) GetFullName() string {
    return p.firstName + " " + p.lastName
}

func (p Person) GetAge() int {
    return p.age
}

func (p Person) GetAgeAsString() string {
    return strconv.Itoa(p.age)
}

func main() {
    p := Person {"John", "Doe", 21}
    fmt.Println(p.GetFullName())
    fmt.Println(p.GetAgeAsString())
}

Output:

"John Doe"
"21"
Tomerikoo
  • 18,379
  • 16
  • 47
  • 61
-8

I prefer something like the following:

type StringRef []byte

func (s StringRef) String() string {
        return string(s[:])
}

…

// rather silly example, but ...
fmt.Printf("foo=%s\n",StringRef("bar"))
JSS
  • 9
  • 1
  • 6
    You don't need the useless `:`, (i.e. just `string(s)`). Also, if `b` is `[]byte` then `string(b)` much simpler and then your `StringRef(b).String()`. Finally, your example is pointless as `%s` (unlike `%v`) already prints `[]byte` arguments as strings without without the potential copy that `string(b)` usually does. – Dave C Oct 21 '15 at 16:45