Why is it forbidden to add methods to an existing type?

2,289 views
Skip to first unread message

Zhaoxun Yan

unread,
Mar 17, 2022, 10:16:46 PM3/17/22
to golang-nuts
Hi everyone!
I just came across this taboo in golang - new methods cannot be added to an existing type:

package main
import "fmt"

func (s string) print(){
    fmt.Println(s)
}

func main() {
   "hello, world\n".print()
}
------Error------------
./main.go:5: cannot define new methods on non-local type string

So I try to go around it by declaring an inheritance type. But it will thus be forbidden from functions already defined for the original type:

package main

import(
    "fmt"
    "strconv"
    )

type mystr string

func (s mystr) print(){
    fmt.Println(s)
}

func main() {
   var a mystr ="hello, world\n"
   a.print()
   
   fmt.Println(strconv.ParseInt("78",10, 64))
   
   var x mystr ="452"
   n, _ := strconv.ParseInt(x, 10, 64)
}

------------Error--------------------------
./main.go:21: cannot use x (type mystr) as type string in argument to strconv.ParseInt

What made things worse is it is universal, including any type in open-source packages from github, to which I cannot add in a new method in main.

Is there a way to add methods to an existing type while maintaining all the functionalities it already has?

Thanks,
  Zhaoxun

Ian Lance Taylor

unread,
Mar 18, 2022, 12:26:51 AM3/18/22
to Zhaoxun Yan, golang-nuts
On Thu, Mar 17, 2022 at 7:17 PM Zhaoxun Yan <yan.z...@gmail.com> wrote:
>
> I just came across this taboo in golang - new methods cannot be added to an existing type:

Yes. If we didn't have this prohibition, then the set of interfaces
satisfied by a type would depend on which package was using the type.

See the discussion at https://go.dev/issue/21401.

Ian

Henry

unread,
Mar 18, 2022, 1:46:34 AM3/18/22
to golang-nuts
My own preference is to have a small number of methods and put the general functionalities into functions. By putting the general functionalities into functions, you allow code reuse. In object-oriented programming, you normally attach as many functionalities as possible to their corresponding types and achieve code reuse via inheritance. Since Go does not have inheritance, you can achieve a similar effect with standalone functions. 

Sam Hughes

unread,
Mar 25, 2022, 6:41:15 PM3/25/22
to golang-nuts

