OOP in golang - HedgeDoc
  547 views
 owned this note
<center> # A how-to guide for OOP in Golang (for Python users) <big> **OOP in Golang might be peculiar, but that doesn’t mean it’s not possible! Let me show you how.** </big> *Written by Cesar Uribe. Originally published 2022-06-29 on the [Monadical blog](https://monadical.com/blog.html). Image credit: @jjfarquitectos* </center> Golang is a compiled language that has become very popular because it has the power of a compiled language like C, but with the versatility and transparency of a high-level language like Python. Golang has features such as static variable typing, pointer handling, compilation, concurrency, garbage collector, and the ease of a high-level dynamically typed language. If you use Python, and you want to learn how to program in Golang, you may want to know how to implement the Object-Oriented Programming (OOP) paradigm. Now, as you start learning how to implement OOP, you’ll probably come across a discussion in the programming community about whether or not Golang allows OOP[^first]. This is because Golang doesn’t have a class statement as seen in conventional languages like C++, Java, or Python. Nevertheless, OOP is possible in Golang, just in an unconventional way. [^first]: <small> I don’t want to debate the subtleties of this topic, but I would like to explain how Golang allows us to implement this paradigm that is widely used in the programming world.</small> In this post, I’ll first setup an example scenario that I’ll use to demonstrate the features of the OOP model. Next, I’ll show you how you can implement the OPP model/principles in Python, since most of us are familiar with this language. Finally, I’ll compare the implementation in Python to that in Golang. ## OOP example - A school system Here's a simple example to show some key OOP concepts such as abstraction, inheritance, polymorphism, and composition. Let’s imagine that you have a platform to manage courses, either online or in-person, and you want to structure your system using OOP. One thing that OOP allows us to do is to build complex systems from simple elements that can be related to each other. To build our example of an educational center or an online course platform, we assume it will be formed by schools or academic departments, which have courses, teachers, and associated students. In addition, each course will have a group of students, one or several teachers, a code, a timetable, a status, etc. Teachers and students will have some differentiating attributes, but others will be common regardless of their role. With this in mind, our example will implement four classes: Person, Student, Teacher, Teacher, Courses, and Academy which are conformed by: <table > <col width="80"> <col width="150"> <col width="200"> <col width="200"> <tr> <th>Name</th> <th>Notes</th> <th>Attributes</th> <th>Methods</th> </tr> <tr> <td>Person</td> <td></td> <td> <ul> <li>Firstname</li> <li>Lastname</li> <li>Age</li> <li>ID number</li> </ul> </td> <td> <ul> <li>greet</li> </ul> </td> </tr> <tr> <td>Student</td> <td> Inherit from Person. They have four categories: SCHOOL, UNDERGRADUATE, POSTGRADUATE, ONLINE </td> <td> All person attributes <ul> <li>category</li> </ul> </td> <td> <ul> <li>greet</li> </ul> </td> </tr> <tr> <td>Teacher</td> <td> Inherit from Person. Area corresponds to the subject the teacher teaches. Specialty is the specific topic that teachers know more about. </td> <td> All person attributes <ul> <li>Area</li> <li>Specialty</li> </ul> </td> <td> <ul> <li>Add specialty</li> <li>Remove specialty</li> </ul> </td> </tr> <tr> <td>Course</td> <td> Code is an ID that identifies the course. <code>isActive</code> establishes the course status: <em>active, inactive.</em> </td> <td> <ul> <li>Name</li> <li>Code</li> <li>Teacher</li> <li>Students</li> <li>isActive</li> </ul> </td> <td> <ul> <li>Add teacher</li> <li>Add student</li> <li>Add students</li> <li>Remove student</li> <li>Remove or upgrade teacher</li> <li>Set Status</li> </ul> </td> </tr> <tr> <td>Academy</td> <td>Composition of student, teacher and courses </td> <td> <ul> <li>Courses</li> <li>Students</li> <li>Teacher</li> </ul> </td> <td></td> </tr> </table> <br/> ## Let’s code our model in Python 🧑‍💻 Let's start by defining the class Person, which will serve as the basis for creating Teachers and Students: ```python class Person: def __init__(self, firstName, lastName, age, idnumber): self.firstName = firstName self.lastName = lastName self.age = age self.idnumber = idnumber def __str__(self): return f"fullname: {self.firstName} {self.lastName}, age: {self.age}, id:{self.idnumber}" def __repr__(self): return f"<{self.__class__.__name__} ({self.firstName} {self.lastName},{self.idnumber})>" def greet(self): print("Say hello from Person") ``` In this case, `__str__` and `__repr__` are not our model's own methods, but are special Python methods. `__str__` allows us to customize the string representation of the object, that is, it allows us to return a string that is readable by people. In the case of `__repr__`, it also allows us to customize the representation of the object but the difference here is that it can be executed, ie, it’s machine readable. When the `__str__` function is not implemented, it’s `__repr__` that returns the default object representation: ```python person1 = PersonWithoutSTR( firstname= "Jhon", lastname = "Mclane", age= 25, idnumber= 12345, ) person2 = Person( firstname= "John", lastname = "Doe", age= 25, idnumber= 12345, ) # Without str print(person1) print(person1.__repr__()) # With str print(person2) print(person2.__repr__()) ### OUTPUT # <__main__.PersonWithoutSTR object at 0x104787e80> # <__main__.PersonWithoutSTR object at 0x104787e80> # fullname: John Doe, age: 25, id:12345 # <Person (John Doe,12345)> ``` Now we’re going to create the Student class, which is inherited by the Person class: ```python class Student(Person): SCHOOL = 0 UNDERGRADUATE = 1 POSTGRADUATE = 2 ONLINE = 3 def __init__(self, category, firstname, lastname, age, idnumber): self.category: int = category super().__init__(firstname, lastname, age, idnumber) def __repr__(self): return f"<{self.__class__.__name__} [{self.firstname}]>" def greet(self): print("Say hello from ", self.__class__) ``` We continue with the Teacher class, which is also inherited from the Person class: ```python class Teacher(Person): def __init__( self, firstname, lastname, age, idnumber, area: str = "", speciality: list = [] ): self.area: str = area self.speciality: list = speciality super().__init__( firstname=firstname, lastname=lastname, age=age, idnumber=idnumber ) def add_speciality(self, speciality): self.speciality.append(speciality) def remove_speciality(self, speciality): self.speciality.remove(speciality) def __repr__(self): return f"<{self.__class__.__name__} [{self.firstname} ({self.area})]>" ``` Now let's create the Course class: ```python class Course: def __init__(self, name, code): self.name: str = name self.code: str = code self.teacher: Teacher = None self.students: list = [] self.is_active: bool = False def add_teacher(self, teacher: Teacher): self.teacher = teacher def add_student(self, student: Student): self.students.append(student) def add_students(self, student: list): self.students.extend(student) def remove_student(self, student: Student): self.students.remove(student) def remove_or_update_teacher(self, teacher): self.teacher = teacher def set_status(self, is_active: bool): self.is_active = is_active def __repr__(self): return f"<{self.__class__.__name__} [{self.name} - ({self.code})]>" ``` Finally, we have the Academy class which is going to be a very simple class that can be thought of as the faculty or school of a specific area. You can add more complexity but, at this moment, we’ll use it as the entity that contains the others. We already have the templates that will allow us to build a school/academy with courses and students. We are now going to create an academy. This process will be done manually, but if we think of this as a much more robust application, it would involve having a frontend or dashboard that allows us to create each of these entities and save the information in a database. We’ll start by creating a list of students, teachers and courses. ```python students_info = [ { "firstname": "Jhon", "lastname" : "Mclane", "age": 25, "idnumber": 12345, "category": Student.ONLINE }, { "firstname": "Siena", "lastname" : "Brooks", "age": 22, "idnumber": 102938, "category": Student.ONLINE }, { "firstname": "Walter", "lastname" : "Savic", "age": 22, "idnumber": 1675849, "category": Student.UNDERGRADUATE }, ] teachers_info = [ { "firstname": "Robert", "lastname" : "Langdon", "age": 33, "idnumber": 1183567, "area": "History", "speciality": ["Symbols"] }, { "firstname": "Marie", "lastname" : "Curie", "age": 33, "idnumber": 1183567, "area": "Physics", "speciality": ["Radiology"] } ] courses_info = [ { "name": "Meaning of symbols", "code": "H354" }, { "name": "Principles of radiation", "code": "CNR125" } ] ``` Now we’ll create the school: ```python school = Academy() school.students = [Student(**data) for data in students_info ] school.teachers = [Teacher(**data) for data in teachers_info] school.courses = [Course(**data) for data in courses_info] ``` We’ll make use of one of the methods of the course class. This operation would be equivalent to the course teacher assignment and student enrollment: ```python school.courses[0].add_students(school.students[:2]) school.courses[1].add_student(school.students[-1]) school.courses[0].add_teacher(school.teachers[0]) school.courses[1].add_teacher(school.teachers[1]) school.courses[0].set_status(is_active=True) ``` Showing the information contained in the school ```python print("Students: ", school.students) print("Courses: ",school.courses) print("Teachers: ",school.teachers) # OUTPUT # Students: [<Student [Jhon]>, <Student [Siena]>, <Student [Walter]>] # Courses: [<Course [Meaning of symbols - (H354)]>, <Course [Principles of radiation - (CNR125)]>] # Teachers: [<Teacher [Robert (History)]>, <Teacher [Marie (Physics)]>] ``` So that’s how OOP is implemented in Python. It’s useful when you want to structure complex problems starting from simpler elements. If you want to see the full code, go to the [Google Colab's notebook](https://colab.research.google.com/drive/1dSEp_0ncXWS__Va4WaLiLfxlgOr_Ye9f?usp=sharing). Now, let’s get to the interesting part. Let’s learn how to implement the OOP paradigm in Golang. ## Let’s code our model in Golang 🎉 The first thing to note is that Golang does not naturally implement OOP, so there is no class statement to implement classes. However, Golang allows us to implement this paradigm using structs and receiver functions. Unlike Python, which contains the entire class definition inside the same struct, these two components are implemented separately in Golang. Let's take a look: ### Step one: Importing libraries As in Python and other languages, you need to import the required libraries at the beginning of the file or code: ```go package main import ( "encoding/json" "fmt" "log" "sort" ) ``` ### Step Two: Defining structures If you’re a programmer used to Python, then this type of variable may not be familiar to you. In contrast, if you come from using C/C++, it’ll probably come naturally to you. A structure in Golang can be understood as a variable composed of another set of variables. These can be basic types or structures (nested structures). You may ask, what do structures have to do with OOP? Well, think of a Golang struct as a class that only has attributes. Let's define the structures for our example entities: #### Person We'll define the same attributes used in the Python example. You’ll notice how each property is accompanied by its variable type. ```go type Person struct { Firstname string Lastname string Age int8 Idnumber int } ``` Remember, if the variable name starts with a lowercase letter in Golang, then it can only be used within the file. However, if the variable name starts with a capitalized letter, then the variable can be used within another file within the same module, or by an external module. In our case, we will define our variables as public since we will use the JSON package later. #### Student For this entity we’ll implement a few things in addition to just the structure: ```go type Mode int8 const ( SCHOOL Mode = iota UNDERGRADUATE POSTGRADUATE ONLINE ) ``` We’ve defined a set of constants that will define the modality of the student. In Python these constants were defined as global attributes of the class; however, here we’ve defined them as *enumerated global constants*. In this case, **[iota](https://golangbyexample.com/iota-in-golang/) works as an enum in Python.** One thing you can do with Golang is that we can define our own variable type. In this case, we have defined our own variable type called Mode, which is just an int8 with a different name, using the statement type So, we can say that to define a custom variable you need this follow the pattern `type <custom name> <variable type>`. If you note, we could say that structures work like custom variables that you can assign a specific name to, because follow the same pattern: `type <custom name> struct` Now, we’ll define the student’s structure that will contain only the entities’ attributes: ```go type Student struct { Person Category Mode } // Without anonymous composition /* type Student struct { Individual Person Category Mode } */ ``` The implementation of this structure is interesting because it allows us to discuss an important peculiarity of OOP implementation in Golang – composition over inheritance. What this means is that you can’t implement class inheritance but you can implement composition. This means that some attributes of the class are instances, or from the type of another class. For this reason, the first variable is anonymous, only the type is defined. This allows us to use the attributes of Person at the same level as Category as would happen with inheritance. In other words, `Student` will have its own attributes of `Person`. Think of this as dictionary destructuring in Python. If the composition was defined without anonymous variables, it would be necessary to define a variable of type `Person`, which we call `Individual` in our case. This implies that the attributes of `Person` would be contained inside `Individual` and would not be at the same level as `Category`. This would be equivalent to the composition of classes in python. #### Teacher In the same way as the Student structure, the Teacher structure makes use of the composition with anonymous variables to make use of the attributes of Person. ```go type Teacher struct { Person Area string Speciality []string } ``` #### Course Unlike the previous structures, Course doesn’t implement composition with anonymous variables but instead defines two of its own attributes. Those attributes’ type is one of the previously defined structures and list of structures. ```go type Course struct { Name string Code string Teacher Teacher Students []Student IsActive bool } ``` #### Academy Finally, we define the last structure which will encompass the rest: ```go type Academy struct { Courses []Course Students []Student Teachers []Teacher } ``` ### Step Three: Defining the methods Since Golang doesn’t have a way to define a class directly, it’s necessary to use another resource: *the receiver functions*. These functions allow us to add functionality to the data types. They aren’t traditional functions but are defined as receivers, which can be value receivers or pointer receivers. By using structures and receivers, we can implement OOP in Golang. In the next example, I’ll define the methods for each structure. :::info Note: For readability purposes, and to make the comparison with Python easier, I’ll show the structs again. ::: #### Person In the Python example, the Person class had four methods defined: `__init__` which corresponds to the constructor, `__str__`, `__repr__`, printMessage. In the case of Golang, it looks like this: ```go type Person struct { Firstname string Lastname string Age int8 Idnumber int } // Constructor // return a pointer to struct func NewPerson( firstname string, lastname string, age int8, idnumber int, ) *Person { return &Person{ Firstname: firstname, Lastname: lastname, Age: age, Idnumber: idnumber, } } // Like __str__ method in python func (person Person) String() string { return fmt.Sprintf("firstname: %s lastname: %s, age: %d, id: %d ", person.Firstname, person.Lastname, person.Age, person.Idnumber, ) } func (person Person) Greet() { fmt.Println("Say hello from Person") } ``` Compared to Python, it’s worth noting a few things: 1. The constructor method is a standard function, not a receiver function. This function creates the structure and initializes the values. 2. The constructor method doesn’t return a variable. Instead, it returns a pointer aka the reference to the variable. There are discussions about whether it is best to return the value, copy, or reference to the variable. This decision can be related to whether you want to change the content or have proper memory management among others[^second]. [^second]: <small>If you want to review this topic, you can consult the references. In our case, we’re going to implement the standard recommendation which is to return a pointer to the variable.</small> 3. The receiver functions define which structure you’re adding to. We’ll see an example of this between the word func and the function name. 4. The defined stringer method (`String`) fulfills the same functionality as `__str__` in Python, that is giving a representation of the structure and making it readable in English. https://go.dev/play/p/JckEcmyBgoV ```go . . . func main() { person := NewPerson("john", "doe", 25, 12345) fmt.Println(person) } //==== OUTPUT ==== // Without stringer method // &{john doe 25 12345} // With stringer method // firstname: john lastname: doe, age: 25, id: 12345 ``` 5. Inside the receiver function we can make use of the structure’s attributes in the same way that a class' method does. The only difference is the syntax. 6. The use of value receivers means that a copy of the structure was made and passed to the receiver function. Only use this method when you’re going to use the values without modifying them! In this case, both the String and Greet functions only use the values stored in the structure but do not alter them. #### Utilities In the Python version, we could see inside each class and define the class’ attributes that were used to define the students’ roles. Here, we use a similar technique but these types of variables are external to the structures that represent the class. I had already mentioned some of them in a previous section ([Student](#Student)), however I will group them in this section as utilities. ```go type Mode int8 const ( SCHOOL Mode = iota UNDERGRADUATE POSTGRADUATE ONLINE ) // Stringer method allows to return // a name instead of a int value func (m Mode) String() string { return [...]string{"School", "Undergraduate", "Postgraduate", "Online"}[m] } func (m Mode) EnumIndex() int { return int(m) } type Status struct { ACTIVE bool INACTIVE bool } var status Status = Status{true, false} ``` The `iota` statement allows us to define an enumerated set of variables, like enum in Python. We also use the stringer method to return a string instead of an integer. The `EnumIndex` method does the opposite operation to the stringer: it returns the corresponding integer value. It’s worth clarifying that these constants are actually integers, so you can do operations directly. The `EnumIndex` function is for those cases where we need to return an integer value instead of a string. Let’s see how this works: ```go . . . func main() { fmt.Println(SCHOOL, UNDERGRADUATE, POSTGRADUATE, ONLINE) fmt.Println(UNDERGRADUATE.EnumIndex(), POSTGRADUATE.EnumIndex()) fmt.Println(UNDERGRADUATE * 2) fmt.Printf("%d\n", POSTGRADUATE) } // ==== Output ===== // School Undergraduate Postgraduate Online // 1 2 // Postgraduate // 2 ``` https://go.dev/play/p/OTXazb-xt5x #### Student The constructor method is defined for the Student class. Note that Student uses an anonymous composition of the Person class. ```go type Student struct { Person Category Mode } // Constructor func NewStudent( firstname string, lastname string, age int8, idnumber int, category Mode, ) *Student { return &Student{ Person: *(NewPerson(firstname, lastname, age, idnumber)), Category: category, } } ``` #### Teacher The constructor method and the methods of the Teacher class are defined: ```go type Teacher struct { Person Area string Speciality []string } // constructor func NewTeacher( firstname, lastname string, age int8, idnumber int, area string, speciality int, ) *Teacher { return &Teacher{ Person: *(NewPerson(firstname, lastname, age, idnumber)), Area: area, Speciality: []string{}, } } // Method to add speciality func (t *Teacher) addSpeciality(speciality string) { t.Speciality = append(t.Speciality, speciality) } // Method to remove speciality func (t *Teacher) removeSpeciality(speciality string) { // get the id position // Search return n [length of the slice] if it not found the element idx := sort.Search(len(t.Speciality), func(i int) bool { return t.Speciality[i] == speciality }) if idx < len(t.Speciality) { t.Speciality = append(t.Speciality[:idx], t.Speciality[idx+1:]...) } fmt.Println(idx, t.Speciality) } ``` In this class, you’ll see that the receiver functions implemented are pointer receivers. We do this here because we want to change the structure’s content. If they were value receivers, we would have passed a copy of the function. This is because if you change one of its values, the initial structure will not change. On the other hand, when we use pointer receivers, we will pass the reference onto the structure, aka change the structure’s values. #### Course The Course class methods are created using the concepts mentioned above: ```go type Course struct { Name string Code string Teacher Teacher Students []Student IsActive bool } // Constructor func NewCourse( name string, code string, ) *Course { isActive := false return &Course{ Name: name, Code: code, IsActive: isActive, } } // Methods for Course struct func (c *Course) addTeacher(teacher Teacher) { c.Teacher = teacher } func (c *Course) addStudent(student Student) { c.Students = append(c.Students, student) } func (c *Course) addStudents(students []Student) { c.Students = append(c.Students, students...) } func (c *Course) removeStudent(student Student) { idx := sort.Search(len(c.Students), func(i int) bool { return c.Students[i] == student }) if idx < len(c.Students) { c.Students = append(c.Students[:idx], c.Students[idx+1:]...) } } func (c *Course) removeOrUpdateTeacher(teacher Teacher) { c.Teacher = teacher } func (c *Course) setStatus(isActive bool) { c.IsActive = isActive } ``` ### Step Four: Defining the main function We’re now going to implement the Golang syntax with the same process done in Python. First I’ll define the objects that I’ll be using. In this case, they’ll be strung with JSON format: ```go studentsInfoJSON := fmt.Sprintf(` [ { "firstname": "Jhon", "lastname" : "Mclane", "age": 25, "idnumber": 12345, "category": %d }, { "firstname": "Siena", "lastname" : "Brooks", "age": 22, "idnumber": 102938, "category": %d }, { "firstname": "Walter", "lastname" : "Savic", "age": 22, "idnumber": 1675849, "category": %d } ] `, ONLINE, ONLINE, UNDERGRADUATE) teachersInfoJSON := `[ { "firstname": "Robert", "lastname" : "Langdon", "age": 33, "idnumber": 1183567, "area": "History", "speciality": ["Symbols"] }, { "firstname": "Marie", "lastname" : "Curie", "age": 33, "idnumber": 1183567, "area": "Physics", "speciality": ["Radiology"] } ]` coursesInfoJSON := `[ { "name": "Meaning of symbols", "code": "H354" }, { "name": "Principles of radiation", "code": "CNR125" } ]` ``` I’ll create the objects from the structures defined above. ```go json.Unmarshal([]byte(studentsInfoJSON), &school.Students) fmt.Println("Students: ", school.Students) json.Unmarshal([]byte(teachersInfoJSON), &school.Teachers) fmt.Println("Teachers: ", school.Teachers) json.Unmarshal([]byte(coursesInfoJSON), &school.Courses) fmt.Println("Courses: ", school.Courses) ``` In this case, we’re making use of the JSON library to process the information. Note that the Unmarshal function allows us to map the string in the JSON format to a list of structures that must implement the same fields as the JSON. Then I’ll make the assignment process: ```go school.Courses[0].addStudents(school.Students[:2]) school.Courses[1].addStudent(school.Students[len(school.Students)-1]) school.Courses[0].addTeacher(school.Teachers[0]) school.Courses[1].addTeacher(school.Teachers[1]) school.Courses[0].setStatus(status.ACTIVE) ``` And display the information contained in the object school: ```go schoolJSON, err := json.MarshalIndent(school, "", " ") if err != nil { log.Fatalf(err.Error()) } fmt.Printf("After updates:\n%s\n", string(schoolJSON)) school.Students[0].PrintMessage() ``` The output we get looks like this: ``` Students: [firstname: Jhon lastname: Mclane, age: 25, id: 12345 firstname: Siena lastname: Brooks, age: 22, id: 102938 firstname: Walter lastname: Savic, age: 22, id: 1675849 ] Teachers: [firstname: Robert lastname: Langdon, age: 33, id: 1183567 firstname: Marie lastname: Curie, age: 33, id: 1183567 ] Courses: [{Meaning of symbols H354 firstname: lastname: , age: 0, id: 0 [] false} {Principles of radiation CNR125 firstname: lastname: , age: 0, id: 0 [] false}] After updates: { "Courses": [ { "Name": "Meaning of symbols", "Code": "H354", "Teacher": { "Firstname": "Robert", "Lastname": "Langdon", "Age": 33, "Idnumber": 1183567, "Area": "History", "Speciality": [ "Symbols" ] }, "Students": [ { "Firstname": "Jhon", "Lastname": "Mclane", "Age": 25, "Idnumber": 12345, "Category": 3 }, { "Firstname": "Siena", "Lastname": "Brooks", "Age": 22, "Idnumber": 102938, "Category": 3 } ], "IsActive": true }, { "Name": "Principles of radiation", "Code": "CNR125", "Teacher": { "Firstname": "Marie", "Lastname": "Curie", "Age": 33, "Idnumber": 1183567, "Area": "Physics", "Speciality": [ "Radiology" ] }, "Students": [ { "Firstname": "Walter", "Lastname": "Savic", "Age": 22, "Idnumber": 1675849, "Category": 1 } ], "IsActive": false } ], "Students": [ { "Firstname": "Jhon", "Lastname": "Mclane", "Age": 25, "Idnumber": 12345, "Category": 3 }, { "Firstname": "Siena", "Lastname": "Brooks", "Age": 22, "Idnumber": 102938, "Category": 3 }, { "Firstname": "Walter", "Lastname": "Savic", "Age": 22, "Idnumber": 1675849, "Category": 1 } ], "Teachers": [ { "Firstname": "Robert", "Lastname": "Langdon", "Age": 33, "Idnumber": 1183567, "Area": "History", "Speciality": [ "Symbols" ] }, { "Firstname": "Marie", "Lastname": "Curie", "Age": 33, "Idnumber": 1183567, "Area": "Physics", "Speciality": [ "Radiology" ] } ] } Say hello from Person ``` If you’re interested in running the full code, you can find it [here](https://go.dev/play/p/m5dcPdDmji3). ## Conclusion Although Golang doesn’t conventionally implement OOP like other languages, it offers an alternative way to implement this paradigm. We’ve seen that, Beyond the discussion of whether or not Golang is OOP, it is possible to develop an application using OOP in Golang. By learning to use OOP in Golang you’ll be able to tap into the power of Golang as a compiled, typed and concurrency-native language to optimize your applications. This power, along with the ease of coding in Golang, allows you to deploy most applications effectively. ## References - https://www.reddit.com/r/golang/comments/kfd5rr/when_should_i_return_a_pointer_vs_value_when/ - https://stackoverflow.com/questions/32208363/returning-value-vs-pointer-in-go-constructor - https://simondrake.dev/2021-05-27-value-receivers-vs-pointer-receivers/ - https://go.dev/tour/methods/8 - https://golangbyexample.com/iota-in-golang/ - https://www.educative.io/edpresso/what-is-an-enum-in-golang - https://pkg.go.dev/golang.org/x/tools/cmd/stringer - https://medium.com/gophersland/gopher-vs-object-oriented-golang-4fa62b88c701 - https://golangbot.com/methods/



Recent posts:


Back to top