Skip to content

Go values as objects and properties #704

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
FlorianUekermann opened this issue Sep 21, 2017 · 37 comments
Open

Go values as objects and properties #704

FlorianUekermann opened this issue Sep 21, 2017 · 37 comments

Comments

@FlorianUekermann
Copy link

There is no straightforward way of storing go values in a js object or property. I know this restriction is intentional, but it is a very inconvenient one at times. All workarounds I know of have severe problems (serialization overhead, global map[string]interface{} won't be garbage collected).

I suspect that there are proper workarounds for this problem using js.InternalObject, but it is unclear to me how to recover the go value from the resulting *js.Object and if I can use it with js.Object.Set/Get.

@4ydx
Copy link
Contributor

4ydx commented Sep 22, 2017

If you take a look at the wiki tab under "Javascript Tips and Gotchas" you will see an example of putting a *js.Object field into a struct. This allows your go struct to interact with an underlying javascript object.

@FlorianUekermann
Copy link
Author

I'm don't understand how that is related to storing go values as JavaScript objects and properties. Can you elaborate?

@theclapp
Copy link

@MaVo159, I think @4ydx misunderstood your question (or I did! :). You're talking about putting a Go object (such as a naked struct (struct { foo int })) into a js object (obj.Set("slot", struct{foo int}{foo: 6})), is that correct? And then, how to get it back out again, as a Go object?

Yeah, I don't know how to do that either.

I agree that you could get a *js.Object pointer to the Go object via js.InternalObject(), but I don't know how you'd get it back out again as its original type. Maybe copy it back out field by field?

Like so: https://gopherjs.github.io/playground/#/3xXHeQqtyZ

But that still gives you a copy and not the original object back again.

I've tested this exactly as much as it took to write the code in the playground, so YMMV, of course.

@FlorianUekermann
Copy link
Author

@theclapp understood the question correctly. Thanks for the example. I'll take a look at it.

Just to clarify why this is useful: I often need to attach tie data to dom elements so I can use it when processing events or methods called from JS. This is also the last missing piece for writing nice js libraries in go (I am currently working on some custom-elements for example).

@theclapp
Copy link

Storing a struct with a pointer to the struct you actually want seems to work.

type S struct{ i int }
type PS struct{ ps *S }

s := S{i: 5}
ps := PS{ps: &s}
println(fmt.Sprintf("s: %#v, ps: %#v, *ps.ps: %#v", s, ps, *ps.ps))

o := js.Global.Get("Object").New()
o.Set("go_slot", js.InternalObject(ps))

newPS := PS{}
js.InternalObject(newPS).Set("ps", o.Get("go_slot").Get("ps"))

println(fmt.Sprintf("o: %#v, s: %#v, s.i: %v, newPS: %#v, newPS.ps: %#v, *newPS.ps: %#v",
	o, s, s.i, newPS, newPS.ps, *(newPS.ps)))

println("set newPS.ps.i = 6")
newPS.ps.i = 6

println(fmt.Sprintf("o: %#v, s: %#v, s.i: %v, newPS: %#v, newPS.ps: %#v, *newPS.ps: %#v",
	o, s, s.i, newPS, newPS.ps, *(newPS.ps)))

=> s.i == 6

https://gopherjs.github.io/playground/#/ZKG-RvJ0fA

@theclapp
Copy link

This might be a little clearer, and a little more generic, to boot:

type S struct{ i int }
type Wrapper struct{ p interface{} }

s := S{i: 5}
w := Wrapper{p: &s}

o := js.Global.Get("Object").New()
o.Set("go_slot", js.InternalObject(w))

var w2 Wrapper
js.InternalObject(w2).Set("p", o.Get("go_slot").Get("p"))
ptrS := w2.p.(*S)

println("w2.p.(*S).i:", w2.p.(*S).i, "s.i:", s.i, "ptrS.i:", ptrS.i)
// => w2.p.(*S).i: 5 s.i: 5 ptrS.i: 5

ptrS.i = 6
println("w2.p.(*S).i:", w2.p.(*S).i, "s.i:", s.i, "ptrS.i:", ptrS.i)
// => w2.p.(*S).i: 6 s.i: 6 ptrS.i: 6

@4ydx
Copy link
Contributor

4ydx commented Sep 22, 2017

No idea what is better or considered the official way of doing this.

package main

import (
	"github.com/gopherjs/gopherjs/js"
)

func main() {
	js.Global.Get("console").Call("clear")

	type S struct{
		*js.Object
		Val string `js:"val"`
	}
	type PS struct{
		*js.Object 
		S *S `js:"S"`
	}

	ps := &PS{Object:js.Global.Get("Object").New()}
	ps.S = &S{Object:js.Global.Get("Object").New()}
	ps.S.Val = "test"
	
	o := js.Global.Get("Object").New()
	o.Set("go_slot", ps)

	println(o)
	println(o.Get("go_slot"))
	
	other := &PS{}
	other.Object = o.Get("go_slot")
	println(other)
	println(other.S)
	println(other.S.Val)
}

https://gopherjs.github.io/playground/#/vHSGf502Z5

@4ydx
Copy link
Contributor

4ydx commented Sep 22, 2017

Or just forgo the confusing bits and work with a struct. The former requires that you use *js.Object pointers while the code below requires that you manually set values when pulling data out of "go_slot".

package main

import (
	"github.com/gopherjs/gopherjs/js"
)

func main() {
	js.Global.Get("console").Call("clear")

	type S struct{
		Val string
	}
	type PS struct{
		S *S
	}
	ps := &PS{
		S : &S{
			Val: "test",
		},
	}
	o := js.Global.Get("Object").New()
	o.Set("go_slot", ps)

	println(o)
	println(o.Get("go_slot"))
	println(o.Get("go_slot").Get("S").Get("Val"))
}

https://gopherjs.github.io/playground/#/_vCeiu98k1

@theclapp
Copy link

@4ydx But, your first solution stores a js-special object, not a regular Go object, and your second solution doesn't get you a Go object back again. Am I missing something?

So far I prefer my solution at #704 (comment).

@myitcv
Copy link
Member

myitcv commented Sep 26, 2017

I'm just catching up so might have missed some intervening discussion on Slack/other.

Using the combination of *js.Object.Unsafe() and the unsafe package I think gives what we're after here (playground):

package main

import (
	"unsafe"

	"github.com/gopherjs/gopherjs/js"
)

type S struct {
	Name string
}

func main() {
	// Whatever the value is....
	v := S{Name: "Rob Pike"}
	
	// ...use js.InternalObject() on a _pointer_ to that value.
	//
	// Here we set a global variable in Javascript world to 
	// the result.
	js.Global.Set("banana", js.InternalObject(&v))
	
	// ....
	
	// To then use that value again, get the *js.Object value...
	vjo := js.Global.Get("banana")
	
	// ... then use .Unsafe() + unsafe.Pointer
	vp := (*S)(unsafe.Pointer(vjo.Unsafe()))
	
	// Verify that we have exactly the same object
	println(vp == &v, *vp == v)
}

If we also look at the transpiled code we can see that's pretty much as expected:

	main = function() {
		var v, vjo, vp;
		v = new S.ptr("Rob Pike");
		$global.banana = v;
		vjo = $global.banana;
		vp = ($pointerOfStructConversion((vjo), ptrType));
		console.log(vp === v, $equal(vp, v, S));
	};

(the slightly incongruous thing here being that struct values are also pointers in Javascript world; the difference is handled in the compiler)

@FlorianUekermann
Copy link
Author

Thanks @myitcv, that's exactly what I was looking for. I think it would make sense to expose this as SetGoValue(p interface{}) and GetGoValue() interface{} methods.

Maybe GetGoValue should panic if the requested field is not a go value.

Or maybe the js.Object.Interface() should just be taught to recognize go values, which currently results in a crash (see https://gopherjs.github.io/playground/#/xaP0qP2yKZ for a modified version of @myitcv's example using Interface())

@theclapp
Copy link

Nice work @myitcv. I knew I wanted a C-style "just do it" cast, I just didn't know how to achieve it. I've never(?) used unsafe and forgot about it.

@myitcv
Copy link
Member

myitcv commented Sep 26, 2017

@theclapp 👍 - unsafe to the rescue 😄

@MaVo159

Or maybe the js.Object.Interface() should just be taught to recognize go values

Unfortunately not; because to do so would mean the value gets externalize-d on the way out (i.e. when Set on banana to take my example) and internalize-d on the way back in (when we do the corresponding Get). And this is not what you want; you want the value to be untouched.

Hence we need unsafe (and the GopherJS equivalent) to side step the compiler's normal behaviour. We're effectively telling the compiler "I know what I'm doing"....

I think it would make sense to expose this as SetGoValue(p interface{}) and GetGoValue() interface{} methods.

... which is why I'm not in favour of making this sort of thing any more friendly 😄. So I wouldn't be in favour of this API change.

For the sake of two or three lines I think the unsafe "pattern" above suffices in the few, specialised occasions this sort of thing is required.

@FlorianUekermann
Copy link
Author

FlorianUekermann commented Sep 27, 2017

Or maybe the js.Object.Interface() should just be taught to recognize go values

Unfortunately not; because to do so would mean the value gets externalize-d on the way out (i.e. when Set on banana to take my example) and internalize-d on the way back in (when we do the corresponding Get). And this is not what you want; you want the value to be untouched.

I fail to see the problem. I'm suggesting that the Interface() method has a look at whether it is dealing with a go value or not and returns the go value if that is what we're dealing with. There is already special treatment for instanceof Node objects.

Returning the map[string]interface{} externalization of the internal object encoding the go value (I'm assuming that's what Interface() does right now) doesn't seem like the better option here.

@myitcv
Copy link
Member

myitcv commented Sep 28, 2017

I fail to see the problem. I'm suggesting that the Interface() method has a look at whether it is dealing with a go value or not and returns the go value if that is what we're dealing with

Ah, I see what you're saying.

I don't think this is feasible. Reason being, not all Go values carry type information around with them. Hence Interface() cannot work out the type of the value. Indeed some Go values are indistinguishable from their corresponding Javascript values (e.g. numeric values); that ambiguity would mean Interface() can never know what to do.

You could instead "set" the interface{}-converted value:

js.Global.Set("banana", js.InternalObject(interface{}(&v)))

but then when you get the value back out you'd still need to type-assert to do anything useful.

So I don't think there's anything to be gained over the approach laid out in #704 (comment)

Does the approach in #704 (comment) miss something you're after? Am I missing something obvious?

@FlorianUekermann
Copy link
Author

FlorianUekermann commented Oct 1, 2017

I don't think this is feasible. Reason being, not all Go values carry type information around with them.

I see. That makes sense.

Does the approach in #704 (comment) miss something you're after? Am I missing something obvious?

A type assertion from interface{} will either panic or be safe, with unsafe there won't even be runtime type checking.

You could instead "set" the interface{}-converted value:
js.Global.Set("banana", js.InternalObject(interface{}(&v)))

So we are back basically back to implementing the o.SetGoValue(p interface{}) and o.GetGoValue() interface{} proposal. I do think that supporting this is worthwhile, since it is generally useful, non-trivial (just look at this thread) and the dangerous part (js.InternalObject) is exposed already.

The idea to use Get/Set methods was probably misguided though. Maybe something like this:

func WrapValue(v interface{}) *js.Object {
  return js.InternalObject(&v)
}
func UnwrapValue(*js.Object) interface{} {
  if not go interface {
    panic("..."); 
  }
  return *(*interface{})(unsafe.Pointer(v.Unsafe()))
}

(needs a better name that doesn't sound like MakeWrapper)

Alternatively, maybe MakeWrapper can be modified a tiny bit to store type info, then we just need an Unwrap method. I would expect MakeWrapper to have unfortunate performance characteristics though.

@myitcv
Copy link
Member

myitcv commented Jan 2, 2018

I do think that supporting this is worthwhile, since it is generally useful, non-trivial (just look at this thread) and the dangerous part (js.InternalObject) is exposed already.

@FlorianUekermann I'm somewhat coming round to your suggestion that this should be exposed more formally.

I recently distilled out the relevant functions in myitcv.io/react. It's arguably not the most complex or long bit of code but I agree it would easier for everyone involved to have it defined in one place.

That said, given this is the first thread on the topic (that I'm aware of) there's perhaps a question of how prevalent such a requirement is?

@FlorianUekermann
Copy link
Author

I recently distilled out the relevant functions in myitcv.io/react. It's arguably not the most complex or long bit of code but I agree it would easier for everyone involved to have it defined in one place.

Looks good. That's pretty much identical to what I use.

That said, given this is the first thread on the topic (that I'm aware of) there's perhaps a question of how prevalent such a requirement is?

I've seen discussions about this here and there. I just never saw it solved without resorting to a global map.
The pattern is practically unavoidable if you want to implement custom-elements with some complexity in go. At least that is where I couldn't come up with a reasonable alternative. But I guess it applies to any situation where you need to return go object handles to javascript (and don't want to lose gc).

@norunners
Copy link

Hello folks, I too have wrestling with this problem a lot while working on a state management binding for gopherjs. I believe this is a shortcoming of gopherjs which should be supported in the future. For now, I created the package vert to convert js objects to go values of any type. Beneficially, the js objects do not need to originate from go values. Please provide feedback if the package does not suite your use case.
https://github.com/norunners/vert

@myitcv
Copy link
Member

myitcv commented Apr 29, 2018

Thanks @norunners - good to see a release of vert!

Beneficially, the js objects do not need to originate from go values

This particular discussion is specific to the case where the values do originate in the Go world, so I think vert looks to address a different problem.

I'll also point out #790 in the more general context of values moving between the two worlds.

@FlorianUekermann - I think I'd be tempted to agree with your conclusion that there is some merit in making this part of the js package. Notwithstanding the fact that the functions themselves are straightforward, that's only the case if you know how to write them.

Given js.MakeWrapper already exists, how about the following names for API addition?

// Cloak encapsulates a Go value within a JavaScript object. None of the fields
// or methods of the value will be exposed; it is therefore not intended that this
// *Object be used by Javascript code. Instead this function exists as a convenience
// mechanism for carrying state from Go to JavaScript and back again.
func Cloak(i interface{}) *Object {
    // ..
}

// Uncloak is the inverse of Cloak.
func Uncloak(*Object) interface{} {
    // ..
}

@norunners
Copy link

Thanks @myitcv for explaining this particular problem more clearly to me. I am curious to what benefit is it to let the object be unusable in the js world? Packagevert aim to ease conversion back to the go world as object.Interface() does, but with less limitations. Most notably, from the js object to the map[string]interface{} type instead of the original go struct type. I do agree that if The js object originated from the go world, it’s type should survive conversion somehow. Since js objects traditionally have no types, it is a problem to tell any js object to convert to a go value without the cost of giving it a type before conversion.

@myitcv
Copy link
Member

myitcv commented Apr 30, 2018

@norunners the concrete use case I have is my use of state in myitcv.io/react. In this case, the JavaScript world, more specifically React, is the source of truth with respect to the state of a component. But the React code does not care what the state value is; it just manages the reference to the current state object. Components written in Go code care what the value of the current state is when they come to render, but when they render and what that current state value is is determined by React (JavaScript). So despite my code needing to update the state, the management of the reference to current state and when to render is a responsibility that lies with the React (JavaScript) world. I take advantage of this fact by using the aforementioned cloaking technique so that the untouched Go value gets passed to and from the JavaScript world, without being externalize-d or internalize-d (because that would a) lose type information and b) be costly in terms of memory/CPU but more importantly c) be totally pointless because I want the exact value as before).

@norunners
Copy link

norunners commented May 18, 2018

Both cases we have described are different and ideally, each should have some form of support. The cloak/uncloak method has the following limitations. 1) The state must always be cloaked before entering the JS layer. This is an extra inconvenient step as gopherjs automatically converts Go values to JS objects by nature. I ask, why not convert back from JS objects to Go values for non-primitive types if possible? 2) The cloaked state may not be used by the JS layer at all. This is a dealbreaker for some state manager behavior, e.g. getters and watchers.

