В лингвистике такое явление получило название ложные друзья переводчика, то есть слова, похожие по звучанию или написанию, но совершенно разные по значению. Например, английское слово buiscuit (печенье) и русское бисквит. С виду практически одинаковые, но последний переводится на английский как sponge cake. Это может привести к путанице.
То же можно сказать и о языках программирования. Например, в C# и golang есть куча схожих моментов, и это обстоятельство нередко помогает разработчикам C# в освоении golang. Но есть нюансы, которые могут приводить к неожиданному поведению. Например, при применении в golang подходов, используемых в C#.
Можно выделить четыре таких момента, способных запутать даже бывалых пользователей С#:
1. Switch с повторяющимися cases
В golang операторы switch
не требуют постановки break
или return
после каждого case
. В случае с несколькими case
, которые в C# последовательно обрабатываются один за другим, в golang обрабатывается только последний case
:
package main
import (
"fmt"
)
type Weekday string
const (
Sunday Weekday = "Sunday"
Monday Weekday = "Monday"
Tuesday Weekday = "Tuesday"
Wednesday Weekday = "Wednesday"
Thursday Weekday = "Thursday"
Friday Weekday = "Friday"
Saturday Weekday = "Saturday"
)
func main() {
printIsWeekday(Sunday)
// выводит: воскресенье - выходной
printIsWeekday(Monday)
// выводит:
printIsWeekday(Tuesday)
// выводит:
printIsWeekday(Wednesday)
// выводит:
printIsWeekday(Thursday)
// выводит:
printIsWeekday(Friday)
// выводит: пятница - будний день
printIsWeekday(Saturday)
// выводит:
}
func printIsWeekday(day Weekday) {
switch day {
case Monday:
case Tuesday:
case Wednesday:
case Thursday:
case Friday:
fmt.Printf("%s is a weekday\n", day)
case Saturday:
case Sunday:
fmt.Printf("%s is a weekend day\n", day)
}
}
Чтобы обрабатывались все, в golang разные варианты для каждого case
разделяются запятыми:
func printIsWeekday(day Weekday) {
switch day {
case Monday, Tuesday, Wednesday, Thursday, Friday:
fmt.Printf("%s is a weekday\n", day)
case Saturday, Sunday:
fmt.Printf("%s is a weekend day\n", day)
}
}
И выглядит очень аккуратно!
2. int != int32
В C# int
— это псевдоним для System.Int32
. Они нередко используются взаимозаменяемо. А в Golang это совершенно разные типы, и поэтому вот этот код не будет компилироваться:
package main
import "fmt"
func main() {
var (
number1 int = 5
number2 int32 = 5
)
if number1 == number2 {
fmt.Println("number1 and number2 are equal")
}
}
В Golang тип int
может меняться в зависимости от компьютера, на котором он запускается:
3. Операторы if могут переопределять смысловое значение переменных
Используя оператор :=
в конструкции с if
, мы создаём новые временные переменные, игнорируя переменные с одинаковым именем, которые могут существовать в одном контексте. Ниже показано, что exists
не будет true
во второй конструкции с if
.
package main
import "fmt"
func main() {
var exists bool
if exists, err := fileExists("somefile"); err == nil {
if exists {
fmt.Println("inside the first if")
}
}
if exists {
fmt.Println("inside the second if")
// Не будет выведен
}
}
func fileExists(fileName string) (bool, error) {
return true, nil
}
Чтобы этого не допустить, надо использовать оператор =
, то есть exists
и err
надо объявить заранее. А можно оставить оператор :=
, переместив весь вызов fileExists
вперёд до конструкции с if
:
func main() {
exists, err := fileExists("somefile")
if err == nil && exists {
fmt.Println("inside the first if")
}
if exists {
fmt.Println("inside the second if")
}
}
В официальной документации указано, что это — ожидаемое поведение:
Заметьте, что это не характерно для операторов if
.
4. Перечисление совсем не как в C#
В этом фрагменте кода схожих моментов меньше всего. Но в Golang реализация чего-то похожего на перечисление основывалась бы на новом type
и нескольких константах вновь созданного type
. Однако компилятор не будет делать такую реализацию (вопреки тому, что можно было бы ожидать при применении в golang подходов, используемых в C#):
package main
import (
"fmt"
"reflect"
)
type Weekday string
const (
Sunday Weekday = "Sunday"
Monday Weekday = "Monday"
Tuesday Weekday = "Tuesday"
Wednesday Weekday = "Wednesday"
Thursday Weekday = "Thursday"
Friday Weekday = "Friday"
Saturday Weekday = "Saturday"
)
func main() {
print(Wednesday)
// выводит: Wednesday (main.Weekday)
print("я притворяюсь, что я будний день!")
// выводит: я притворяюсь, что я будний день(main.Weekday)
}
func print(day Weekday) {
Как видим, здесь оператор type
определяет день недели, имея в виду значение, а ассоциирует его с фактическим типом (в данном случае string
). И без этих перечислений предельно ясно, что эти конкретные строки означают дни недели. Но компилятор не требует, чтобы print
получал только объекты Weekday
, вместо этого он концентрирует внимание на базовом типе.
В сообществе продолжают появляться предложения о включении в golang функционала перечислений (добавить тип-сумму и добавить поддержку типа-перечисления), но споры по этому поводу не утихают.
В заключение можно сказать, что есть масса схожих моментов в программировании на обоих языках. Но главный вывод, который следует сделать всем приступающим к освоению Go: не считать, что синтаксис, допустимый на c#, можно не глядя использовать в golang.
Начните с азов, потихоньку оттачивайте свои навыки, и у вас обязательно всё получится 🙂
Читайте также:
Перевод статьи Paulo Gomes: 4 golang code snippets that will deceive C# developers!