-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
vlad krilovskiy
committed
Feb 17, 2024
1 parent
ffeeb02
commit 41deda4
Showing
1 changed file
with
88 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |