【问题标题】:Wrapping a pointer in Go在 Go 中包装一个指针
【发布时间】:2018-07-29 18:08:19
【问题描述】:

foo 公开了一个类型A,而该库中的一个函数Fn 返回一个*A

我为A 定义了一个名为B 的“包装器”:

type B foo.A

我可以在不取消引用 A 的情况下将 *A 转换为 *B 吗?

换句话说,如果我有

a := foo.Fn()   // a is a *A
b := B(*a)
return &b

如何在不使用*a 的情况下将*a 转换为*b

我问的原因是,在我正在使用的库中,github.com/coreos/bbolt*DB 函数返回的 *DB 值包含一个 sync.Mutex,所以当我尝试制作一个Mutex 的副本。

更新说明我将如何使用它

我有一个

type Datastore struct {
    *bolt.DB
}

我也有这样的功能(其中之一):

func (ds *Datastore) ReadOne(bucket, id string, data interface{}) error {
    return ds.View(func(tx *bolt.Tx) error {
        b, err := tx.CreateBucketIfNotExists([]byte(bucket))
        if err != nil {
            return fmt.Errorf("opening bucket %s: %v", bucket, err)
        }

        bytes := b.Get([]byte(id))
        if bytes == nil {
            return fmt.Errorf("id %s not found", id)
        }

        if err := json.Unmarshal(bytes, data); err != nil {
            return fmt.Errorf("unmarshalling item: %v", err)
        }

        return nil
    })
}

我想使用哈希映射来模拟底层的 BoltDB 数据库。我遇到了一个问题,因为 View 期待一个接受 bolt.Tx 的函数。然后使用txCreateBucketIfNotExists 中创建一个新存储桶。我无法用调用我的哈希映射模拟版本CreateBucketIfNotExists 的参数替换该匿名函数参数。

我想出了这个:

package boltdb

import (
    "github.com/coreos/bbolt"
)

type (
    bucket bolt.Bucket

    // Bucket is a wrapper for bolt.Bucket to facilitate mocking.
    Bucket interface {
        ForEach(fn func([]byte, []byte) error) error
        Get(key []byte) []byte
        NextSequence() (uint64, error)
        Put(key, value []byte) error
    }

    db bolt.DB

    // DB is a wrapper for bolt.DB to facilitate mocking.
    DB interface {
        Close() error
        Update(fn func(*Tx) error) error
        View(fn func(*Tx) error) error
    }

    transaction bolt.Tx

    // Tx is a wrapper for bolt.Tx to facilitate mocking.
    Tx interface {
        CreateBucketIfNotExists(name []byte) (Bucket, error)
    }
)

// ForEach executes a function for each key/value pair in a bucket.
func (b *bucket) ForEach(fn func([]byte, []byte) error) error {
    return ((*bolt.Bucket)(b)).ForEach(fn)
}

// Get retrieves the value for a key in the bucket.
func (b *bucket) Get(key []byte) []byte {
    return ((*bolt.Bucket)(b)).Get(key)
}

// NextSequence returns an autoincrementing integer for the bucket.
func (b *bucket) NextSequence() (uint64, error) {
    return ((*bolt.Bucket)(b)).NextSequence()
}

// Put sets the value for a key in the bucket.
func (b *bucket) Put(key, value []byte) error {
    return ((*bolt.Bucket)(b)).Put(key, value)
}

// Close releases all database resources.
func (db *db) Close() error {
    return ((*bolt.DB)(db)).Close()
}

// Update executes a function within the context of a read-write managed transaction.
func (db *db) Update(fn func(Tx) error) error {
    return ((*bolt.DB)(db)).Update(func(tx *bolt.Tx) error {
        t := transaction(*tx)
        return fn(&t)
    })
}

// View executes a function within the context of a managed read-only transaction.
func (db *db) View(fn func(Tx) error) error {
    return ((*bolt.DB)(db)).View(func(tx *bolt.Tx) error {
        t := transaction(*tx)
        return fn(&t)
    })
}

// CreateBucketIfNotExists creates a new bucket if it doesn't already exist.
func (tx *transaction) CreateBucketIfNotExists(name []byte) (Bucket, error) {
    b, err := ((*bolt.Tx)(tx)).CreateBucketIfNotExists(name)
    if err != nil {
        return nil, err
    }
    w := bucket(*b)
    return &w, nil
}

到目前为止,在我的代码中,我只使用了上面显示的函数。如果新代码需要,我可以添加更多。

我将在实际代码中将每个bolt.DB 替换为DB,将bolt.Tx 替换为Tx,并将bolt.Bucket 替换为Bucket。 mocker 将对使用底层哈希映射而不是存储到磁盘的所有三种类型进行替换。然后我可以测试我的所有代码,一直到数据库调用。

【问题讨论】:

  • 您可以直接转换它们,但这里奇怪的是为什么您首先要制作类型B?这不是“包装”foo.A,而是删除方法集。
  • 我正在尝试为底层数据库编写一个模拟单元测试。
  • 不确定这将如何模拟任何东西,因为它仍然需要原始类型。您最好在自己的数据结构中编写所需的类型。
  • 查看我将如何使用它的更新说明。如果您能提出更好的方法,我很想知道。
  • 我不确定你的意思,你仍然可以用组合类型来实现你的接口。这里的基本问题是,基于来自另一个包的结构声明一个新类型不会使您与该包分离,仍然需要您存根所有现有方法,并且您需要非常小心地处理复制值。例如(警告,只是在没有验证任何内容的情况下输入此内容):play.golang.org/p/LBVcXkm31gi

标签: pointers go dereference


【解决方案1】:

您可以简单/直接地将*A 类型的值转换为*B 类型的值,您只需将*B 括起来即可:

a := foo.Fn()   // a is a *A
b := (*B)(a)
return b

你甚至可以转换函数调用的返回值:

return (*B)(foo.Fn())

Go Playground 上试用。

这是可能的,因为Spec: Conversions:

在以下任何一种情况下,非常量值 x 都可以转换为类型 T

还有Spec: Assignability:

如果以下条件之一适用,则值 x 可分配给 T 类型的 variable(“x 可分配给 T”):

*B*A 类型均未定义,*B 的底层类型与 *A 的底层类型相同(即指向任何类型的底层类型的指针) A 的类型声明)。

【讨论】:

  • 这就是我想要的。我尝试了几种组合,但没有编译。谢谢。
猜你喜欢
  • 2021-04-07
  • 2016-12-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-08-15
  • 2016-07-12
  • 2012-05-06
  • 2018-08-02
相关资源
最近更新 更多