Skip to content

Instantly share code, notes, and snippets.

@iporsut
Created June 23, 2023 09:41
Show Gist options
  • Save iporsut/4685fac0f843b8a6840a8b490f412cfd to your computer and use it in GitHub Desktop.
Save iporsut/4685fac0f843b8a6840a8b490f412cfd to your computer and use it in GitHub Desktop.

Copy semantic

Go is language that use copy value semantic (no reference semantic like C++ or Rust).

Assignment Statement Copy Value from Right to Left

        num := 10 // copy ค่า 10 ลงหน่วยความจำที่ชื่อไว้ว่า num
        num = 20 // copy ค่า 20 ลงหน่วยความจำที่ชื่อไว้ว่า num

        num2 := num // copy ค่าจาก num ลงหน่วยความจำที่ชื่อว่า num2

        num = 30
        fmt.Println(num2) // ยังคงเป็น 20 เพราะเป็นตัวแปรคนละตัว

Scope of variable

func main() {
        num := 20 // ตั้งแต่ประกาศตัวแปร
        fmt.Println(num) // ใช้ใน
} // จนสิ้นสุด การทำงานของ ฟังก์ชัน

Passing value to when call function , Copy value from caller to function parameter

func sayHello(nameParameter string) { // parameter variable เป็นตัวแปรใน scope ของ sayHello function
        fmt.Println("Hello, ", nameParameter)
}

func main() {
        name := "Por" // ตัวนี้เป็นตัวแปรใน scope ของ main function
        sayHello(name) // ทำงานเหมือนกับ nameParameter = name คือ copy ค่าจาก name ใน scope ของ main ให้กับ nameParameter ใน scope ของ sayHello
}
func setNamePrefix(name string) {
        name = "Mr. " + name
}

func main() {
        name := "Por"
        setNamePrefix(name)
        fmt.Println(name) // Por , ไม่เปลี่ยน เพราะเกิดการ copy ค่าไปแล้ว ตัวแปร name คนละ scope กัน ก็คือตัวแปรคนละตัวนั่นละ เปลี่ยนอีกตัวใน setNamePrefix ไม่กระทบกับอีกตัวที่อยู่ใน main
}

How to share an access to same variable to other scope in Go

No the direct way to share variable in Go, but it has the indirect way to access same memory location through pointer type

        num := 10
        ptrNum := &num // ใช้ operator `&` เพื่อเอา memory location (address) กำหนดในตัวแปร ptrNum ซึ่งจะมี type เป็น *int (pointer ของ int)

        // แบบนี้ไม่ได้นะ ptrNum ไม่ใช่ตัวแปรตัวเลข แต่เป็น address ของ int
        ptrNum = 20

        // ต้องใช้ แบบนี้ เรียกว่า dereference (`*` คือ dereference operator) เพื่อกำหนดค่าใน memory location ที่อยู่ใน ptrNum
        *ptrNum = 20

        fmt.Println(num) // 20, เปลี่ยนเป็น 20 ด้วยเพราะใช้ deference operator ทำให้เปลี่ยนค่าใน memory location ที่เก็บอยู่ได้
func setNamePrefix(ptrName *string) {
        *ptrName = "Mr. " + *ptrName
}

func main() {
        name := "Por"
        setNamePrefix(&name) // ใช้ operator `&` ทำให้เป็นการ copy location ของ name ซึ่งก็คือ ptrName = &name ให้กับตัวแปร ptrName
        fmt.Println(name) // Mr. Por , เปลี่ยนได้แล้วเพราะในฟังก์ชัน setNamePrefix ใช้ dereference operator แก้ไขค่าใน location เดียวกัน
}

Use same memory location after variable end of scope

type Todo struct {
        ID          string
        Description string
}

func createTodo(id string, description string) Todo {
        todo := Todo { // ตัวแปร todo อยู่ใน scope ของ createTodo
                ID: id,
                Description: description,
        }

        return todo // จุดนี้คือการ copy ค่าของ todo ออกไป เพราะจะเอา todo ออกไปเลยไม่ได้เนื่องจากมันจะหายไปหลังจากจบ scope
} // สิ้นสุด scope ตรงนี้

