4

I want to write a file cache in Go. I am using gob encoding, and saving to a file, but my get function has some problem:

package main

import (
    "encoding/gob"
    "fmt"
    "os"
)

var (
    file = "tmp.txt"
)

type Data struct {
    Expire int64
    D      interface{}
}

type User struct {
    Id   int
    Name string
}

func main() {
    user := User{
        Id:   1,
        Name: "lei",
    }
    err := set(file, user, 10)
    if err != nil {
        fmt.Println(err)
        return
    }

    user = User{}
    err = get(file, &user)
    if err != nil {
        fmt.Println(err)
        return
    }

    //user not change.
    fmt.Println(user)

}

func set(file string, v interface{}, expire int64) error {
    f, err := os.OpenFile(file, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600)
    if err != nil {
        return err
    }
    defer f.Close()

    //wrapper data
    //save v in data.D
    data := Data{
        Expire: expire,
        D:      v,
    }
    gob.Register(v)
    enc := gob.NewEncoder(f)
    err = enc.Encode(data)
    if err != nil {
        return err
    }
    return nil
}

func get(file string, v interface{}) error {
    f, err := os.OpenFile(file, os.O_RDONLY, 0600)
    if err != nil {
        return err
    }
    defer f.Close()

    var data Data
    dec := gob.NewDecoder(f)
    err = dec.Decode(&data)
    if err != nil {
        return err
    }

    //get v
    v = data.D
    fmt.Println(v)

    return nil
}

The get function passes interface type and I want to change the value, but not change. http://play.golang.org/p/wV7rBH028o

0

2 Answers 2

2

In order to insert an unknown value into v of type interface{}, you need to use reflection. This is somewhat involved, but if you want to support this in full, you can see how its done by walking through the decoding process in some of the encoding packages (json, gob).

To get you started, here's a basic version of your get function using reflection. This skips a number of checks, and will only decode something that was encoded as a pointer.

func get(file string, v interface{}) error {
    f, err := os.OpenFile(file, os.O_RDONLY, 0600)
    if err != nil {
        return err
    }
    defer f.Close()

    rv := reflect.ValueOf(v)
    if rv.Kind() != reflect.Ptr || rv.IsNil() {
        panic("need a non nil pointer")
    }

    var data Data
    dec := gob.NewDecoder(f)
    err = dec.Decode(&data)
    if err != nil {
        return err
    }

    dv := reflect.ValueOf(data.D)
    if dv.Kind() != reflect.Ptr {
        panic("didn't decode a pointer")
    }

    rv.Elem().Set(dv.Elem())
    return nil
}

I would actually suggest an easier way to handle this in your own code, which is to have the Get function return an interface{}. Since you will know what the possible types are at that point, you can use a type switch to assert the correct value.

Sign up to request clarification or add additional context in comments.

Comments

1

An alternative approach is to return directly the value from the file:

func get(file string) (interface{}, error) {
    f, err := os.OpenFile(file, os.O_RDONLY, 0600)
    if err != nil {
        return nil, err
    }
    defer f.Close()

    var data Data

    dec := gob.NewDecoder(f)
    err = dec.Decode(&data)
    if err != nil {
        return nil,err
    }

    fmt.Println(data.D)

    return data.D,nil
}

full working example: http://play.golang.org/p/178U_LVC5y

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.