I use memcache in several of my apps on AppEngine using just gob encoding and it works with all the different structs I pass into it. Â I have the two helper functions for add/get:
// AddToMemcache encapsulates the steps that are needed to encode something and stuff it into the cache.
func AddToMemcache(c appengine.Context, key string, data interface{}) error {
    // Encode the entry for storing.
    var buf []byte
    b := bytes.NewBuffer(buf)
    enc := gob.NewEncoder(b)
    err := enc.Encode(data)
    // If there was an encoding failure, log it and return it.
    if err != nil {
        c.Errorf("Error encoding data for cache (%s): %v", key, err)
        return err
    }
    // Create memcache.Item for storing.
        Value: b.Bytes(),
    }
    // Save to the cache.  If there was an error, return it.
    return memcache.Set(c, item)
}
// DecodeMemcache wraps the main tasks of grabbing from memcache and decoding.
// Assuming we properly registered the type in it's init() the gob.Decode call
// here will populate the element that is passed in.
func DecodeMemcache(c appengine.Context, key string, t interface{}) error {
    // Get from memcache
    item, err := memcache.Get(c, key)
    // For Cache Miss, log all so I can track down what isn't matching.
    //if err == memcache.ErrCacheMiss {
    //     c.Errorf("CacheMiss: key: %s", key)
    //}
    // Errors usually means memcache miss, or other reason we didn't get data.
    if err != nil {
        // Fall back to reading from datastore.
        return err
    }
    r := bytes.NewReader(item.Value)
    dec := gob.NewDecoder(r)
    return dec.Decode(t)
}
And here is how I use it. Â For every random data struct I have, I do the gob.Register in init() so it knows about it:
// UserProfile contains basic user profile info.
type UserProfile struct {
    Email    string  // viking - the primary key used to tie other structs to this one.
    DisplayName string // "Bob"
    StartDate     time.Time // 2006-01-02
    EmployeeType   string   // [ employee, contractor, ... ]
    OfficeNumber   string   // 275F
    Building     string   // Bldg 51
    Campus      string   // London, Paris, New York, San Fran, ...
    ...
}
func init() {
    // For generic encoding/decoding to work, you need to register
    // types you want to use with gob at init time.
    x := &UserProfile{}
    gob.Register(x)
}
Then for every struct I have, I call the DecodeMemcache(..) as needed:
// LoadUserProfile is the main interface to fetching a users profile. Start by trying to
// fetch from the cache. Â If that fails, fall back to the backend loading (datastore or
// RPC). If we get a hit from there re-cache and return the results.
func LoadUserProfile(c appengine.Context, ldap string) *UserProfile {
    if ldap == "" {
        return nil
    }
    u := &UserProfile{}
    err := DecodeMemcache(c, userProfileCacheKey(ldap), u)
    if err == nil {
        // If we did not get an error, then we are good to go.
        return u
    }
    // Else, reload from backing store.
    u, err = loadUserProfileFromDatastore(c, ldap)
    if err != nil {
        c.Errorf("Error on get from datastore: %v", err)
        return nil
    }
    // Cache this for next time.
    AddToMemcache(c, userProfileCacheKey(ldap), u)
    return u
}
You should be able to pass in the specific type you want back where you need it at this way. (i.e. pass the real type to the cache calls, and don't operate on the interface{} as a pointer but as a raw type in the cache get/set methods)
 - Robert