Одной из частых операций может оказаться объединения(concatenation) строк, есть много библиотек для решения задач, мы рассмотрим несколько самых распространенных примеров.
В стандартной библиотеке fmt есть метод Sprintf(format string, a ...interface{}) string в качестве первого аргумента он принимает формат, в нашем случае это просто %s %s для двух строк. Также есть метод strings.Join, тут все просто slice строк и разделитель. Еще можно использовать тип bytes.Buffer с его методами WriteString. Ну и не забываем про string+" "+ string".
Запустим для всего этого benchmark для 100 строк, результат для него будет такой
BenchmarkSfmt-4 100000 12207 ns/op 4705 B/op 102 allocs/op BenchmarkBuff-4 500000 2516 ns/op 6928 B/op 7 allocs/op BenchmarkJoin-4 1000000 1041 ns/op 1792 B/op 1 allocs/op BenchmarkConc100-4 1000000 1367 ns/op 1792 B/op 1 allocs/op
Сразу видно что fmt.Sprintf меньше всего для этого подходит, он скорее предназначен когда нам надо больше чем просто объединить строки, с его возможностями можете ознакомится в документации. У него есть также аналоги как например log.Printf,fmt.Errorf и тп. Buffer почти в двое проигрывает специализированным решениям, там прямая зависимость от строк , к примеру пустой срез будет иметь такой результат
BenchmarkBuff-4 3000000 405 ns/op 0 B/op 0 allocs/op BenchmarkJoin-4 2000000 787 ns/op 0 B/op 0 allocs/op BenchmarkConc100-4 10000000 134 ns/op 0 B/op 0 allocs/op
а при длине строки в 322 символа
BenchmarkBuff-4 100000 15442 ns/op 125088 B/op 9 allocs/op BenchmarkJoin-4 300000 4451 ns/op 32768 B/op 1 allocs/op BenchmarkConc100-4 300000 4497 ns/op 32768 B/op 1 allocs/op
Если вам нужно объединить строки то используйте + или strings.Join. Не используете сложение строк в foreach иначе получите результат сравнимый с Sprintf
BenchmarkFor100-4 100000 14175 ns/op 84848 B/op 99 allocs/op BenchmarkConc100-4 1000000 1328 ns/op 1792 B/op 1 allocs/op
А что если нам нужно сложить не только строки? Для этого надо будет преобразовать типы в строку и точна также сложить, есть пакет strconv мы будем использовать методы FormatFloat и Itoa
BenchmarkIfmt-4 100000 12045 ns/op 4289 B/op 102 allocs/op BenchmarkFfmt-4 100000 23098 ns/op 4289 B/op 102 allocs/op BenchmarkJoinInt-4 200000 5824 ns/op 5248 B/op 101 allocs/op BenchmarkJoinFloat-4 100000 19269 ns/op 8448 B/op 201 allocs/op BenchmarkConc100Int-4 200000 6017 ns/op 5248 B/op 101 allocs/op BenchmarkConc100Float-4 100000 18470 ns/op 8448 B/op 201 allocs/op
Тут думаю понятно что выигрыша мы на типе float вообще не получаем, но учитывайте что используются аргументы 'g', -1, 64.
Выводы
Надеюсь вы перестанете везде использовать методы fmt.Sprintf("err: %s, message: %s", err,message)" да возможно они более читабельные, или если вас не устраивает использование strings.Join([]string{"err:",err.Error(),"message:",message}, "") можно сделать в проекте func join(in ...string) string. По поводу вывода строковой информации о других типов тут уже все зависит от задачи, если будет float или смешанный вариант то я наверное предпочту читабельный код. Если вы для логов делаете вывод то возможно стоит обратить внимание на пакет go.uber.org/zap там к примеру есть удобные методы SugaredLogger.Warnw которые позволяют использовать основные типы, и как бонус искать потом по этим полям если вы используете json. Данная статья не последняя инстанция, если у вас есть свои выводы подкрепленные Benchmark, оставляйте ссылку на gist, уверен будет интересно всем. Все основные результаты можно посмотреть на github.