Why does the following Go program need the function's name to be String() and not anything else?

agolangf · 2017-09-12 02:00:09 · 606 次点击    
这是一个分享于 2017-09-12 02:00:09 的资源,其中的信息可能已经有所发展或是发生改变。

I'm following a book which shows the following example.

package main

import (
    "fmt"
)

const (
    KB = 1024
    MB = 1048576          //KB * 1024
    GB = 1073741824       //MB * 1024
    TB = 1099511627776    //GB * 1024
    PB = 1125899906842624 //TB * 1024
)

type ByteSize float64

func (b ByteSize) String() string {
    switch {
    case b >= PB:
        return "Very Big"
    case b >= TB:
        return fmt.Sprintf("%.2fTB", b/TB)
    case b >= GB:
        return fmt.Sprintf("%.2fGB", b/GB)
    case b >= MB:
        return fmt.Sprintf("%.2fMB", b/MB)
    case b >= KB:
        return fmt.Sprintf("%.2fKB", b/KB)
    }
    return fmt.Sprintf("%dB", b)
}

func main() {
    fmt.Println(ByteSize(2048))
    fmt.Println(ByteSize(3292528.64))
}

When I run this program it gives me the following output (in human readable data size units).

2.00KB
3.14MB

But when I change the name of the function called String() to anything else, or if I lower-case the S in String, it gives me the following output.

2048
3.29252864e+06

What is the reason behind that? Is there some String() function attached to some interface and our ByteSize type satisfies that interface? I mean what the hell?


评论:

rauyran:

Yes. Take a look at https://godoc.org/fmt#Stringer

nevyn:

Probably also worth linking to: https://gobyexample.com/interfaces

avidal:

That's exactly correct. There's an interface fmt.Stringer which, if implemented on your type, is used to get the string representation when calling fmt.Print and family.

deusmetallum:

It has to be called String() because fmt.Println is simply checking to see if that function exists. It can't check for all possible itterations!

Further more, anything beginning with a capitol letter is an "exported" function, meaning it can be run from any other package when imported. If you make the S lowercase, then the function can't be run in another module.

deusmetallum:

Also, if you change the name to string() or whatever(), you'd have to change your fmt.Println lines to look like this:

fmt.Println(ByteSize(2048).string())
magpiecub:
const (
    KB = 1024
    MB = 1048576          //KB * 1024
    GB = 1073741824       //MB * 1024
    TB = 1099511627776    //GB * 1024
    PB = 1125899906842624 //TB * 1024
)

If MB is KB * 1024 as the comment suggests, why don't they just write MB = KB * 1024?

Indu_Pillai:

I changed that myself. Just cultivating the habit of reducing unwanted computations. :)

magpiecub:

Computers are fast and cheap. Humans are slow and expensive. You shouldn't be sacrificing readability to prevent 4 multiplication operations.

The operations don't even happen at run time - they happen at compile time:

% cat > main.go
package main
import "fmt"
const x = 2
func main() {
        fmt.Println(x)
}
% go build -o x2 main.go
% cat > main.go
package main
import "fmt"
const x = 1 + 1
func main() {
        fmt.Println(x)
}
% go build -o x11 main.go
% sha1sum x2 x11
515ccebaedfdedd5f3adba0d600b1d02873670b6  x2
515ccebaedfdedd5f3adba0d600b1d02873670b6  x11
Indu_Pillai:

so it means the value of constants is calculated at the compile time?

zevdg:

In recent versions of Go, yes. it's one of the many optimizations enabled by the recent SSA compiler work and is specifically called out in this video from Gophercon 2017.

That said, even if it weren't, you're talking about obfuscating your source code to gain fractions of nanoseconds at runtime. That's NEVER the right trade off unless you've profiled your code and found this particular path to be worth optimizing. Donald Knuth (who probably knows more about optimizing code than either of us ever will) famously said

The real problem is that programmers have spent far too much time worrying about efficiency in the wrong places and at the wrong times; premature optimization is the root of all evil (or at least most of it) in programming.

And he said it again in this way

Programmers waste enormous amounts of time thinking about, or worrying about, the speed of noncritical parts of their programs, and these attempts at efficiency actually have a strong negative impact when debugging and maintenance are considered. We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil. Yet we should not pass up our opportunities in that critical 3%.

Please don't give into this evil. Always start by writing readable code and then if/when you need better performance, profile your code and only optimize the bottlenecks. Trust me, the people who you ask for help from (like us) will really appreciate it and be even be more likely to help if they can understand your code at a glance.

rpk788:

All of the other answers about implementing the Stringer interface are great. It is also worth mentioning that capitalization plays a big role in Go as well. so swapping the method name from String() to string() is more significant than you might think. See here:

https://tour.golang.org/basics/3


入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

606 次点击  
加入收藏 微博
0 回复
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传