func main() {
        tado := createTodo("1", "ซักผ้า") // tado คือของที่ copy มาจากตอน return todo
}

แต่ถ้าเราไม่ต้องการ copy ค่าล่ะ อยากได้ memory location เดิมที่สร้างไว้กับ ตัวแปร todo ใน createTodo เอาไว้แล้ว เราทำตรงๆไม่ได้ แต่ก็ทำได้อ้อมๆโดยใช้ pointer ช่วย

type Todo struct {
        ID          string
        Description string
}

func createTodo(id string, description string) Todo {
        todo := Todo { // ตัวแปร todo อยู่ใน scope ของ createTodo
                ID: id,
                Description: description,
        }

        return &todo // จุดนี้คือการ copy ค่าของ memory location ของ todo ไปแทน (ซึ่งก็คือตัวแปร pointer ของ todo `type *Todo` นั่นเอง)
} // สิ้นสุด scope ตรงนี้ แต่ว่า memory location เดียวกันยังอ้างอิงได้จาก pointer ของ todo ที่ return กลับไป

func main() {
        tado := createTodo("1", "ซักผ้า") // tado คือ pointer ของ todo แล้ว (type *Todo) ไม่ใช่ todo โดยตรง แต่อ้างอิงถึง memory location เดิมที่ถูกสร้างใน createTodo นั่นแหละได้ผ่าน dereference operator

        fmt.Println(*todo) // {1, ซักผ้า}
}

Pointer of struct can access field without using dereference operator

type Todo struct {
        ID          string
        Description string
}

func main() {
        num := 10
        ptrNum := &num

        // ต้องใช้ dereference operator เมื่อเปลี่ยนค่าของ memory ที่ ptrNum ชี้อยู่
        *prtNum = 20
        fmt.Println(num) // 20

        todo := Todo{"1", "ซักผ้่า"}
        ptrTodo := &todo

        // ไม่ต้องทำแบบนี้ก็ได้
        id := (*ptrNum).ID
        desc := (*ptrNum).Description

        // ใช้แบบนี้พอ เหมือน type Todo ปกติ
        id := todo.ID
        desc := todo.Description
        fmt.Println(id, description) // 1 ซักผ้า

        id := ptrTodo.ID
        desc := ptrTodo.Description
        fmt.Println(id, description) // 1 ซักผ้า

        ptrTodo.ID = 2
        fmt.Println(todo) // {2, ซักผ้า} , todo เปลี่ยนด้วย เพราะแชร์ memory location กันอยู่

        // หรือถ้าจะเปลี่ยนทั้งก้อนเลยไม่ใช่แค่บาง field ยังต้องใช้ dereference operator อยู่เช่น
        *ptrTodo = Todo{"2", "ล้างจาน"}
        fmt.Println(todo) // {2 ล้างจาน}, todo เปลี่ยนด้วย

        // แต่แบบนี้ไม่ได้นะ มันคือเปลี่ยน memory location ให้ ptrTodo แชร์กับตัวอื่นแทน ไม่ใช่เปลี่ยนค่าที่แชร์อยู่เดิม
        todo := Todo{"1", "ซักผ้่า"}
        ptrTodo := &todo
        ptrTodo = &Todo{"2", "ล้างจาน"} // เปลี่ยนไปแชร์ memory กับ location อื่นแล้ว
        fmt.Println(todo) // {1, ซักผ้า} todo ยังเป็นค่าเดิม
}

When we need to share memory location?

  • When we design a function to read/update partial of complex data structure.
type Config struct {
        // it has many fields
}

func ProcessingData(c *Config) {
        if c.Flag {

        }
}

type Stat struct {
}

func Count(s *Stat) {
        s.Counter++
}
  • When we design a function to create new complex data structure.
type Config struct {
        // it has many fields
}

func LoadConfig() *Config {
        var conf Config
        // has many logic to create Config

        return &conf
}
  • Method receiver is just an another parameter.
type Config struct {
        // has many fields
}

func (c *Config) Flag() bool {
        return c.flag
}

func main() {
        conf := LoadConfig()

        if conf.Flag() { // call method Flag by passing &conf to (c *Config) automatically
        }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment