# Métodos y Receptores en Go: Value vs Pointer Receivers

En los posts anteriores exploramos los **structs** y las **interfaces implícitas** en Go. Hoy profundizaremos en cómo agregar comportamiento a nuestros tipos mediante **métodos** y entenderemos una de las decisiones más importantes en Go: cuándo usar **value receivers** vs **pointer receivers**.

Si vienes de Java, este concepto puede ser nuevo para ti, ya que en Java todos los métodos trabajan con referencias implícitas. En Go, tienes control explícito sobre esto, y elegir correctamente es crucial para el rendimiento y la corrección de tu código.

## **¿Qué son los Métodos en Go?**

En Go, puedes definir métodos en cualquier tipo, no solo en structs. Un método es simplemente una función con un **receptor** especial:

```go
type Counter struct {
    value int
}

// Método con value receiver
func (c Counter) GetValue() int {
    return c.value
}

// Método con pointer receiver
func (c *Counter) Increment() {
    c.value++
}
```

La diferencia clave está en el receptor: `(c Counter)` vs `(c *Counter)`.

## **Value Receiver: Trabajando con Copias**

Un **value receiver** recibe una **copia** del valor. Cualquier modificación que hagas dentro del método no afecta al valor original:

```go
type Counter struct {
    value int
}

// Value receiver - recibe una COPIA
func (c Counter) Increment() {
    c.value++ // Solo modifica la copia
    fmt.Printf("Inside method: %d\n", c.value)
}

func main() {
    counter := Counter{value: 0}
    fmt.Printf("Before: %d\n", counter.value)
    counter.Increment()
    fmt.Printf("After: %d\n", counter.value) // ¡Sigue siendo 0!
}
```

**Salida:**

```go
Before: 0
Inside method: 1
After: 0  ← No cambió el original
```

### **¿Cuándo usar Value Receiver?**

✅ **Usa value receiver cuando:**

* El struct es pequeño (pocos campos, tipos primitivos)
    
* El método solo lee datos, no los modifica
    
* Quieres garantizar inmutabilidad
    
* El método es una función pura (sin efectos secundarios)
    

```go
type Point struct {
    X, Y float64
}

// Value receiver es perfecto aquí
func (p Point) Distance() float64 {
    return math.Sqrt(p.X*p.X + p.Y*p.Y)
}
```

## **Pointer Receiver: Trabajando con Referencias**

Un **pointer receiver** recibe una **referencia** al valor original. Las modificaciones afectan al valor original:

```go
type CounterPtr struct {
    value int
}

// Pointer receiver - recibe una REFERENCIA
func (c *CounterPtr) Increment() {
    c.value++ // Modifica el original
    fmt.Printf("Inside method: %d\n", c.value)
}

func main() {
    counter := CounterPtr{value: 0}
    fmt.Printf("Before: %d\n", counter.value)
    counter.Increment()
    fmt.Printf("After: %d\n", counter.value) // ¡Ahora sí cambió!
}
```

**Salida:**

```go
Before: 0
Inside method: 1
After: 1  ← Sí cambió el original
```

### **¿Cuándo usar Pointer Receiver?**

✅ **Usa pointer receiver cuando:**

* El método modifica el struct
    
* El struct es grande (evita copias costosas)
    
* Quieres consistencia (si un método modifica, todos deberían usar pointer)
    
* Necesitas que los cambios persistan
    

```go
type Account struct {
    ID      string
    Balance float64
    Owner   string
}

// Pointer receiver necesario porque modifica
func (a *Account) Deposit(amount float64) error {
    if amount <= 0 {
        return fmt.Errorf("amount must be positive")
    }
    a.Balance += amount // Modifica el original
    return nil
}

// También pointer receiver por consistencia
func (a *Account) GetBalance() float64 {
    return a.Balance
}
```

## **Regla de Oro: Consistencia**

