接口作为值存储;方法无法更新结构体字段。

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

Interfaces stored as value; Methods unable to update struct fields

问题

我有一个工具,正在编写一个将一些从静态数据库中提取信息的函数暴露给我嵌入到工具中的几种脚本语言的工具。

我想:“嗨,听起来是接口的一个很好的用例”。所以我在我的包 scripting 中定义了一个接口,如下所示:

  1. type ScriptingLang interface {
  2. RunScript(filename string) error
  3. RunString(s string) error
  4. Interpreter() error
  5. Init() error
  6. IsInit() bool
  7. }

然后我存储了一个以不同包中定义的字符串为键的映射,以便可以通过字符串查找它们。

  1. var ScriptingLangs = make(map[string]scripting.ScriptingLang)

还有一个用于注册它们的函数,以及一些小的辅助函数,例如:

  1. func RunString(lang, s string) error {
  2. if v, ok := ScriptingLangs[lang]; ok {
  3. if !v.IsInit() {
  4. v.Init()
  5. }
  6. return v.RunString(s)
  7. } else {
  8. return NoSuchLangErr
  9. }
  10. return nil
  11. }

我遇到的问题是接口似乎不能有带指针接收器的方法。因此,我的实现 ScriptingLang 的 Lua 结构无法保存其 *state,因为它存储在 ScriptingLangs 中。

我尝试在保存状态的函数末尾更新映射中存储的值,但它没有更新该值。

据我了解,不应该使用接口的指针,那么在这里我有哪些选择呢?我真的想保留接口,以便可以在 git 子模块中做一些有趣的事情。

这是我问题的一个最小示例:

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. type ScriptingLang interface {
  6. DoString(s string) error
  7. Init() error
  8. }
  9. type Lua struct {
  10. state string
  11. }
  12. func (l Lua) DoString(s string) error {
  13. fmt.Printf("Doing '%v' with state '%v'\n", s, l.state)
  14. return nil
  15. }
  16. func (l Lua) Init() error {
  17. l.state = "Inited"
  18. return nil
  19. }
  20. var lang ScriptingLang
  21. func main() {
  22. lang = Lua{}
  23. lang.Init()
  24. lang.DoString("Stuff")
  25. }

请注意,这只是一个示例,用于说明问题。

英文:

I have a tool that I'm writing that exposes some functions that pull information out of a static database to several scripting languages that I'm embedding into the tool.

I thought; "Hey sounds like a nice use case for interfaces". So I defined an interface like so in my package scripting

  1. type ScriptingLang interface {
  2. RunScript(filename string) error
  3. RunString(s string) error
  4. Interpreter() error
  5. Init() error
  6. IsInit() bool
  7. }

Then I store a map of them so I can look them up by a string defined like so in a different package.

  1. var ScriptingLangs = make(map[string]scripting.ScriptingLang)

and a function to register them. Also some little helper functions like

  1. func RunString(lang, s string) error {
  2. if v, ok := ScriptingLangs[lang]; ok {
  3. if !v.IsInit() {
  4. v.Init()
  5. }
  6. return v.RunString(s)
  7. } else {
  8. return NoSuchLangErr
  9. }
  10. return nil
  11. }

The problem that I ran into is it seams that interfaces can't have methods with pointer receivers. As a result my Lua struct that implements ScriptingLang isn't able to save it's *state because it's stored in ScriptingLangs.

I've tried updating the value stored in the map at the end of functions that save state and it didn't update the value.

To my understanding you shouldn't use pointers of interfaces so what are my options here? I would like to really keep the interfaces so I can do some neat stuff with git submodules.

A minimal example of my problem:

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. type ScriptingLang interface {
  6. DoString(s string) error
  7. Init() error
  8. }
  9. type Lua struct {
  10. state string
  11. }
  12. func (l Lua) DoString(s string) error {
  13. fmt.Printf("Doing '%v' with state '%v'\n", s, l.state)
  14. return nil
  15. }
  16. func (l Lua) Init() error {
  17. l.state = "Inited"
  18. return nil
  19. }
  20. var lang ScriptingLang
  21. func main() {
  22. lang = Lua{}
  23. lang.Init()
  24. lang.DoString("Stuff")
  25. }

答案1

得分: 7

如果你想要改变状态,你需要一个指针接收器,并且你的Init方法没有一个。将值存储在接口中并不会有任何区别。

在你的简化示例中,将Init方法(以及任何更新状态的方法)更改为具有指针接收器,并在接口内部指向一个指针,这样一切都能正常工作:

  1. func (l *Lua) Init() error {
  2. l.state = "Inited"
  3. return nil
  4. }
  5. ...
  6. func main() {
  7. lang = &Lua{}
  8. lang.Init()
  9. lang.DoString("Stuff")
  10. }

这篇文章可能会有所帮助:http://jordanorelli.com/post/32665860244/how-to-use-interfaces-in-go

英文:

If you want to mutate state, you need a pointer receiver, and your Init method doesn't have one. The fact that you're storing the value inside an interface makes no difference.

In your minimal(-ish) example, change the Init method (and any method that updates state) to have a pointer receiver, and point a pointer inside the interface and everything works:

  1. func (l *Lua) Init() error {
  2. l.state = "Inited"
  3. return nil
  4. }
  5. ...
  6. func main() {
  7. lang = &Lua{}
  8. lang.Init()
  9. lang.DoString("Stuff")
  10. }

This article might help: http://jordanorelli.com/post/32665860244/how-to-use-interfaces-in-go

huangapple
  • 本文由 发表于 2015年5月5日 11:55:39
  • 转载请务必保留本文链接:https://go.coder-hub.com/30043995.html
匿名

发表评论

匿名网友

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

确定