gop icon indicating copy to clipboard operation
gop copied to clipboard

Introduce smart pointer in Go+

Open xushiwei opened this issue 5 years ago • 6 comments

When we use objects from a cgo package, we often need manage memory manually. In Go, we are suggested to treat them as resources like files. Here is a typically code:

cobj := foo.NewCObj()
defer cobj.Free()
...

If there are many cgo objects, It is ugly, and difficult to manage life cycles of these objects.

In C++, there is a concept named smart pointer to reduce difficulty of memory management. For example:

template <typename Ptr>
class smart {
private:
    Ptr data;

public:
    explicit smart(Ptr p) : data(p) {}
    ~smart() { data->Release(); }

    void operator=(const smart& sp) {
        if (sp.data != NULL) {
            sp.data->Acquire();
        }
        if (data != NULL) {
            data->Release();
        }
        data = sp.data;
    }
    Ptr operator ->() {
        return data;
    }
    operator Ptr() {
        return data;
    }
}

Maybe we can introduce smart pointer in Go+:

var (
    p *Type
    sp &Type  // Use `&Type` to indicate a smart pointer
)

func bar(sptr &Type) &Type { // Smart pointer can be input/output parameters
   ...
   return sptr
}

sp = (&Type)(p) // Convert a normal pointer into a smart pointer
p = (*Type)(sp) // Convert a smart pointer into a normal pointer
sp2 := sp // Assign a smart pointer to another
sp3 := bar(sp2) // Call a function who uses smart pointers
sp3 = nil // Release memory manually

However, the concept smart pointer breaks some basic conventions of the Go language. For example:

  • Smart pointer has a destructor.
  • Assignment of smart pointers is not only a memory copy.
  • Assignment of structs who use smart pointers is not only a memory copy.

Example1: cgo objects without reference count (has Close() method)

gobj := (&CObj)(NewCObj())
gobj.DoSth()
... // don't need to call gobj.Close() manually

gobj2 := gobj
// assignments have movement semantics:
// gobj2.data, gobj.data = gobj.data, nil

bar(gobj2)
// ERROR: can't use smart pointer as input parameters, if it hasn't reference count
// Please use normal pointer instead: bar((*CObj)(gobj2))

Example2: cgo objects with reference count (have Acquire/Release() methods)

import "C"

type Tensor &C.Tensor

func NewTensor(params Params) Tensor {
    return Tensor(C.NewTensor(params))
}

func (a Tensor) + (b Tensor) Tensor {
   ...
}

a := NewTensor(...)  // assignments have the same semantics of C++ smart<*C.Tensor>
b := NewTensor(...)
c := a + b
...

Important notes:

  • We don't support to override operator=, and the smart pointer is a very special case.

xushiwei avatar Jul 31 '20 18:07 xushiwei

Compare &T to ^T for representing smart pointers:

var (
    p *Type
    sp ^Type  // Use `^Type` to indicate a smart pointer
)

func bar(sptr ^Type) ^Type { // Smart pointer can be input/output parameters
   ...
   return sptr
}

sp = (^Type)(p) // Convert a normal pointer into a smart pointer
p = (*Type)(sp) // Convert a smart pointer into a normal pointer
sp2 := sp // Assign a smart pointer to another
sp3 := bar(sp2) // Call a function who uses smart pointers
sp3 = nil // Release memory manually

xushiwei avatar Aug 03 '20 03:08 xushiwei

Smart pointers not only can be used for normal pointer, but also can be used for interface:

func open(file string) ^io.ReadCloser {
    f, _ := os.Open(file)
    return (^io.ReadCloser)(f)
}

f := open("/foo/bar.jpg")
...
// don't need to call f.Close() manually

xushiwei avatar Aug 03 '20 05:08 xushiwei

Compare &T to ^T for representing smart pointers:

var (
    p *Type
    sp ^Type  // Use `^Type` to indicate a smart pointer
)

func bar(sptr ^Type) ^Type { // Smart pointer can be input/output parameters
   ...
   return sptr
}

sp = (^Type)(p) // Convert a normal pointer into a smart pointer
p = (*Type)(sp) // Convert a smart pointer into a normal pointer
sp2 := sp // Assign a smart pointer to another
sp3 := bar(sp2) // Call a function who uses smart pointers
sp3 = nil // Release memory manually

I prefer &. ^ was coined in C++/CLI to represent a managed object(garbage-collected), using ^ as a deterministic mechanism is confusing. On the contrary, & is from ISO C++ and is more well-known.

shendiaomo avatar Aug 05 '20 12:08 shendiaomo

This seems like a strange inclusion in a language that does not support cgo. Also, I don't know how you would even start to implement this, on multiple levels. It adds more parsing ambiguity, adding another function to the & operator, a symbol that is already used in three operations. All that for something that seems mildly useful, but unnecessarily complicated. What happened to "less is more"?

lolbinarycat avatar Aug 05 '20 19:08 lolbinarycat

What happened to "less is more"?

Now it is only a proposal, not a feature which has been decided adding into Go+.

This proposal is introduced when we implement GoTorch: https://github.com/wangkuiyi/gotorch/issues/3. A tensor object often is using GPU memory, and need be freed in time.

xushiwei avatar Aug 05 '20 22:08 xushiwei

gotorch link is broken. Is gotorch dead or moved to another place?

egolearner avatar Dec 23 '20 09:12 egolearner

outdated suggestion.

xushiwei avatar Apr 09 '24 02:04 xushiwei