
Apenas alguns dias atrás, em Denver, terminou a próxima, já na quinta vez consecutiva, a maior conferência sobre a Go - GopherCon . Nele, a equipe do Go fez uma declaração importante - os rascunhos do design preliminar do novo tratamento de erros e genéricos no Go 2 são publicados e todos são convidados a discutir.
Tentarei recontar em detalhes a essência desses rascunhos em três artigos.
Como muitas pessoas provavelmente sabem, no ano passado (também na GopherCon), a equipe do Go anunciou que estava coletando relatórios ( relatos de experiência ) e sugestões para solucionar os principais problemas do Go - os pontos mais criticados pelas pesquisas. Durante o ano, todas as propostas e relatórios foram estudados e considerados e ajudaram na criação de esboços, que serão discutidos.
Então, vamos começar com os rascunhos do novo mecanismo de tratamento de erros .
Para começar, uma pequena digressão:
- Go 2 é um nome condicional - todas as inovações farão parte do processo normal de lançamento das versões do Go. Portanto, ainda não se sabe se será o Go 1.34 ou o Go2. O script Python 2/3 não será de ferro.
- Rascunhos de design nem sequer são propostas , com as quais qualquer alteração na biblioteca, no ajuste ou no idioma Go começa. Este é o ponto de partida para a discussão de design proposta pela equipe Go após vários anos de trabalho nessas questões. Tudo o que é descrito nos rascunhos com um alto grau de probabilidade será alterado e, na melhor das hipóteses, só se tornará realidade após alguns lançamentos (dou ~ 2 anos).
Qual é o problema com o tratamento de erros no Go?
Go inicialmente tomou a decisão de usar a verificação de erro "explícita", em oposição à verificação "implícita" popular em outros idiomas - exceções. O problema com a verificação implícita de erros é como é descrito em detalhes no artigo “Mais limpo, mais elegante e não mais correto” , o que é muito difícil de entender visualmente se o programa se comportar corretamente no caso de certos erros.
Veja um exemplo de um Go hipotético com exceções:
func CopyFile(src, dst string) throws error {
    r := os.Open(src)
    defer r.Close()
    w := os.Create(dst)
    io.Copy(w, r)
    w.Close()
}
, . : io.Copy w.Close , .
, Go :
func CopyFile(src, dst string) error {
    r, err := os.Open(src)
    if err != nil {
        return err
    }
    defer r.Close()
    w, err := os.Create(dst)
    if err != nil {
        return err
    }
    defer w.Close()
    if _, err := io.Copy(w, r); err != nil {
        return err
    }
    if err := w.Close(); err != nil {
        return err
    }
}
, , , –  . , , – " ", - , , , .
, ( , , ..) , .
, Go . :
func CopyFile(src, dst string) error {
    r, err := os.Open(src)
    if err != nil {
        return fmt.Errorf("copy %s %s: %v", src, dst, err)
    }
    defer r.Close()
    w, err := os.Create(dst)
    if err != nil {
        return fmt.Errorf("copy %s %s: %v", src, dst, err)
    }
    if _, err := io.Copy(w, r); err != nil {
        w.Close()
        os.Remove(dst)
        return fmt.Errorf("copy %s %s: %v", src, dst, err)
    }
    if err := w.Close(); err != nil {
        os.Remove(dst)
        return fmt.Errorf("copy %s %s: %v", src, dst, err)
    }
}
, .
Go Go 2:
Go.
.
- check(x,y,z)- check err
- handle– ,
check , handle ( handler, , . , return)
:
func CopyFile(src, dst string) error {
    handle err {
        return fmt.Errorf("copy %s %s: %v", src, dst, err)
    }
    r := check os.Open(src)
    defer r.Close()
    w := check os.Create(dst)
    handle err {
        w.Close()
        os.Remove(dst) // (  check )
    }
    check io.Copy(w, r)
    check w.Close()
    return nil
}
, ( main). :
func main() {
    hex, err := ioutil.ReadAll(os.Stdin)
    if err != nil {
        log.Fatal(err)
    }
    data, err := parseHexdump(string(hex))
    if err != nil {
        log.Fatal(err)
    }
    os.Stdout.Write(data)
}
:
func main() {
    handle err {
        log.Fatal(err)
    }
    hex := check ioutil.ReadAll(os.Stdin)
    data := check parseHexdump(string(hex))
    os.Stdout.Write(data)
}
, . :
func printSum(a, b string) error {
    x, err := strconv.Atoi(a)
    if err != nil {
        return err
    }
    y, err := strconv.Atoi(b)
    if err != nil {
        return err
    }
    fmt.Println("result:", x + y)
    return nil
}
:
func printSum(a, b string) error {
    handle err { return err }
    x := check strconv.Atoi(a)
    y := check strconv.Atoi(b)
    fmt.Println("result:", x + y)
    return nil
}
:
func printSum(a, b string) error {
    handle err { return err }
    fmt.Println("result:", check strconv.Atoi(x) + check strconv.Atoi(y))
    return nil
}
check handle.
Check
check ( ) , "" error, , . nil, check (handler), return .
:
v1, ..., vN := check <>
:
v1, ..., vN, vErr := <>
if vErr != nil {
    <error result> = handlerChain(vn)
    return
}
vErr error <error result> , .
,
foo(check <>)
:
v1, ..., vN, vErr := <>
if vErr != nil {
    <error result> = handlerChain(vn)
    return
}
foo(v1, ..., vN)
Check try
try check – /, , , Rust Swift try ( Rust ? ).
try :
data := try parseHexdump(string(hex))
:
data, err := parseHexdump(string(hex))
if err == ErrBadHex {
    ... special handling ...
}
try err
, try c . check/handle , check .
Handle
handle , "" (handler), , check. (return) . ( , return) ( func foo() (bar int, err error)).
, " " – , , , error , , . :
func handler(err error) error {...}
( , , –  ).
–  , . (check) , , . , , –  . :
func process(user string, files chan string) (n int, err error) {
    handle err { return 0, fmt.Errorf("process: %v", err)  }      // handler A
    for i := 0; i < 3; i++ {
        handle err { err = fmt.Errorf("attempt %d: %v", i, err) } // handler B
        handle err { err = moreWrapping(err) }                    // handler C
        check do(something())  // check 1: handler chain C, B, A
    }
    check do(somethingElse())  // check 2: handler chain A
}
check 1 C, B A – . check 2 A, C B for-.
, . if , (handle) ( ) , – , , :
type Error struct {
    Func string
    User string
    Path string
    Err  error
}
func (e *Error) Error() string
func ProcessFiles(user string, files chan string) error {
    e := Error{ Func: "ProcessFile", User: user}
    handle err { e.Err = err; return &e } // handler A
    u := check OpenUserInfo(user)         // check 1
    defer u.Close()
    for file := range files {
        handle err { e.Path = file }       // handler B
        check process(check os.Open(file)) // check 2
    }
    ...
}
, handle defer, , , . – , . , handler B –  defer , . Go defer/panic handle/check , , -.
– (.. return), - . .
(panic) , .
-
– (handle err {}). " -" (default handler). handle , , -, , check ( ; — zero values).
-:
func printSum(a, b string) error {
    x := check strconv.Atoi(a)
    y := check strconv.Atoi(b)
    fmt.Println("result:", x + y)
    return nil
}
Go , . - , , . , t.Helper() , :
func TestFoo(t *testing.T) {
    handle err {
        t.Helper()
        t.Fatal(err)
    }
    for _, tc := range testCases {
        x := check Foo(tc.a)
        y := check Foo(tc.b)
        if x != y {
            t.Errorf("Foo(%v) != Foo(%v)", tc.a, tc.b)
        }
    }
}
(shadowing)
check (:=), err. handle/check .
defer/panic
(defer/panic handle/check) . .
handle defer (, , , ), handle/check defer-. :
func Greet(w io.WriteCloser) error {
    defer func() {
        check w.Close()
    }()
    fmt.Fprintf(w, "hello, world\n")
    return nil
}
, .
Go –  , . - "", , defer, break goto. , goto, , .
try, catch, ? , . , Go , check handle .
, handle catch , ( , (keywords) ).
Go2?
. Go, 2-3 , – . , 2-3 .
, , Go2 –  . , Go – Go 1.20 . .
, ?
. / . , , , Go.
Go 2 – , if err != nil {} , handle/check?
, , if err , –  , . , .
?  , Go .
. , .
?
, , . , - . , , , , .
, ! ?
- Go2ErrorHandlingFeedback
- Go —  handle/check
- -
- , ,
- / ( defer/panic)
? ?