After encountering those issues, I was convinced to only focus on converting JS objects to Go values of any type, e.g. structs. After some refinement, package vert was developed. Beneficialy, this cleaned up all my usages of converting before the JS layer, resulting in only needing to convert unidirectional instead of bidirectional.

Lastly, I recognized a common theme for JS libraries to prefer callback functions. This was problematic since we want Go function with type safe parameters. After the core convert logic was in place, I extended package vert to accommodate JS callbacks by allowing type safe Go functions to be called with JS objects. I’ll conclude this rabbit hole with a trivial hello world example.

func main() {
    object := js.Global.Get("Object").New("Hello World!")
    function := vert.New(print)
    function.Call(object)
}

func print(message string) {
    fmt.Println(message)
}

Note, function print has a type safe string parameter.

@myitcv
Copy link
Member

myitcv commented May 18, 2018

@norunners

why not convert back from JS objects to Go values for non-primitive types if possible?

Because, in my case, it's an unnecessary overhead first converting from Go -> Javascript, and then Javascript -> Go, particularly when you a) lose type information and b) increase the CPU and memory cost, c) lose pointers... the list goes on. Best to just keep the Go values as Go values and "cloak" them.

The cloaked state may not be used by the JS layer at all. This is a dealbreaker for some state manager behavior, e.g. getters and watchers.

