In Go is naming the receiver variable 'self' misleading or good practice? [closed]
Asked Answered
H

5

57

I have seen a fair amount of blogs & videos on Go and as far as I recall, none of the authors use 'self' or 'this' for the receiver variable when writing methods. However there seems to be a number of questions on stack overflow that do this, and it got me thinking about if this is misleading to name the variable 'self'?

Reading the Spec for Method Sets does not provide any evidence either way (in my interpretation).

I seem to recall finding somewhere that it was not really a self pointer, can anyone list evidence or provide reasoning either way, and if any problems/traps that might occur from thinking of it as 'self'?

A quick example:

type MyStruct struct {
    Name string
} 

Which method is more appropriate, or both?

func (m *MyStruct) MyMethod() error {
    // do something useful
}

or

func (self *MyStruct) MyMethod() error {
    // do something useful
}
Halonna answered 5/5, 2014 at 21:17 Comment(4)
No self or this.., also take a look at Effective Go (golang.org/doc/effective_go.html) and Code examples of go's pkgs (e.g. golang.org/src/pkg/net/http/server.go)Botheration
Thanks to everyone who answered/commented. Everyone has added info to what I already knew.Halonna
Let's be honest, instead of using this/self, everyone including go src uses the first letter of the receiver type as the name. Does this really improve anything?Paramaribo
There's a very good answer here: softwareengineering.stackexchange.com/a/436311/413348 . I think the importance of keeping this convention is that it helps newcomers ask themselves "why is this different?" and discovering the philosophy of GoJoel
R
53

