Introduce smart pointer in Go+
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.
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
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
Compare
&Tto^Tfor 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.
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"?
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.
gotorch link is broken. Is gotorch dead or moved to another place?
outdated suggestion.