Что произойдет при попытке записи или чтения из `nil`-канала?

Ответ

Операции чтения и записи в nil-канал блокируют исполнение горутины навсегда.

Если в программе не останется других активных горутин, среда выполнения Go прервет программу с ошибкой fatal error: all goroutines are asleep - deadlock!.

Поведение при операциях:

  • Запись в nil-канал

    var ch chan int // ch == nil
    ch <- 42       // Эта горутина заблокируется здесь навсегда
  • Чтение из nil-канала

    var ch chan int // ch == nil
    <-ch           // Эта горутина также заблокируется навсегда

Практическое применение

Это предсказуемое поведение часто используется в операторе select для динамического включения или отключения одного из case. Если канал в case равен nil, этот case никогда не будет выбран.

var ch1, ch2 chan int
// ...

// Чтобы временно отключить обработку из ch2, можно сделать так:
// ch2 = nil

select {
case val := <-ch1:
    fmt.Println("Получено из ch1:", val)
case val := <-ch2: // Этот case будет игнорироваться, если ch2 == nil
    fmt.Println("Получено из ch2:", val)
}

Ответ 18+ 🔞

Да ты посмотри, что вытворяют с этими каналами! Ну, блядь, классика жанра, ёпта.

Вот представь, объявляешь ты канал, а он nil, сука. Ну, просто переменная, пустота, нихуя. И если ты, такой довольный, попробуешь в эту пустоту что-то запихнуть — ch <- 42 — всё, пиши пропало. Горутина твоя встанет как вкопанная и будет ждать у моря погоды. До скончания века, блядь. Пока её кто-нибудь не разбудит. А если никто не разбудит, потому что все остальные тоже уснули, то программа твоя охуеет и сдохнет с криком fatal error: all goroutines are asleep - deadlock!. Красиво, да?

То же самое и с чтением. Пытаешься вытащить что-то из ниоткуда — <-ch — и опять впадаешь в вечную спячку. Охуенная ловушка для новичков, ядрёна вошь!

var ch chan int // ch == nil, пустота, пиздец
ch <- 42       // Всё, приехали. Сиди тут и жди. Бесконечно.

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

Смотри, есть у тебя два канала, ch1 и ch2. И ты в select их слушаешь. А потом тебе надо, чтобы ch2 на время заткнулся, не мешался. Ну, ты можешь его, мудака, в nil превратить! И что происходит? А в select case с nil-каналом никогда не сработает, его как будто и нет! Удобно же, в рот меня чих-пых!

var ch1, ch2 chan int
// ...
// Хочу, чтобы ch2 не работал
// ch2 = nil // Вот и вся магия, ёбана!

select {
case val := <-ch1:
    fmt.Println("Получено из ch1:", val)
case val := <-ch2: // А этот case теперь спит, как сурок, потому что ch2 — нихуя
    fmt.Println("Получено из ch2:", val) // Сюда мы никогда не попадём, пока ch2 nil
}

Вот так вот, блядь. Казалось бы, баг, а оказалось — фича. Главное — не наступить на эти грабли в обычном коде, а то охуеешь потом искать, где же твоя горутина сгинула.