Reputation: 32284
A library foo
exposes a type A
and a function Fn
in that library returns a *A
.
I have defined a "wrapper" for A
called B
:
type B foo.A
Can I convert the *A
to a *B
without dereferencing the A
?
In other words, if I have
a := foo.Fn() // a is a *A
b := B(*a)
return &b
How can I convert the *a
to a *b
without using *a
?
The reason that I ask is that in the library that I am using, github.com/coreos/bbolt
, the *DB
value returned from the Open
function includes a sync.Mutex
and so the compiler complains when I try to make a copy of the Mutex
.
UPDATE TO EXPLAIN HOW I'LL USE THIS
I have a
type Datastore struct {
*bolt.DB
}
I also have a function (one of many) like this:
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
})
}
I would like to mock the underlying BoltDB database using a hash map. I ran into a problem mocking this because of the View
expecting a function that takes bolt.Tx
. That tx
is then used to create a new bucket in CreateBucketIfNotExists
. I cannot replace that anonymous function argument with one that calls my hash map mock version of CreateBucketIfNotExists
.
I came up with this:
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
}
So far, in my code, I am only using the functions shown above. I can add more if new code requires.
I will replace each bolt.DB
with DB
, bolt.Tx
with Tx
, and bolt.Bucket
with Bucket
in the real code. The mocker will use replacements for all three types that use the underlying hash map instead of storing to disk. I can then test all of my code, right down to the database calls.
Upvotes: 2
Views: 1254
Reputation: 417462
You can simply / directly convert a value of type *A
to a value of type *B
, you just have to parenthesize *B
:
a := foo.Fn() // a is a *A
b := (*B)(a)
return b
You can even convert the return value of the function call:
return (*B)(foo.Fn())
Try it on the Go Playground.
This is possible, because Spec: Conversions:
A non-constant value
x
can be converted to typeT
in any of these cases:
x
is assignable toT
....
A value
x
is assignable to a variable of typeT
("x
is assignable toT
") if one of the following conditions applies:
...
x
's typeV
andT
have identical underlying types and at least one ofV
orT
is not a defined type.
Both *B
and *A
types are not defined, and the underlying type of *B
is the same as the underlying type of *A
(which is the pointer to the underlying type of whatever type there is in the type declaration of A
).
Upvotes: 4