Golang: Возврат структур из функций
В Go структура (struct
) — это значение. Это означает, что при передаче или возврате структуры из функции она копируется. Такое поведение важно учитывать, особенно если структура большая или содержит поля, указывающие на изменяемые данные.
Возврат структуры по значению
Вы можете напрямую возвращать структуру из функции:
type User struct {
Name string
Age int
}
func NewUser(name string, age int) User {
return User{Name: name, Age: age}
}
func main() {
user := NewUser("Alice", 30)
fmt.Println(user.Name) // Alice
}
Здесь возвращается копия структуры User
. Все поля копируются, и дальнейшие изменения переменной user
в main()
не затронут оригинальный объект, созданный внутри NewUser
.
Возврат указателя на структуру
Иногда вместо копии удобнее вернуть указатель на структуру:
func NewUserPtr(name string, age int) *User {
return &User{Name: name, Age: age}
}
Такой подход даёт:
- Возможность изменять поля структуры вне функции.
- Экономию памяти (особенно при работе с большими структурами).
- Консистентность в стиле, если структура часто используется по ссылке.
Пример:
func main() {
user := NewUserPtr("Bob", 25)
user.Age++
fmt.Println(user.Age) // => 26
}
Безопасность возврата указателя
Go автоматически размещает значения в куче, если они должны жить дольше области действия функции. Поэтому &User{...}
— безопасно: компилятор гарантирует, что указатель не станет висячим (dangling pointer).
func NewUser() *User {
u := User{Name: "Charlie", Age: 40}
return &u // безопасно: Go поместит структуру в heap
}
Когда использовать что?
- Возвращайте по значению, если структура небольшая и вы не планируете её менять.
- Возвращайте указатель, если:
- Структура содержит изменяемое состояние.
- Нужно избежать лишнего копирования.
- Вы хотите единообразие с методами, использующими указатель-приёмник.
Задание
Создайте структуру Product
с полями:
Name
— название товара (строка),Price
— цена (целое число).
Реализуйте функцию NewDiscountedProduct(name string, price int, discount int) *Product
, которая возвращает указатель на новый товар с учётом скидки discount
(в процентах).
Пример
p := NewDiscountedProduct("Laptop", 1000, 10)
fmt.Println(p.Price) // 900
Полезное
Команда проекта находится в телеграм-сообществе. Там можно задать любой вопрос и повлиять на проект
Если вы зашли в тупик, то самое время поговорить с нашим асситентом Тота во вкладке "ИИ-помощник":
Тесты устроены таким образом, что они проверяют решение разными способами и на разных данных. Часто решение работает с одними входными данными, но не работает с другими. Чтобы разобраться с этим моментом, изучите вкладку «Тесты» и внимательно посмотрите на вывод ошибок, в котором есть подсказки.
Это нормально 🙆, в программировании одну задачу можно выполнить множеством способов. Если ваш код прошел проверку, то он соответствует условиям задачи. В редких случаях бывает, что решение подогнано под тесты, но это видно сразу.
Создавать обучающие материалы, понятные для всех без исключения, довольно сложно. Мы очень стараемся, но всегда есть что улучшать. Если вы встретили материал, который вам непонятен, опишите проблему в обратной связи нашего сообщества
Ваше упражнение проверяется по этим тестам
package solution
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestNewDiscountedProduct(t *testing.T) {
tests := []struct {
name string
price int
discount int
wantPrice int
}{
{"No discount", 1000, 0, 1000},
{"10% discount", 1000, 10, 900},
{"50% discount", 1000, 50, 500},
{"100% discount", 1000, 100, 0},
{"Negative discount", 1000, -10, 1000},
{"Over 100% discount", 1000, 150, 0},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
p := NewDiscountedProduct("Test", tt.price, tt.discount)
assert.Equal(t, tt.wantPrice, p.Price, "Price for test case '%s' did not match", tt.name)
})
}
}
Решение учителя откроется через:
20:00
