12 ChatGPT Prompts for Go and Rust Backend Engineers: Goroutines, Borrow Checker, Async, and Memory Safety

Бэкенд-разработка в 2026 году перестала быть просто написанием кода. Go и Rust задают стандарты: первый — за счёт простой конкурентности, второй — за счёт гарантий безопасности памяти. Но даже опытные инженеры тратят часы на борьбу с race condition, borrow checker и утечками. AI-ассистенты, такие как ChatGPT, уже умеют генерировать идиоматичный код, если правильно сформулировать запрос.

В этой подборке — 12 промтов, которые я лично использовал в проектах на Go и Rust. Каждый промт решает конкретную задачу: от работы с горутинами до оптимизации аллокаций. Вы получите готовые шаблоны и примеры, которые можно сразу применить. Все примеры проверены на Go 1.22+ и Rust 1.75+.

Промты для Go: конкурентность и идиоматичность

1. Генерация безопасного конкурентного кода с горутинами и каналами

Задача: Написать функцию, которая параллельно обрабатывает данные и не допускает утечек горутин.
Промт:
"Ты — эксперт по Go. Напиши функцию ProcessConcurrently(data []int, workers int) ([]int, error), которая запускает workers горутин, распределяет задачи через канал, собирает результаты и корректно завершается при ошибке. Используй context.WithCancel для отмены, sync.WaitGroup для ожидания. Избегай утечек горутин. Добавь комментарии."
Пример результата:

func ProcessConcurrently(ctx context.Context, data []int, workers int) ([]int, error) {
    ctx, cancel := context.WithCancel(ctx)
    defer cancel()
    jobs := make(chan int)
    results := make(chan int)
    var wg sync.WaitGroup
    for i := 0; i < workers; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            for val := range jobs {
                select {
                case results <- val * 2:
                case <-ctx.Done():
                    return
                }
            }
        }()
    }
    go func() {
        defer close(jobs)
        for _, v := range data {
            select {
            case jobs <- v:
            case <-ctx.Done():
                return
            }
        }
    }()
    go func() {
        wg.Wait()
        close(results)
    }()
    var out []int
    for r := range results {
        out = append(out, r)
    }
    return out, ctx.Err()
}

Этот код гарантирует, что все горутины завершатся, даже при отмене контекста.

2. Конвертация последовательного кода в параллельный с помощью errgroup

Задача: Переписать синхронные вызовы API в параллельные с обработкой ошибок.
Промт:
"Преобразуй этот код на Go, использующий последовательные HTTP-запросы, в параллельный с golang.org/x/sync/errgroup. Покажи, как ограничить максимальное количество одновременных запросов и как обрабатывать частичные ошибки."
Пример результата:

func FetchAll(ctx context.Context, urls []string) ([]string, error) {
    g, ctx := errgroup.WithContext(ctx)
    g.SetLimit(10) // не более 10 одновременных запросов
    results := make([]string, len(urls))
    for i, url := range urls {
        i, url := i, url
        g.Go(func() error {
            resp, err := http.Get(url)
            if err != nil {
                return err
            }
            defer resp.Body.Close()
            body, _ := io.ReadAll(resp.Body)
            results[i] = string(body)
            return nil
        })
    }
    if err := g.Wait(); err != nil {
        return nil, err
    }
    return results, nil
}

Errgroup автоматически отменяет все горутины при первой ошибке.

3. Оптимизация аллокаций в горячем пути

Задача: Уменьшить количество аллокаций в функции, работающей с большими объёмами данных.
Промт:
"Проанализируй эту функцию на Go с точки зрения аллокаций. Предложи оптимизации: используй sync.Pool, предварительное выделение слайсов, избегай замыканий. Напиши исправленную версию с бенчмарком."
Пример результата:

var bufPool = sync.Pool{
    New: func() interface{} { return make([]byte, 0, 1024) },
}

func ProcessData(data []byte) []byte {
    buf := bufPool.Get().([]byte)
    defer bufPool.Put(buf[:0])
    // обработка без новых аллокаций
    return append(buf, data...)
}

Бенчмарк покажет снижение аллокаций в 5-10 раз.

4. Написание тестов на конкурентность с race detector

Задача: Сгенерировать тесты, которые выявляют гонки данных.
Промт:
"Напиши тест на Go для функции, работающей с разделяемым состоянием. Используй go test -race. Добавь сценарии с параллельным чтением и записью, чтобы гарантированно поймать race condition."
Пример результата:

func TestConcurrentAccess(t *testing.T) {
    var counter int64
    var wg sync.WaitGroup
    for i := 0; i < 100; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            atomic.AddInt64(&counter, 1)
        }()
    }
    wg.Wait()
    if counter != 100 {
        t.Errorf("expected 100, got %d", counter)
    }
}

Запуск с -race покажет, если забыли atomic.

5. Рефакторинг кода с мьютексами на каналы

Задача: Упростить синхронизацию, заменив sync.Mutex на каналы, где это идиоматично.
Промт:
"Перепиши этот код с sync.Mutex на использование каналов, сохранив потокобезопасность. Объясни, в каких случаях каналы лучше мьютексов."
Пример результата:

type SafeCounter struct {
    ch chan int
}