Totally agree, it doesn't suit all cases. Hence why I pointed out #790 in #704 (comment).

Beneficialy, this cleaned up all my usages of converting before the JS layer, resulting in only needing to convert unidirectional instead of bidirectional.

One thing I'm not totally clear on is where the automatic conversions applied by the github.com/gopherjs/gopherjs/js package fell short?

I’ll conclude this rabbit hole with a trivial hello world example.

Is this therefore equivalent to what you want?

package main

import (
	"fmt"

	"github.com/gopherjs/gopherjs/js"
)

func main() {
	object := js.Global.Get("Object").New("Hello World!")
	print(object.String())
}

func print(message string) {
	fmt.Println(message)
}

Playground link: https://gopherjs.github.io/playground/#/SquXDT1mHT

Most notably, from the js object to the map[string]interface{} type instead of the original go struct type.

Is this what you are referring to with getting map[string]interface{} values? Does this give you what you need?

package main

import (
	"fmt"

	"github.com/gopherjs/gopherjs/js"
)

func main() {
	js.Global.Call("eval", `window.banana = {hello: "world", age: 1000}`)
	v := js.Global.Get("banana").Interface()
	fmt.Printf("v has type %T\n", v)
}

Playground: https://gopherjs.github.io/playground/#/__MJlotUxS