My workaround like is something like `type <Purpose>String struct{string}. It can be reasonably treated as a string for most cases in which as string is needed, and it lets you convert back conveniently from any scope in which it's reasonable for your program to know the difference.

Brian Candler

unread,
Mar 28, 2022, 10:40:31 AM3/28/22
to golang-nuts
This works:

        n, _ := strconv.ParseInt(string(x), 10, 64)

Go intentionally does no automatic type conversion, not even this:

        var a int32 = 1234
        var b int64 = a    // error: use int64(a) instead

It would be inconsistent if passing "mystr" to a function which accepts "string" were performed automatically.

Zhaoxun Yan

unread,
Apr 1, 2022, 1:48:04 AM4/1/22
to golang-nuts
Hi Sam! Your solution does not seem to work:

package main

import(
    "fmt"
    "strconv"
    )

type <Purpose> String struct{string}

func (s String) print(){
    fmt.Println(s)
}

func main() {
   var a String ="hello, world\n"

   a.print()
   
   fmt.Println(strconv.ParseInt("78",10, 64))
   
   var x String ="452"

   n, _ := strconv.ParseInt(x, 10, 64)
}

Brian Candler

unread,
Apr 1, 2022, 5:47:32 AM4/1/22
to golang-nuts
That wasn't literal code with anglebrackets - you're supposed to fill that in yourself.  I think he meant something like:

type fooString struct{ string }


What this is doing is *embedding* a string value into a struct; if you have not come across type embedding before then Google for the details.

You still cannot use one of these types transparently as a string, but you can use
    x.string
instead of
    string(x)
to extract the value.

Sam Hughes

unread,
Apr 1, 2022, 12:40:42 PM4/1/22
to golang-nuts

Thanks for clarifying that, @Brian. Yeah. It took a bit to warm up to that approach, but the github.com/jackc/pgtypes convinced me that the results were worth it. The benefits:  A, package interpretation methods with a piece of data, B, lazily valuate data when needed, and C, gain introspective capability specific to the type and not using reflect

Brian Candler

unread,
Apr 2, 2022, 5:37:36 AM4/2/22
to golang-nuts
Correct repository path is: https://github.com/jackc/pgtype

Interesting.  These structs generally contain a Status value as well:

type Int8 struct {
   Int int64
   Status Status
}

where pgtype.go has:

type Status byte

const (
   Undefined Status = iota
   Null
   Present
)

They also have Set() and Get() methods, although these are not compiled-time type safe as they accept and return interface{}

Sam Hughes

unread,
Apr 5, 2022, 2:29:09 AM4/5/22
to golang-nuts
Uh, you should check the code: github.com/jackc/pgtype/blob/master/float8.go If you know what you have and what you're dealing with, and if you actually check the error result, you do get safety. If you don't know what you've got, or if you're iterating through the values, then you end up hitting the sad, sad, reflective path...for now.

Brian Candler

unread,
Apr 5, 2022, 6:13:04 AM4/5/22
to golang-nuts
I did look at the code briefly.  The main thing I noticed was that the return type of float8.Get() was an interface{} - so it's up to the caller to check at runtime whether the value is nil, a float64, or a Status.

So you're right, it's not that they are compile-time unsafe, but rather that you have to write type matching code at each use site. Within a given switch branch, it's safe.

Sam Hughes

unread,
Apr 5, 2022, 8:10:23 PM4/5/22
to golang-nuts
I get what you're saying, and for what it's worth, you're absolutely correct. Something like above is a set of tradeoffs, and I guess all I can assert is that it works for me. I do think it's a good design, given Go's lack of a None<T> type to enforce well-defined behavior on a nil pointer dereference, but it's an example of a well-designed API from the days before generics, which safely respects nil/null values in the appropriate context.

For most of my use of the model, the types offered by the pgtype pacakge are A. total overkill, and B. just an intermediate type to hold a value between two boundaries that do understand null values, such as bridging an RPC frontend and a SQL backend.

If anything, it proves that Generics do simplify codebases, and that well-defined, graceful null/nil handling is worth the effort.

Brian Candler

unread,
Apr 6, 2022, 5:01:32 AM4/6/22
to golang-nuts
I agree, and there's a clear benefit that it's harder to dereference a nil interface than a nil pointer, when that interface has no methods.  The problem is that statically it doesn't tell you anything about the possible contained type.

In comparison, a pointer tells you exactly the type contained, but it must be exactly one type, and there's always a risk of accidentally deferencing a nil pointer. 

If one day, generics type-constraint interfaces could be used as regular interface types, that would be powerful: you could have a variable of type "interface { float64 | Status }" and know for sure that the value *must* be either nil, float64 or Status. Furthermore, you wouldn't be able to use the contained value without an explicit type assertion.

Sam Hughes

unread,
Apr 7, 2022, 3:53:57 AM4/7/22
to golang-nuts
Like, I get that the extra type-pointer for a declared struct of float64+bool isn't that big of a deal, I do wish we could express tuple types naturally. What you suggest is probably the closest we get for now. For `if v, ok := value.(bool); ok && v`, the value is tuly 0. For `if v, ok := value.(bool); ok && !v`, the value is truly nil. For `if v, ok := value.(float64); ok`, the value can be known to be properly and truly the value, even if `0.0`. This would extend to any other type I can think of, excepting, naturally, bools.

Meanwhile, did you see the half-serious sigma-type proposal? I'm pretty sure that would effectively resolve this question, as little hope as I have for it to be accepted.

Reply all
Reply to author
Forward
0 new messages