web-dev-qa-db-ja.com

Golangの混合割り当てと宣言

私はgoで数週間作業を始めましたが、(もう一度)奇妙に思える何かに偶然出会いました。

// Not working
a := 1
{
    a, b := 2, 3
}

// Works
a := 1
a, b := 2, 3

遊び場

2つの変数を同時に割り当てたい。 1つはすでに宣言されていますが、上位のスコープではもう1つは宣言されていません。

これは機能しません。コンパイラは前の変数を再宣言しようとします。ただし、この変数が同じスコープで宣言されている場合は問題なく機能します。

何故ですか?

19
Procrade

あなたが経験しているものは、一般的に "variable shadowing" として知られています。中括弧がないにもかかわらず、ifforなどのステートメントを含め、内部スコープ内の任意の変数で:=を使用すると、新しい値と型がその変数に関連付けられます。

n := "Example"
//Prints the string variable `n` to standard output and
// returns the number of bytes written in int variable `n` and
// an error indicator in error variable `err`.
if n, err := fmt.Println(n); err != nil {
    panic(err)
} else {
    fmt.Println(n, "bytes written")
}

//Prints the string variable `n` to standard output.
fmt.Printf("n = %q\n", n)

出力:

Example
8 bytes written
n = "Example"

この問題を修正するには、いくつかの方法があります。

  • 使用する前に必要な変数を宣言し、=で通常の割り当てを使用します
  • 異なる変数名を使用する
  • 新しいスコープを作成し、後でアクセスできるように変数の値を保存します。必要に応じて変数名を:=で使用し、スコープが終了する前に値を復元します。とにかく別の変数を作成しているので、通常は異なる変数名を使用する方が簡単です。

反対の効果も発生する可能性があり、内部スコープで何かを宣言し、それを認識しません。

if _, err := fmt.Println(n); err != nil {
    panic(err)
} else {
    fmt.Println(n, "bytes written")
}

//undefined: err
if _, err = fmt.Println(n); err != nil {
    //undefined: err
    panic(err)
}

この問題を修正するには、いくつかの方法があります。

  • 使用する前に必要な変数を宣言し、=で通常の割り当てを使用します
  • 最初の:=およびifステートメントを分離して、変数が意図したとおりに宣言されるようにします。これにより、そのスコープおよびそれが囲まれているスコープのコンテキストで、その変数の他のすべてのインスタンスに=を使用できます
  • エラーを修正するには、=のすべてのインスタンスを:=に変更します

関数が複数の値を返す場合、最後の2つのケースのいずれかで変数のシャドウイングの問題が発生する可能性がありますが、上記で説明したように解決できます。

Go Playgroundで両方の例を試してください。

最後の例は、新しい変数bを宣言して初期化すると同時に、既存の変数aに値を割り当てることを示しています。新しいスコープは作成されないので、元の変数aをシャドウすることはありません。これは 各割り当ての後(ただし次の宣言の前)にaのアドレスを出力して確認します) /割り当て)

a := 1
fmt.Println(&a)
a, b := 2, 3
fmt.Println(&a)
a = b          // avoids a "declared but not used" error for `b`

もちろん、bを宣言しなかった場合、2番目の宣言である:=の左側に新しい変数がないというエラーがコンパイラーから返されます。同じスコープ内でaを2回宣言しようとしていることを示す遠回りの方法。

このアイデアは、注意深く適用すると、シャドウされている変数を見つけるためにも使用できることに注意してください。たとえば、例の「機能しない」コードは、内部スコープ内のaがまだ宣言されているかどうかに応じて、 aの異なるアドレスを出力する になります。ない:

a := 1
{
    fmt.Println(&a)    // original `a`
    a, b := 2, 3
    fmt.Println(&a)    // new `a`
    a = b              // avoids a "declared but not used" error for `b`
}
fmt.Println(&a)        // original `a`
12
user539810

golangドキュメント によると:

ブロックで宣言された識別子は、内部ブロックで再宣言できます。

それはまさにあなたの例が示しているものであり、aは ':='のために括弧内で再宣言され、決して使用されません。

解決策は、両方の変数を宣言してから使用することです。

var a, b int
{
    b, a = 2, 3
    fmt.Println(b)
}
fmt.Println(a)
7
jnmoal

あなたの質問には2つの部分があります:
最初の部分:
=は単なる割り当てです
:=は、関数ブロック(グローバルではない)内の新しい変数(少なくとも1つの新しい変数)を定義し、割り当てます。作業サンプル:

package main

import (
    "fmt"
)

func main() {
    var u1 uint32      //declare a variable and init with 0
    u1 = 32            //assign its value
    var u2 uint32 = 32 //declare a variable and assign its value at once
    //declare a new variable with defining data type:
    u3 := uint32(32)        //inside the function block this is equal to: var u3 uint32 = 32
    fmt.Println(u1, u2, u3) //32 32 32
    //u3 := 20//err: no new variables on left side of :=
    u3 = 20
    fmt.Println(u1, u2, u3)       //32 32 20
    u3, str4 := 100, "str"        // at least one new var
    fmt.Println(u1, u2, u3, str4) //32 32 100 str
}

後編:
ブロックで宣言された識別子は、内部ブロックで再宣言される場合があります。
ここでは、変数のスコープとシャドウイングの4つの異なる作業サンプルを示します。

変数のスコープを制限する簡単な方法:

package main
import "fmt"
func main() {
    i := 1
    j := 2
    //new scope :
    {
        i := "hi" //new local var
        j++
        fmt.Println(i, j) //hi 3
    }
    fmt.Println(i, j) //1 3
}

関数呼び出しを使用して変数のスコープを制限します。

package main
import "fmt"
func fun(i int, j *int) {
    i++                //+Nice: use as local var without side effect
    *j++               //+Nice: intentionally use as global var
    fmt.Println(i, *j) //11 21
}
func main() {
    i := 10 //scope: main
    j := 20
    fun(i, &j)
    fmt.Println(i, j) //10 21
}

ステートメント内での省略代入を使用する:

package main
import "fmt"
func main() {
    i := 10 //scope: main
    j := 4
    for i := 'a'; i < 'b'; i++ {
        fmt.Println(i, j) //97 4
    }
    fmt.Println(i, j) //10 4

    if i := "test"; len(i) == j {
        fmt.Println(i, j) // i= test , j= 4
    } else {
        fmt.Println(i, j) //test 40
    }
    fmt.Println(i, j) //10 4
}

グローバル変数のシャドウイング:

package main
import "fmt"
var i int = 1 //global
func main() {
    j := 2
    fmt.Println(i, j) //1 2
    i := 10           //Shadowing global var
    fmt.Println(i, j) //10 2
    fun(i, j)         //10 2
}
func fun(i, j int) {
    //i := 100   //no new variables on left side of :=
    fmt.Println(i, j) //10 2
}
2
user6169399