@norunners
Copy link

@myitcv

Because, in my case, it's an unnecessary overhead first converting from Go -> Javascript

The cost this direction should be negligible and gopherjs handles it naturally for free with no client conversions needed.

and then Javascript -> Go, particularly when you a) lose type information

I've demonstrated with package vert that the type information is not required in the JS layer and the JS object can still be converted to a Go value with the same type structure.

and b) increase the CPU and memory cost

Yes a little bit, I haven't experienced any noticeable overhead with package [vert (https://github.com/norunners/vert) and it shouldn't block a gopherjs solution from being optimized.

c) lose pointers

Again, I've demonstrated with package vert that it accommodate the same Go type that the user defines, with or without pointers.

Best to just keep the Go values as Go values and "cloak" them.

This is a viable approach for simple use cases but I wanted to document where it falls short. So the community can be aware of the two solutions that solve two related but different problems.

One thing I'm not totally clear on is where the automatic conversions applied by the github.com/gopherjs/gopherjs/js package fell short?

The shortcoming of gopherjs is that structs, maps and arrays are strictly converted to either map[string]interface{} or []interface{} which forces the clients to type assert often and even into deeper levels of the structure. It's doable but an unnecessary burden.

Is this therefore equivalent to what you want?

print(object.String())

This too works for simple cases, but it's not generic at all since the client must be aware of the js.Object instead of just the string type.

