ProgrammingGo developer

How to implement and use custom types and methods in Go, and what nuances exist when defining them?

Pass interviews with Hintsage AI assistant

Answer.

Background:

In Go, situations often arise where built-in types are insufficient, and it is necessary to define a custom data type with methods to encapsulate logic and extend functionality. This is achieved by creating custom types (type) and methods (func (r Receiver) MethodName()).

Problem:

Beginner developers often get confused — what is the difference between declaring a new type based on an existing one? How to correctly implement methods? How is the issue of copying, passing by value/pointer resolved? They make mistakes in visibility, receiver type, and working with embedded structs.

Solution:

To define a custom type, the keyword type is used. Methods are implemented using a receiver — this is important for working with interfaces.

Code example:

type MyInt int func (m MyInt) Double() int { return int(m) * 2 } // For structs: type User struct { Name string Age int } func (u *User) Birthday() { u.Age++ } var u = User{"Alice", 30} u.Birthday() // Age = 31

Key features:

  • Custom types do not inherit methods from base types.
  • Methods with pointer receivers can modify state, while value receivers work with copies.
  • For interfaces, methods must be implemented on a "concrete" type, not on an alias.

Trick questions.

Do custom types inherit methods from the base type?

No. If you define type MyInt int, then MyInt does not have the methods of int. For example, calling String() or other methods from the base type will not work.

Can methods be defined for type aliases?

For an alias (type MyType = ExistingType), methods cannot be added. Methods can only be defined for new types (type MyType ExistingType), not for aliases.

What receiver should be used: pointer or value?

If a method needs to modify the object, it is better to use a pointer. A value receiver copies the structure, which can lead to unexpected behavior if, for instance, the structure contains slice and map fields.

Code example:

type Counter struct { value int } func (c *Counter) Inc() { c.value++ } func main() { c := Counter{} c.Inc() // only with a pointer will the method change value }

Common mistakes and anti-patterns

  • Confusing alias/new type — thinking that an alias can be extended with methods.
  • Using value receiver for "setters" and getting non-working code.
  • Expecting that built-in methods will automatically transfer to a custom type.

Example from real life

Negative case

A programmer created type MySlice []int and expected that methods of []int, for example, append, would work as methods on the type MySlice. It turned out that there were no methods, and accessing MySlice directly as []int was not allowed.

Pros:

  • It initially seemed convenient to reuse.

Cons:

  • Unexpected compatibility errors and inconveniences with methods.

Positive case

A type Counter int was defined with a method Inc, which allowed its use in several parts of the program with shared logic and without code duplication.

Pros:

  • Clear encapsulation of logic. Easy to test.

Cons:

  • It required manually implementing some helper functions, as they did not transfer from the built-in type int.