Ralph
Ralph

Reputation: 32284

Wrapping a pointer in Go

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

Answers (1)

icza
icza

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 type T in any of these cases:

And Spec: Assignability:

A value x is assignable to a variable of type T ("x is assignable to T") if one of the following conditions applies:

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

Related Questions