Is this what you are referring to with getting map[string]interface{} values? Does this give you what you need?

Yes, this is possible but extremely inconvenient as it burdens the client to both key into the map and type assert each value needed instead of being able to provide a single struct that suits the JS object's structure.

Below is your example from above, but with added type safety.

type banana struct {
	*js.Object

	hello string `js:"hello"`
	age   int    `js:"age"`
}

func main() {
	js.Global.Call("eval", `window.banana = {hello: "world", age: 1000}`)
	v := js.Global.Get("banana")

	function := vert.New(print)
	function.Call(v)
}

func print(v *banana) {
	fmt.Printf("v has type %T\n", v)
}

Output: v has type *main.banana
Playground: https://play.jsgo.io/0d8d2dcbe13b5128645ef32aaf0460108d53f931

@myitcv
Copy link
Member

myitcv commented May 20, 2018

The cost this direction should be negligible and gopherjs handles it naturally for free with no client conversions needed.

For non-*js.Object-special types, the cost is infinitely higher than not doing any conversion in either direction. I say infinitely, because of course the cost of not doing any conversion is zero. I don't have *js.Object-special types and want to avoid any conversion because I don't need it; hence any conversion will simply cost me time and memory.

I'm unclear, do you see a particular problem with what I'm doing with the "cloak"/"uncloak" approach?