Una regla importante en Go es **ser consistente**. Si un método de un struct usa pointer receiver porque modifica el struct, todos los métodos de ese struct deberían usar pointer receiver, incluso los que solo leen:

```go
type LargeStruct struct {
    data [1000]int  // Struct grande
    name string
}

// Modifica → pointer receiver
func (ls *LargeStruct) UpdateName(name string) {
    ls.name = name
}

// Solo lee, pero usa pointer receiver por consistencia
func (ls *LargeStruct) GetName() string {
    return ls.name
}
```

Esto hace que el código sea más predecible y evita confusiones.

## **Métodos en Tipos No-Struct**

Una característica poderosa de Go es que puedes definir métodos en **cualquier tipo**, no solo structs:

```go
// Tipo personalizado basado en int
type MyInt int

// Método en el tipo personalizado
func (m MyInt) Double() MyInt {
    return m * 2
}

// Implementar interfaces (como Stringer)
func (m MyInt) String() string {
    return fmt.Sprintf("MyInt(%d)", int(m))
}

func main() {
    num := MyInt(5)
    fmt.Println(num)           // MyInt(5)
    fmt.Println(num.Double())  // MyInt(10)
}
```

### **Métodos en Slices Personalizados**

```go
type IntSlice []int

func (is IntSlice) Sum() int {
    sum := 0
    for _, v := range is {
        sum += v
    }
    return sum
}

func (is IntSlice) Append(value int) IntSlice {
    return append(is, value)
}

func main() {
    numbers := IntSlice{1, 2, 3, 4, 5}
    fmt.Println(numbers.Sum())        // 15
    numbers = numbers.Append(6)
    fmt.Println(numbers)               // [1 2 3 4 5 6]
}
```

## **Conversión Automática: La Magia de Go**

Go hace algo muy inteligente: **convierte automáticamente** entre valores y punteros cuando llamas métodos:

```go
type Account struct {
    Balance float64
}

func (a *Account) Deposit(amount float64) {
    a.Balance += amount
}

func main() {
    // Tienes un value
    acc := Account{Balance: 1000}
    
    // Puedes llamar método con pointer receiver directamente
    acc.Deposit(100)  // Go automáticamente hace: (&acc).Deposit(100)
    
    // Tienes un pointer
    accPtr := &Account{Balance: 500}
    
    // Puedes llamar método con value receiver directamente
    fmt.Println(accPtr.String())  // Go automáticamente hace: (*accPtr).String()
}
```

Esto significa que **no necesitas preocuparte** por si tienes un valor o un puntero al llamar métodos. Go lo maneja automáticamente.

## **Ejemplo Práctico: Sistema Bancario**

Vamos a construir un sistema de cuentas bancarias que demuestra el uso correcto de receptores:

```go
type Account struct {
    ID      string
    Balance float64
    Owner   string
}

// Métodos que modifican → pointer receiver
func (a *Account) Deposit(amount float64) error {
    if amount <= 0 {
        return fmt.Errorf("deposit amount must be positive")
    }
    a.Balance += amount
    return nil
}

func (a *Account) Withdraw(amount float64) error {
    if amount <= 0 {
        return fmt.Errorf("withdrawal amount must be positive")
    }
    if a.Balance < amount {
        return fmt.Errorf("insufficient funds")
    }
    a.Balance -= amount
    return nil
}

// Método que solo lee → pero usa pointer por consistencia
func (a *Account) GetBalance() float64 {
    return a.Balance
}

// Método que solo lee → value receiver está bien
func (a Account) String() string {
    return fmt.Sprintf("Account{ID: %s, Owner: %s, Balance: $%.2f}", 
        a.ID, a.Owner, a.Balance)
}

func main() {
    account := &Account{
        ID:      "ACC001",
        Balance: 1000.0,
        Owner:   "John Doe",
    }
    
    account.Deposit(500.0)
    account.Withdraw(200.0)
    fmt.Println(account) // Account{ID: ACC001, Owner: John Doe, Balance: $1300.00}
}
```

