为什么通过HTTP传输结构会创建一个副本?

huangapple go评论115阅读模式
英文:

Why does serving a struct via http create a copy?

问题

我注意到在Go语言中创建一个结构体并在其New函数中注册一个http处理程序时出现了一些奇怪的行为。

考虑以下代码:

  1. package main
  2. import (
  3. "fmt"
  4. "net/http"
  5. )
  6. type Counter struct {
  7. name string
  8. value int
  9. }
  10. func New(name string) Counter {
  11. c := Counter{
  12. name: name,
  13. value: 0,
  14. }
  15. http.HandleFunc("/", c.serve)
  16. return c
  17. }
  18. func (c *Counter) inc() { c.value++ }
  19. func (c *Counter) reset() { c.value = 0 }
  20. func (c *Counter) nameApp(n string) { c.name += n }
  21. func (c *Counter) print() { fmt.Printf("[%s]: %d (%p)\n", c.name, c.value, &c) }
  22. func (c *Counter) Reinit(name string, value int) {
  23. c.name = name
  24. c.value = value
  25. }
  26. func (c *Counter) serve(w http.ResponseWriter, req *http.Request) {
  27. c.inc()
  28. c.nameApp("-foo")
  29. fmt.Println("Counter served:")
  30. c.print()
  31. w.WriteHeader(http.StatusOK)
  32. w.Write([]byte{})
  33. }
  34. func main() {
  35. c := New("My New Counter")
  36. fmt.Println("New Counter:")
  37. c.print()
  38. c.Reinit("My reinit Counter", 10)
  39. fmt.Println("Counter after Reinit() call:")
  40. c.print()
  41. http.ListenAndServe("localhost:9000", nil)
  42. }

运行代码后,输出如下:

  1. New Counter:
  2. [My New Counter]: 0 (0xc00012a2a0)
  3. Counter after Reinit() call:
  4. [My reinit Counter]: 10 (0xc00012a2a0)

发送两个请求到服务器后,输出如下:

  1. Counter served:
  2. [My New Counter-foo]: 1 (0xc00012a2c0) // 期望输出为"[My reinit Counter]: 11 (0xc00012a2a0)"
  3. Counter served:
  4. [My New Counter-foo-foo]: 2 (0xc00012a2c0) // 期望输出为"[My reinit Counter]: 12 (0xc00012a2a0)"

为什么结构体的行为不符合我的预期,即使我正确使用了指针接收器?

如何修改我的结构体,使其能够从主程序或其他任何程序中进行修改,并反映在关联的http请求处理程序中?

英文:

I noticed some strange behaviour when I create a struct in go that registers an http-Handler in its New function.

Consider the following code:

  1. package main
  2. import (
  3. "fmt"
  4. "net/http"
  5. )
  6. type Counter struct {
  7. name string
  8. value int
  9. }
  10. func New(name string) Counter {
  11. c := Counter{
  12. name: name,
  13. value: 0,
  14. }
  15. http.HandleFunc("/", c.serve)
  16. return c
  17. }
  18. func (c *Counter) inc() { c.value++ }
  19. func (c *Counter) reset() { c.value = 0 }
  20. func (c *Counter) nameApp(n string) { c.name += n }
  21. func (c *Counter) print() { fmt.Printf("[%s]: %d (%p)\n", c.name, c.value, &c) }
  22. func (c *Counter) Reinit(name string, value int) {
  23. c.name = name
  24. c.value = value
  25. }
  26. func (c *Counter) serve(w http.ResponseWriter, req *http.Request) {
  27. c.inc()
  28. c.nameApp("-foo")
  29. fmt.Println("Counter served:")
  30. c.print()
  31. w.WriteHeader(http.StatusOK)
  32. w.Write([]byte{})
  33. }
  34. func main() {
  35. c := New("My New Counter")
  36. fmt.Println("New Counter:")
  37. c.print()
  38. c.Reinit("My reinit Counter", 10)
  39. fmt.Println("Counter after Reinit() call:")
  40. c.print()
  41. http.ListenAndServe("localhost:9000", nil)
  42. }

When running it it creates the following output:

  1. New Counter:
  2. [My New Counter]: 0 (0xc00012a2a0)
  3. Counter after Reinit() call:
  4. [My reinit Counter]: 10 (0xc00012a2a0)

After sending two requests to the server the output is:

  1. Counter served:
  2. [My New Counter-foo]: 1 (0xc00012a2c0) // expected "[My reinit Counter]: 11 (0xc00012a2a0)"
  3. Counter served:
  4. [My New Counter-foo-foo]: 2 (0xc00012a2c0) // expected "[My reinit Counter]: 12 (0xc00012a2a0)"

Why does the struct not behave as I had expected, even though I am properly using pointer receivers?

How can I modify my struct from a main routine or really any other routine to and have these changes being reflected in the associated http request handlers?

答案1

得分: 2

  1. func (c *Counter) print()

中,你正在打印

  1. fmt.Printf("[%s]: %d (%p)\n", c.name, c.value, &c)

值得注意的是,你通过%p打印了&c
c是该方法的指针接收器参数。&c是指向指针接收器的指针。换句话说,&c是指向局部变量的指针,即接收器变量。如果你只想打印调用该方法的Counter的地址,请使用c。例如:

  1. fmt.Printf("[%s]: %d (%p)\n", c.name, c.value, c)
英文:

In

  1. func (c *Counter) print()

You are printing

  1. fmt.Printf("[%s]: %d (%p)\n", c.name, c.value, &c)

Most notably, you are printing &c via %p.
c is the pointer receiver argument to the method. &c is a pointer to the pointer receiver. In other words, &c is a pointer to a local variable, the receiver variable. If you want to print just the address of the Counter which was called on, use the plain c. For example:

  1. fmt.Printf("[%s]: %d (%p)\n", c.name, c.value, c)

答案2

得分: 1

在撰写问题时找到了解决方案:

新函数的返回值需要是一个指针,否则它将返回一个副本。以下更改解决了这个问题:

  1. func New(name string) *Counter { // 添加 *
  2. // ... 之前的代码
  3. return &c // 添加 &
  4. }

(但是,我真的不明白的是,为什么初始输出中的地址保持不变!?)

英文:

Found the solution while writing up the question:

The return value of the new function needs to be a pointer, otherwise it will return a copy. The following change solves the problem:

  1. func New(name string) *Counter { // add the *
  2. // ... as before
  3. return &c // add the &
  4. }

(What I don't really get, though, is why the address in initial output stays the same!?)

huangapple
  • 本文由 发表于 2022年7月29日 18:51:56
  • 转载请务必保留本文链接:https://go.coder-hub.com/73165556.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定