@norunners
Copy link

norunners commented May 21, 2018

Thank you for the clarification about conversion costs.

I'm unclear, do you see a particular problem with what I'm doing with the "cloak"/"uncloak" approach?

Your approach is awesome, correct and trivial to implement. It should be used when possible. If the user needs more flexibility in the JS layer (mentioned earlier) then package vert is the only effort I've seen to support these use cases.

@FrankReh
Copy link
Contributor

FrankReh commented Jun 9, 2018

Storing Go addresses in the javascript world and later getting them back seems very useful. I'm surprised this thread hasn't garnered more comments. I had spent the morning trying to see how it was done by other GopherJS binding packages and then luckily came across this issue. I want to use the javascript WeakMap feature with Go addresses.

Cloak() doesn't return the same *js.Object for each call with the same input, so it can't be used as the key for a javascript WeakMap. In fact, it seems to intentionally create a closure so that's more overhead than I would expect is needed - at least how I am seeing using Go as a better way to write javascript. This is how Cloak is transpiled for me:

       Cloak = function(v) {
               var v, v$24ptr;
               return (v$24ptr || (v$24ptr = new ptrType$1(function() { return v; }, function($v) { v = $v; })));
       };

Perhaps this is interesting because it may be able to return the address of the interface type itself, having captured it in a closure, rather than storing the two parts of the interface separately (which would also require a javascript object to be created).

But since using the unsafe was now on the table, I wondered if it could be used more directly. I use it to convert a concrete pointer to a *js.Object, and back, and now the javascript WeakMap works with Go addresses. Thanks!

@theclapp
Copy link

theclapp commented Jun 9, 2018

@FrankReh:

Cloak() doesn't return the same *js.Object for each call with the same input

FWIW, the Cloak function you're using isn't quite right. You're using

func Cloak(v interface{}) *js.Object {
	return js.InternalObject(&v)
}

func Uncloak(v *js.Object) interface{} {
	return *(*interface{})(unsafe.Pointer(v.Unsafe()))
}

This calls new ptrType$1 each time and, as you say, will thus be different each time, even for the same input.

Try this:

func Cloak(i interface{}) *js.Object {
	return js.InternalObject(i)
}

func Uncloak(o *js.Object) interface{} {
	return interface{}(unsafe.Pointer(o.Unsafe()))
}

=>

Cloak = function(i) {
	var i;
	return i;
};
Uncloak = function(o$1) {
	var o$1;
	return ((o$1));
};

Example (using the GoConvey testing library):

func TestCloak(t *testing.T) {
	type testGoType struct {
		Name string
	}

	Convey("Cloak/Uncloak", t, func() {
		v := testGoType{Name: "My Name"}
		cl := Cloak(&v)
		v2 := Uncloak(cl).(*testGoType)
		So(&v, ShouldEqual, v2)

		cl2 := Cloak(&v)
		So(cl == cl2, ShouldBeTrue)
	})
}

% gopherjs test
..
2 total assertions

PASS
ok  	github.com/huckridgesw/hvue	0.443s

@FrankReh
Copy link
Contributor

FrankReh commented Jun 9, 2018

Brilliant. Simply very, very timely help. Thank you.

That is so much better. With that, empty interfaces can be passed to the javascript WeakSet too.