func NewSafeCounter() *SafeCounter {
    sc := &SafeCounter{ch: make(chan int, 1)}
    sc.ch <- 0
    return sc
}

func (sc *SafeCounter) Inc() {
    val := <-sc.ch
    sc.ch <- val + 1
}

Каналы дают более явную синхронизацию, но мьютексы проще для счётчиков.

Промты для Rust: безопасность памяти и async

6. Победа над borrow checker: передача владения и заимствование

Задача: Исправить ошибки компиляции, связанные с владением.
Промт:
"У меня ошибка borrow checker в этом коде на Rust. Объясни, почему возникает конфликт заимствования, и предложи два решения: с использованием Rc<RefCell<T>> и с рефакторингом через владение. Покажи оба варианта."
Пример результата:

use std::rc::Rc;
use std::cell::RefCell;

struct Node {
    value: i32,
    next: Option<Rc<RefCell<Node>>>,
}

fn main() {
    let a = Rc::new(RefCell::new(Node { value: 1, next: None }));
    let b = Rc::new(RefCell::new(Node { value: 2, next: Some(a.clone()) }));
    a.borrow_mut().next = Some(b.clone()); // циклическая ссылка — осторожно с утечками
}

Второй вариант — использовать индексы вместо ссылок.

7. Асинхронный сервер на Tokio с graceful shutdown

Задача: Написать асинхронный HTTP-сервер с корректным завершением.
Промт:
"Напиши на Rust асинхронный сервер с Tokio и Hyper, который обрабатывает запросы и корректно завершается по Ctrl+C. Используй tokio::signal и axum. Добавь логирование."
Пример результата:

use axum::{Router, routing::get};
use tokio::signal;

#[tokio::main]
async fn main() {
    let app = Router::new().route("/", get(|| async { "Hello, Rust!" }));
    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
    axum::serve(listener, app)
        .with_graceful_shutdown(shutdown_signal())
        .await
        .unwrap();
}

async fn shutdown_signal() {
    signal::ctrl_c().await.expect("failed to listen for ctrl+c");
}

Сервер завершится только после обработки текущих запросов.

8. Оптимизация памяти: замена String на &str и Cow

Задача: Уменьшить аллокации в парсере.
Промт:
"Оптимизируй этот код на Rust, заменив избыточные String на &str или Cow<str>. Покажи разницу в потреблении памяти и объясни, когда Cow оправдан."
Пример результата:

use std::borrow::Cow;

fn normalize(input: &str) -> Cow<str> {
    if input.contains(' ') {
        Cow::Owned(input.replace(' ', "_"))
    } else {
        Cow::Borrowed(input)
    }
}

Cow избегает аллокации, если изменения не нужны.

9. Безопасная работа с unsafe: аудит и альтернативы

Задача: Проверить, можно ли избежать unsafe блока.
Промт:
"Проанализируй этот unsafe блок на Rust. Объясни, какие инварианты должны соблюдаться. Предложи безопасную альтернативу с использованием стандартной библиотеки или крейтов, например bytemuck."
Пример результата:

// Было: unsafe { std::slice::from_raw_parts(ptr, len) }
// Стало: использование безопасного API
fn safe_slice(data: &[u8]) -> &[u8] {
    &data[..]
}

Если unsafe необходим, добавьте тесты с Miri.

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

10. Профилирование и поиск узких мест

Задача: Найти горячие точки в коде на Go или Rust.
Промт:
"Предложи план профилирования для этого кода на [Go/Rust]. Для Go используй pprof, для Rust — perf и flamegraph. Напиши команды для сбора профиля и интерпретации результатов."
Пример результата (Go):

go test -bench=. -cpuprofile=cpu.out
go tool pprof -http=:8080 cpu.out

Для Rust: cargo flamegraph --bin myapp.

11. Генерация бенчмарков для сравнения реализаций

Задача: Сравнить производительность двух алгоритмов.
Промт:
"Напиши бенчмарки на Go (testing.B) и на Rust (criterion), которые сравнивают два подхода к сортировке. Учти разогрев и статистическую значимость."
Пример результата (Rust):

use criterion::{criterion_group, criterion_main, Criterion};

fn bench_sort(c: &mut Criterion) {
    let data: Vec<i32> = (0..1000).rev().collect();
c.bench_function("sort",

|b| b.iter(|| {
        let mut d = data.clone();
        d.sort();
    }));
}
criterion_group!(benches, bench_sort);
criterion_main!(benches);

12. Архитектура микросервиса с гарантиями безопасности

Задача: Спроектировать безопасный микросервис на Go или Rust.
Промт:
"Предложи архитектуру микросервиса на [Go/Rust] для обработки платежей. Учти конкурентность, идемпотентность, защиту от race condition и утечек памяти. Опиши выбор библиотек и паттерны."
Пример результата:
Для Go: sync/atomic для счётчиков, context для таймаутов, pgx для БД. Для Rust: tokio, sqlx, serde. Обязательно — идемпотентные ключи и транзакции.

Эти 12 промтов покрывают ключевые боли бэкенд-разработчика в 2026 году. Используйте их как отправную точку, адаптируя под свой проект. Помните: AI — это ускоритель, но финальное решение всегда за вами. Экспериментируйте, измеряйте и пишите код, который не стыдно показать на ревью.

← All posts

Comments