In addition to what others said (especially PeterSO and dskinner—in his comment to the Peter's answer), note several important things:

You can call a method like a simple function

In Go, you can call any method function not as a method on a receiver but rather as a regular function—simply by qualifying its name with the name of the type it's defined to be a method on and explicitly passing it a receiver argument (obtaining a simple function from a method is called using a method expression).

To demonstrate:

package main

import "fmt"

type Foo int

func (f Foo) Bar() {
    fmt.Printf("My receiver is %v\n", f)
}

func main() {
    a := Foo(46)
    a.Bar()
    b := Foo(51)
    Foo.Bar(b)
}

(Playground link.)

When run, this program prints:

My receiver is 46
My receiver is 51

As you can see, self loses its sacred meaning here because you've just called a method artificially constructing the context for it which has nothing to do with the much cited "calling an object's method is passing a message to that object" concept.

To recap, in Go, a method is just a function semantically bound to a particular type which receives a single extra argument—its receiver—no matter how it's called. Contrary to many other mainstream languages, Go does not hide this fact under the carpet.

A receiver is not necessarily mutable inside a method defined on its type

As demonstrated in my example, I've defined a method, Bar(), on a non-pointer receiver, and if you'll try to assign a value to the receiver that will succeed but won't affect the caller because the receiver—as everything in Go—has been passed by value (so that integer has just been copied).

To be able to mutate the receiver's value in the method, you'd have to define it on an appropriately-typed pointer, like

func (f *Foo) Bar() {
    // here you can mutate the value via *f, like
    *f = 73
}

Again, you can see that using self meaning "me", "my internals" becomes moot here: in my example the method merely received a value which type it knows. You can see this is in contrast with many OO-languages in which an object is a black box usually passed around by reference. In Go, you can define a method on virtually anything (including other methods, which is used by the net/http standard package, by the way) which erodes that "methods are for objects" concept.

Different sets of methods might be applicable to the same value at different times

In Go, methods are a convenient way to group functionality around particular types, and different sets of methods might be applicable to the same value in different points of the program flow. Combined with interfaces and duck-typing they provide, this concept really flourishes. The idea is that in Go, there's an idiom of defining "support" types which perform certain operation on values of some other type.

A good example of this is the standard package sort: for instance, it provides the type IntSlice which allows you to sort a slice of integers—a value of type []int. To do that you type-convert your slice to sort.IntSlice and the value you get as a result has a whole set of methods for sorting your slice while the internal representation of your value has not changed— because sort.IntSlice is defined as type IntSlice []int. In each method of that IntSlice type, it's hard to reconcile the meaning of their receiver value with self—simply because the type solely exists to provide a set of methods for another type; in a philosophical sense, such utility types have no concept of "self" ;-)

Conclusion

So I'd say, keep things simple in your head and do not try to "overload" the clear and simple approach taken by Go with semantics it does not explicitly state it provides.

One more note. My personal perception of Go's idioms as I learned them is that the paramount property of Go is its practicality (as opposed to idealism etc) so if you see some concept which "feels" unnatural try to work out why it's designed that way, and most often you'll discover why so the concept "clicks" in your brain and gets natural. (I must admit that to grok this particular problem with understanding methods in Go, a good working familiarity with C would be of much help.)

Rodarte answered 6/5, 2014 at 12:12 Comment(21)
@miltonb, split the latter idiom into two for easier digesting and for their descriptions to be more detailed. Have fun!Rodarte
A great answer to an important question. This settles the self/this questions I had while providing a deeper understanding of the language mechanisms. I wish that information was described on Effective GoContextual
But python does the same thing, right? foo.bar() is just syntax sugar for Foo.bar(foo). Wouldn't using the standard it sets (always use self) make sense here?Gravure
@Rodarte your answer make me think a lot (btw thanks). I keep wondering, what is the meaning of having receiver in a function if it is, after all, an implicit first argument to the function when it gets invoked? I mean.. What would be the thought or criteria that anyone should follow in order to dictaminate to write a function with receiver or ,instead, a regular function with its first parameter beign a variable of the receiver type?Compliant
@Victor, thanks! Your question is too complex to answer in a few characters (so I'd recommend to post a question to the mailing list with a summary and link to this answer. Still, I'd point at a few directions for further thinking: 1) Types provide sort-of namespaces for their methods. Note that every "regular" ("free-standing", "non-method" etc) function occupies a name in the namespace formed by its package. This might seem like a non-problem but think of two cases: a) sometimes a package is "wide" enough (like, say, "net")…Rodarte
@Victor, …to have short-named functions, and long-named functions are heavy-weight; b) sometimes it's most clean to have a function named using a simple verb, like the Do method of the net/http.Request type. Having a free function named PerformRequest in that type's package would work too, but I think the former is clearer. 1.1) Methods of a type is a natural group of a functions related by their intended purpose. 2) Methods have access to unexported fields and methods of a type. Go is way more "simple-minded" than many "more conventional" OOP languages when it comes to "protection",Rodarte
@Victor, but still, it's sometimes useful to have exported methods access unexported (think if "private") fields of their type. This naturally decouples interface from implementation. 3) In "classic OOP" (I mean systems like Smalltalk, not C++ objects influence each other's state by sending messages to each other; in most OOP languages sending messages is implemented by calling methods. Go definitely leans "outwards" of such extreme—to the domain of algorythmic languages (C and its heritage), deferring message passing to…Rodarte
@Victor,…communicating over channels between goroutines (yeah, a goroutine is an object in the sense of a "classic OOP": it has a state, and a code spinning around that state and communicating with other objects via channels), but still sometimes it's useful to think of types and methods in this modus: types are blueprints for objects, and objects exchange messages by calling each other's methods.Rodarte
@kostix, please let me feedback each point with a quick recap to see if i get the idea 1) Avoid namespace pollution, 1.1) Group Type + Algorithms (it should be by functional affinity as you've mentioned) 2) encapsulation and hide implementation, 3) is like the "OO" way inside golang? (i'm afraid i don't get it maybe), Anyway thanks for beign so explicative... btw: the difference of interface naming convention between java (e.g. interface "Readable") and golang (Would be seen as interface "Sourcer"?) is making think a lot too :S..Compliant
@Compliant 1 to 2—all correct; 3—not quite. What I wanted to convey is that in a classic OO any system is composed of objects (which are built according to the definitions of a set of classes), and the objects affect each other by means of sending messages. In most OOP languages sending messages is implemented by calling methods. And I tried to explain that sometimes it may be useful to think of your types and their methods in these terms. Is it clearer now?Rodarte
totally clear @kostix, again thanks for bearing with me on this!. please allow me to provide further context on this: We shall agree that the "OO" philosophy is not attached to any language as it shall be recognized as mental tool for studying and defining components and their relations on a system, therefore... I'm trying to understand how this philosophy fits into golang.... (i know that this is a process that requires time...) in order to enforce the right code design decision... (somethign that is the topic of this life...)Compliant
i put more thoughts regarding this at play.golang.org/p/qfEE_-KD0bu, please consider to take a look at it when you have any time for spare as teacher :P THanks!!Compliant
The adjective "mutable" is misleading. The receiver is still mutable. It just operates on a mutable copy of the original value that got passed to the method.Paramaribo
This is typical double standard. If this or self should be avoided, then everybody should call functions like Foo.Bar(b). Fair enough?Mendive
@QianChen, I fail to parse this. Care to elaborate? From a cursory glance, I don't see any connection between the best practices of naming the receiver and a possibility to call any method as a plain function: 99.9% of Go code calls methods as methods using the usual receiver+dot+methodName notation.Rodarte
@Rodarte all you said were true. But it's not enough to justify this/self shouldn't be used. I am not against not using this, what I am against was the punishment of using this, like a linter warning or error.Mendive
@QianChen, I understand. But I'd recommend you to look at it at another angle: not using this/self is a so-called established practice. Like, say, snake_case in most C code: you may dislike it, but 99% of live Go code in the wild is written using this practice. And not following you buys you arguably almost nothing—mostly just not going out your existing comfort zone—while following this practice lessens friction when working with other people's code (expecially when you're about contributing somewhere or write Go at your $dayjob).Rodarte
@QianChen, I feel myself somewhat qualified for these musings because I've had reasonable exposure to writing production code in C++, Python, Ruby and Delphi—of which some use this, some—self, and the latter—Self. For some reason, all were fine with me. Now I'm working with Go, and the recommended approach to name method receivers when writing it feels naturally as well. I mean, I've used literally all popular ways of naming the receivers, and really cannot grasp what all the fuss is all about :-)Rodarte
@Rodarte you prefer to not use this to name the receive, that's great. My point is I don't see anything wrong to use this. The benefit is I can immediately recognize this is the instance of this struct. I don't think any of the reason you mentioned not to use this stands, but if you prefer not to use this, that's great. 20 years ago, people expressed their opinions by using the word reference implementation, today, people say your code is not pythonic, and mine is.Mendive
@QianChen, FWIW, here is what you can call the reference implementation of Go. It follows the conventions being discussed.Rodarte
What I meant by reference implementation is the spirit that if you are not happy with my implementation, implement your own. That is if you are not happy to use the lowercased struct name as receiver name, use your own.Mendive
U
40

I can see no particularly compelling reason to avoid the this / self convention. Other posts here merely cite community norms or describe aspects of method dispatch which have no bearing on naming conventions.

These code review guidelines reject this or self without giving any reason at all, unless you can read something into the implied claim that go places less emphasis on methods than other languages.

One advantage of committing to a this or self convention is that it helps to highlight violations of the Law of Demeter. Code such as this:

func (w *ChunkWriter) Write(recId uint32, msg []byte) (recs uint64, err error) {
    recs = w.chunk.Records
    err = w.handle.Write(recId, msg)
    if err == nil {
        recs++
        w.chunk.Records = recs
    }
    return
}

appears on the face of it to be reaching into w's members inappropriately. In fact it is accessing members of its receiver, which is perfectly proper.

Undrape answered 20/10, 2016 at 0:21 Comment(2)
Nice point. However Go has private/public distinction but uses packages not classes as boundary — any func/method in same package may access w.handle... which makes me wonder how if Law of Demeter applied to Go would frown upon those but allow it in a ChunkWriter method because that's "member of its reciever"? If demeter "unit" == package, than it's easy to apply "optically" —lowercase anyvar.anymember.Foo is still proper?Sparerib
Agree. The convention is purely, err, conventional. Using an abbreviation of the type is not telling me what role a variable is playing. Also, the type of a variable is easily accessible when using modern tooling.Sarette
W
20

I concur with Oliver Goodman's answer and admire the courage of his convictions in the face of such overwhelming odds.

Basically, the naysayers are saying that using "this" (my preference) as the receiver variable does NOT have precisely the same meaning as in other languages. So we should avoid it to preempt potential gotchas and keep confusion to a minimum.

But, of course, the same could be said about -> and ., not to mention many other C-like facets of Go (over which we have no control).

However, using "this" in the way the OP implies makes it so much easier to understand context. And since we know we are programming in Go, we understand that it has different implications compared to other languages.

Using "this" also implies that our type ("class") methods are "conventional". If some weirdness is expected then you can always use another receiver moniker to emphasise this fact.

But let's not throw the baby out with the bathwater!

From the very start, I have found that hard core Gophers tend to be very rigid and unbending. But I must admit that this has served the language well in many many areas. And I take my hat off to them in that respect.

However, this same unyielding mindset has unleashed problems in other areas, The GOPATH craziness is the most obvious example.

Listening to the experts is no excuse for not thinking things through. Even the experts have blind spots and hang-ups whose magnitude is often in direct proportion to their speciality.

Short answer: Use the convention that suits you best!

Wehner answered 27/10, 2018 at 18:38 Comment(4)
Is this an answer or are you just sharing an opinion?Pelotas
Both answer and opinion (in the general context in which the question was framed)!Wehner
I agree, naming the receiver variable “this” helps to convey the purpose of the function (purely as an instance method) and consistency in your codebase will allow for increased readability and understanding. Just because we can call the function in a different context doesn’t mean we shouldn’t be allowed to express the limited intentions of the function in the context of the code. In python we conventionally prefix private methods with an underscore (even though they can still be accessed publicly) to express the intentions of the function. How is this different ?Milburr
Since I have learnt this language i have understood how meaningless is this concern. They are much more interesting things to care about when using this language.Dwarf
C
14

The Go wiki recommends not using terms like this or self and instead using abbreviations derived from the type name: https://github.com/golang/go/wiki/CodeReviewComments#receiver-names

Coffeng answered 5/5, 2014 at 21:24 Comment(2)
Those are by some opinionated people. Everyone has a brain. They are not necessarily correct. I use this all the time, and I couldn't be happier.Mendive
What are the reasons or benefits of following their rules? I find no reasons or benefits from following their rules. I only find cons: I have to recall "Oh, what abbrev did I make before? Was it c or cl or what?", which will cost another 3 to 5 seconds. By using self, I can let my brain just type self, no need to waste 3 seconds just to recall our abbrevHeinrick
K
1

From https://blog.heroku.com/neither-self-nor-this-receivers-in-go.

func (this *Room) Announce() {
    srv := this.Server()
    for _, c := range srv.Clients() {
        // Send announcement to all clients about a new room
        c.Send(srv.RenderAnnouncement(this))
    }
}

// Moved between...

func (this *Server) AddRoom(room *Room) {
    for _, c := range this.Clients() {
        // Send announcement to all clients about a new room
        c.Send(this.RenderAnnouncement(room))
    }
}

When using this, there is confusion about whether we're referring to the server or the room as we're moving the code between.

-       c.Send(this.RenderAnnouncement(room))
+       c.Send(srv.RenderAnnouncement(this))

Refactoring this kind of code produce some bugs that the compiler will hopefully catch (or maybe not, if the interfaces happen to be compatible). Even bugs aside, having to edit all the little innards does make moving code around more tedious.

Moving across levels of abstraction is a great example of when consistently well-named receivers make a huge difference:

func (room *Room) Announce() {
    srv := room.Server()
    for _, c := range srv.Clients() {
        // Send announcement to all clients about a new room
        c.Send(srv.RenderAnnouncement(room))
    }
}

// Moved between...

func (srv *Server) AddRoom(room *Room) { 
    for _, c := range srv.Clients() { 
        // Send announcement to all clients about a new room
        c.Send(srv.RenderAnnouncement(room))
    }
}
Kresic answered 11/4, 2020 at 17:5 Comment(1)
Tongue in cheek. Go doesn't have overloading detection, so there's no compelling reason to have the same name for those methods. On the other hand, if you really want them look the same (as in implementing the same interface), then there's no reason to differentiate the two by the argument naming convention. Compare: printf("%s", this) to printf("%s", room). Compiler won't help with this if it is the same interface, and you provide it wrong args.Abib

© 2022 - 2024 — McMap. All rights reserved.