## **Comparación: Java vs Go**

| **Aspecto** | **Java** | **Go** |
| --- | --- | --- |
| Receptores | Siempre referencia implícita | Explícito: value o pointer |
| Control | No tienes control | Control total |
| Copias | Automáticas en primitivos | Explícitas con value receiver |
| Performance | Optimizado por JVM | Control explícito del desarrollador |
| Consistencia | No aplica | Importante mantener consistencia |

En Java:

```go
public class Counter {
    private int value;
    
    public void increment() {
        this.value++; // Siempre modifica el original
    }
}
```

En Go:

```go
type Counter struct {
    value int
}

// Puedes elegir:
func (c Counter) Increment() { ... }      // Copia
func (c *Counter) Increment() { ... }     // Referencia
```

## **Decisiones de Diseño: Tabla de Referencia**

| **Escenario** | **Receiver Recomendado** | **Razón** |
| --- | --- | --- |
| Método modifica el struct | Pointer `*T` | Necesitas modificar el original |
| Struct grande (&gt;100 bytes) | Pointer `*T` | Evita copias costosas |
| Método solo lee, struct pequeño | Value `T` | Más simple, inmutable |
| Método solo lee, struct grande | Pointer `*T` | Consistencia y performance |
| Implementar `String()` | Value `T` | Convención común |
| Todos los métodos modifican | Pointer `*T` | Consistencia |

## **Errores Comunes**

### **❌ Error 1: Usar value receiver cuando necesitas modificar**

```go
// ❌ MAL
func (c Counter) Increment() {
    c.value++ // No funciona, solo modifica la copia
}

// ✅ BIEN
func (c *Counter) Increment() {
    c.value++ // Modifica el original
}
```

### **❌ Error 2: Mezclar value y pointer receivers sin razón**

```go
// ❌ MAL - Inconsistente
func (a *Account) Deposit(amount float64) { ... }  // Pointer
func (a Account) GetBalance() float64 { ... }      // Value

// ✅ BIEN - Consistente
func (a *Account) Deposit(amount float64) { ... }   // Pointer
func (a *Account) GetBalance() float64 { ... }     // Pointer
```

## **Mejores Prácticas**

1. **Sé consistente**: Si un método usa pointer receiver, todos deberían usarlo
    
2. **Piensa en el tamaño**: Structs grandes → pointer receiver
    
3. **Piensa en mutabilidad**: Si modificas → pointer receiver
    
4. **Excepciones**: `String()` y métodos similares pueden usar value receiver
    
5. **Cuando dudes**: Usa pointer receiver (es más seguro)
    

## **Conclusiones**

Los métodos y receptores en Go te dan control explícito sobre cómo se pasan los valores:

✅ **Value receivers**: Para structs pequeños, métodos inmutables, y cuando quieres garantizar que no se modifique el original

✅ **Pointer receivers**: Para métodos que modifican, structs grandes, y cuando quieres consistencia

✅ **Conversión automática**: Go maneja automáticamente las conversiones entre valores y punteros, haciendo el código más limpio

✅ **Consistencia**: Mantén consistencia en tu código usando el mismo tipo de receiver para todos los métodos de un struct

Esta decisión puede parecer pequeña, pero tiene implicaciones importantes en el rendimiento y la corrección de tu código. Una vez que entiendas cuándo usar cada uno, escribirás código Go más eficiente y mantenible.

## **Próximos Pasos**

En el siguiente post exploraremos las **colecciones en Go**: slices, maps y arrays. Aprenderemos sobre `len`, `cap`, `make`, y cómo trabajar eficientemente con estructuras de datos en Go.

---

**¿Tienes preguntas sobre value vs pointer receivers?** Déjame saber en los comentarios. Y si quieres ver el código completo de estos ejemplos, puedes encontrarlo en mi repositorio [go-mastery-lab](https://github.com/tu-usuario/go-mastery-lab).

Enjoy!

José Díaz

+51 939965148