@myitcv
Copy link
Member

myitcv commented Jun 11, 2018

@theclapp

FWIW, the Cloak function you're using isn't quite right

That's probably as a result of @FrankReh having read #704 (comment), so mea culpa 😄

The one thing to note about the difference between the two is that with your approach, when you uncloak you need to know the exact type of the value that was cloaked. This might be ok in your situation but where interface values are concerned it can be inconvenient (because you need to know the pointer type to the original interface value as it was cloaked).

That said, "my" approach does have the noted overhead (not sure how significant it ends up being).

@theclapp
Copy link

The one thing to note about the difference between the two is that with your approach, when you uncloak you need to know the exact type of the value that was cloaked

I don't follow. (Not saying you're wrong, just that I don't understand.) Both our functions return interface{}. Can you elaborate or give an example?

@myitcv
Copy link
Member

myitcv commented Jun 11, 2018

Can you elaborate or give an example?

Hmm, I was just trying to pull together an example of what I meant, and I can't. Which makes me think I've actually got this wrong.

Indeed per myitcv/react#153, I've sufficiently proved to myself that I'm wrong 👍

Thanks @theclapp!

@FrankReh
Copy link
Contributor

It's very interesting. The cloak and uncloak functions become no-ops, simply returning the address they were passed. They serve to make the gopherjs compiler happy - which is great because the gopherjs compiler is half the reason for doing things this way.

And it still feels lucky, or the result of the natural progression of garbage collection design, that this can work - the assumptions the go compiler makes about the runtime gc (mostly that it is a tracing gc) are aligned with the modern browser javascript runtimes. I'm so used to reference counting in obj-c and swift or nothing in C - that it seems natural that a variable would need to be boxed. But here, there are not two runtimes trying to coexist. There's just the browser runtime with the go language assumptions being mapped into it, and some map directly.

The address of the interface structure being passed to cloak, and coming back from uncloak, I also started to think of as opaque data to javascript, and for a moment, I was surprised the javascript garbage collector could "see" inside. And then I remembered, again, that when this is running, it is all js, and in fact other js code could be written to look into that data that I was considering opaque - and the runtime that the gopherjs team has built does exactly that already.

It's all very cool and I'm still trying to get my head around how it works and what it is making possible. Weakmaps and proxies are now on the table for go that is designed for gopherjs.

@myitcv
Copy link
Member

myitcv commented Jun 12, 2018

It's very interesting. The cloak and uncloak functions become no-ops, simply returning the address they were passed.

Yes, that's true (as @theclapp pointed out), but also note the call site to see what transformation happens there (in the compiler). So given:

package main

import (
	"fmt"
	"unsafe"

	"github.com/gopherjs/gopherjs/js"
)

func main() {
	var a uint8 = 5
	b := Cloak(a)
	c := Uncloak(b)
	fmt.Printf("%T %v\n", c, c)
}

func Cloak(i interface{}) *js.Object {
	return js.InternalObject(i)
}

func Uncloak(o *js.Object) interface{} {
	return interface{}(unsafe.Pointer(o.Unsafe()))
}

the relevant part of the transpiled output are the two functions:

Cloak = function(i) {
	var i;
	return i;
};
Uncloak = function(o) {
	var o;
	return ((o));
};

but also the call site for Cloak where the "wrapping" in an interface{} type happens:

a = 5;
b = Cloak(new $Uint8(a));

@FrankReh
Copy link
Contributor

Wrapping a uint8 to an interface is just done that way by the gopherjs compiler I think. The go compiler also has some work to do to convert a uint8 to an interface type - and perhaps if escape analysis says it is warranted, allocate memory for the interface object.

If I'm cloaking an interface to begin with, then gopherjs adds no additional wrapping. (At least that's what I though I saw when I worked with it yesterday.)

@myitcv
Copy link
Member

myitcv commented Jun 12, 2018

Wrapping a uint8 to an interface is just done that way by the gopherjs compiler I think.

Yes, indeed, but that's because the representation of values of numeric Go types does not carry type information around with it. So I was just including it as effectively an edge case of where the call site does matter.

If I'm cloaking an interface to begin with, then gopherjs adds no additional wrapping.

Yep, completely agree.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants