Skip to content

Commit

Permalink
Первая статья
Browse files Browse the repository at this point in the history
  • Loading branch information
vlad krilovskiy committed Feb 17, 2024
1 parent ffeeb02 commit 41deda4
Showing 1 changed file with 88 additions and 0 deletions.
88 changes: 88 additions & 0 deletions _posts/2024-02-17-methods-on-t.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
---
title: Объявление методов у типа T или *T
date: 2024-02-17 20:21:00 +0500
categories: [Programming]
tags: [golang]
---

В Go для любого типа T существует тип *T, который является результатом выражения, принимающего адрес переменной типа T
(<i>Мы говорим T, но это просто держатель для типа, который вы объявляете</i>). Например:

```go
type T struct {
a int; b bool
}
var t T // у t тип T
var p = &t // у p тип *T
```
Эти два типа, T и *T, различны, но *T не может быть заменен на T.
(<i>Это правило рекурсивно: взяв адрес переменной типа *T, вы получите результат типа **T</i>)

Вы можете объявить метод у любого типа, который принадлежит вам;
то есть у типа, который вы объявили в своем пакете.
(<i>Именно поэтому никто не может объявлять методы на примитивных типах вроде int</i>)

Отсюда следует, что вы можете объявлять методы как у объявленного вами типа T,
так и у соответствующем ему производном типе-указателе *T.

По-другому можно сказать, что методы у типа объявляются для получения копии значения
их получателя или указателя на значение их получателя.
(<i>Методы в Go - это просто синтаксический сахар для функции,
которая передает получателя в качестве первого формального параметра</i>)

<b>Возникает вопрос, какую форму лучше использовать?</b>

Очевидно, что если ваш метод мутирует получателя, то он должен быть объявлен у типа *T.
Однако если метод не мутирует получателя, безопасно ли объявлять его вместо этого у Т?

Оказывается, случаи, когда это безопасно, очень ограничены.
Например, хорошо известно, что нельзя копировать значение sync.Mutex, так как это нарушает инварианты мьютекса.

Поскольку мьютексы управляют доступом к другим вещам, их часто оборачивают в структуру со значением,
которое они контролируют:

```go
package counter

import "sync"

type Val struct {
mu sync.Mutex
val int
}

func (v *Val) Get() int {
v.mu.Lock()
defer v.mu.Unlock()
return v.val
}

func (v *Val) Add(n int) {
v.mu.Lock()
defer v.mu.Unlock()
v.val += n
}
```

Большинство программистов на Go знают, что ошибкой будет забыть объявить методы Get или Add у получателя указателя *Val.
Однако любой тип, который встраивает Val, чтобы использовать его нулевое значение,
также должен объявлять методы только у своего получателе-указателе (*Т),
иначе он может случайно скопировать содержимое значений своего встроенного типа.

```go
type Stats struct {
a, b, c counter.Val
}

func (s Stats) Sum() int {
return s.a.Get() + s.b.Get() + s.c.Get() // whoops
}
```

Аналогичный подводный камень может возникнуть с типами, которые хранят срезы значений,
и, конечно, существует возможность непреднамеренной гонки данных.

<b>Короче говоря, я считаю, что лучше объявлять методы на *T, если у вас нет веских причин поступать иначе.</b>


<i>Данная статья является вольным переводом статьи [Should methods be declared on T or *T](https://dave.cheney.net/2016/03/19/should-methods-be-declared-on-t-or-t)</i>

0 comments on commit 41deda4

Please sign in to comment.