
Il y a quelques jours à Denver, la prochaine, déjà 5e de suite, la plus grande conférence sur Go - GopherCon s'est terminée . L'équipe de Go a fait une déclaration importante - des ébauches de la conception préliminaire du nouveau traitement des erreurs et des génériques dans Go 2 sont publiées , et tout le monde est invité à en discuter.
Je vais essayer de raconter en détail l'essence de ces projets dans trois articles.
Comme beaucoup le savent probablement, l'année dernière (également à GopherCon), l'équipe Go a annoncé qu'elle collectait des rapports ( rapports d'expérience ) et des suggestions pour résoudre les principaux problèmes de Go - les points les plus critiqués par les sondages. Au cours de l'année, toutes les propositions et tous les rapports ont été étudiés et examinés, et ont aidé à créer des projets de conception, qui seront discutés.
Commençons donc par les ébauches du nouveau mécanisme de gestion des erreurs .
Pour commencer, une petite digression:
- Go 2 est un nom conditionnel - toutes les innovations feront partie du processus normal de publication des versions Go. On ne sait donc toujours pas si ce sera Go 1.34 ou Go2. Le script Python 2/3 ne sera pas ironique.
- Les ébauches de conception ne sont même pas des propositions , avec lesquelles tout changement dans la bibliothèque, le réglage ou la langue Go commence. C'est le point de départ de la discussion de conception proposée par l'équipe Go après plusieurs années de travail sur ces questions. Tout ce qui est décrit dans les ébauches avec un haut degré de probabilité sera changé et, dans le meilleur des cas, ne deviendra réalité qu'après quelques versions (je donne environ 2 ans).
Quel est le problème avec la gestion des erreurs dans Go?
Go a initialement pris la décision d'utiliser la vérification d'erreur "explicite", par opposition à la vérification "implicite" populaire dans d'autres langues - exceptions. Le problème avec la vérification d'erreur implicite est de savoir comment elle est décrite en détail dans l'article «Plus propre, plus élégant et pas plus correct» , ce qui est très difficile à comprendre visuellement si le programme se comporte correctement en cas de certaines erreurs.
Prenons un exemple de Go hypothétique avec des exceptions:
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
)
? ?