在Go中包装指针

时间:2018-02-19 13:30:03

标签: pointers go dereference

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

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

type B foo.A

我可以将*A转换为*B而无需取消引用A吗?

换句话说,如果我有

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

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

我问的原因是,在我使用的库github.com/coreos/bbolt中,*DB函数返回的Open值包含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.DBDB替换为bolt.Tx,将Tx替换为实际代码中的bolt.Bucket。对于使用底层哈希映射而不是存储到磁盘的所有三种类型,模拟器将使用替换。然后我可以测试我的所有代码,直到数据库调用。

1 个答案:

答案 0 :(得分:2)

您可以简单地/直接将*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")适用以下条件之一:

     
      
  • ...

  •   
  • x的类型VT具有相同的underlying types以及V或{{1}中的至少一个}不是defined类型。

  •   

T*B类型都没有定义,*A的基础类型与基础类型*B相同(它是指向的*A A)类型声明中的任何类型的基础类型。