Re: [go-nuts] Writing bitmap data to a canvas with WebAssembly

2023-11-07 Thread Kai O'Reilly
Apologies for any confusion I created. I was referring to the time spent 
transferring the image from Go to JS and placing it on the canvas, not the 
actual time it takes the browser to render the canvas, as the transfer and 
placing time is the only thing that can be easily controlled.

On Tuesday, November 7, 2023 at 8:36:23 PM UTC-8 robert engels wrote:

> Your numbers do not make sense.
>
> At 300 us a frame, it would be more than 3k frames a second - which would 
> only be possible for an extremely tiny area.
>
> You need to include the time to produce the data when considering “render 
> time” - otherwise you are talking apples and oranges.
>
> On Nov 7, 2023, at 9:43 PM, Kai O'Reilly  wrote:
>
> For future reference for anyone who comes across this thread, you can 
> directly pass RGBA image data to a canvas, which is around 200 times faster 
> than encoding it to a PNG and rendering it to an offscreen image. Also, 
> passing the unsafe pointer to the slice to JavaScript instead of copying 
> the bytes improves performance by another factor of 5. These two changes 
> combined reduced the render time for me from roughly 300 milliseconds to 
> 300 microseconds (a 1000x improvement). This performance is enough to run 
> an interactive pure Go GUI through WASM without any noticeable lag, just by 
> writing images to a canvas.
>
> This is the relevant Go code:
>
> ```go
>  sz := dw.image.Bounds().Size()
> ptr := uintptr(unsafe.Pointer([0]))
> js.Global().Call("displayImage", ptr, len(dw.image.Pix), sz.X, sz.Y)
> ```
>
> And this is the relevant JS code:
>
> ```js
> const appCanvas = document.getElementById('app');
> const appCanvasCtx = appCanvas.getContext('2d');
>
> let wasm;
> let memoryBytes;
>
> // displayImage takes the pointer to the target image in the wasm linear 
> memory
> // and its length. Then, it gets the resulting byte slice and creates an 
> image data
> // with the given width and height.
> function displayImage(pointer, length, w, h) {
>   // if it doesn't exist or is detached, we have to make it
>   if (!memoryBytes || memoryBytes.byteLength === 0) {
> memoryBytes = new Uint8ClampedArray(wasm.instance.exports.mem.buffer);
>   }
>
>   // using subarray instead of slice gives a 5x performance improvement 
> due to no copying
>   let bytes = memoryBytes.subarray(pointer, pointer + length);
>   let data = new ImageData(bytes, w, h);
>   appCanvasCtx.putImageData(data, 0, 0);
> }
> ```
> In the JS code, `wasm` is initialized as the result of 
> `WebAssembly.instantiateStreaming`.
>
> On Friday, July 24, 2020 at 10:53:14 AM UTC-7 Mark Farnan wrote:
>
>> Little old, but this might also help. 
>>
>>  A while back I built a helper package to deal with these issues for 
>> canvas rendering from Go.  
>>
>> https://github.com/markfarnan/go-canvas  
>>
>> I'm currently working on it to add  WebGL support & get it working in 
>> TinyGo (some issues still).
>>
>> Regards
>>
>> Mark.
>>
>>
>>
>> On Sunday, 22 March 2020 at 09:08:40 UTC+1 Scott Pakin wrote:
>>
>>> I figure I ought to follow up with some results.  First, I got the 
>>> suggested approach of local render + js.CopyBytesToJS 
>>>  + update canvas from 
>>> image to work, so thanks, Agniva and Howard!  Second, for the benefit of 
>>> future readers of this thread, one thing that wasn't obvious to me is that 
>>> one needs to render the image data in a browser-recognizable image format—I 
>>> used PNG —*not* 
>>> raw {red, green, blue, alpha} bytes as is needed when writing directly to a 
>>> canvas 's image data.  
>>> Third, I used JavaScript code like the following to update an invisible 
>>> img  then copy the image 
>>> data from there to a visible canvas:
>>>
>>> function copyBytesToCanvas(data) {
>>> let blob = new Blob([data], {"type": "image/png"});
>>> let img = document.getElementById("myImage");
>>> img.onload = function() {
>>> let canvas = document.getElementById("myCanvas");
>>> let ctx = canvas.getContext("2d");
>>> ctx.drawImage(this, 0, 0);
>>> };
>>> img.src = URL.createObjectURL(blob);
>>> }
>>>
>>> Fourth, the performance is indeed substantially faster than my previous 
>>> approach based on using SetIndex 
>>>  to write directly 
>>> to the canvas, even though the new approach requires the extra steps of 
>>> encoding the image in PNG format and copying the image data from an img 
>>> to a canvas.  The following performance data, measured with Go 1.14 and 
>>> Chromium 80.0.3987.132 on an Ubuntu Linux system, is averaged over 10 
>>> runs:
>>>
>>> *Old*: 686.9 ± 7.6 ms
>>> *New*: 290.4 ± 4.1 ms (284.3 ± 4.2 on the WebAssembly side plus 6.0 ± 
>>> 2.3 on the JavaScript side)
>>>
>>> This is the time to render a simple 800×800 

Re: [go-nuts] Writing bitmap data to a canvas with WebAssembly

2023-11-07 Thread robert engels
Your numbers do not make sense.

At 300 us a frame, it would be more than 3k frames a second - which would only 
be possible for an extremely tiny area.

You need to include the time to produce the data when considering “render time” 
- otherwise you are talking apples and oranges.

> On Nov 7, 2023, at 9:43 PM, Kai O'Reilly  wrote:
> 
> For future reference for anyone who comes across this thread, you can 
> directly pass RGBA image data to a canvas, which is around 200 times faster 
> than encoding it to a PNG and rendering it to an offscreen image. Also, 
> passing the unsafe pointer to the slice to JavaScript instead of copying the 
> bytes improves performance by another factor of 5. These two changes combined 
> reduced the render time for me from roughly 300 milliseconds to 300 
> microseconds (a 1000x improvement). This performance is enough to run an 
> interactive pure Go GUI through WASM without any noticeable lag, just by 
> writing images to a canvas.
> 
> This is the relevant Go code:
> 
> ```go
>  sz := dw.image.Bounds().Size()
> ptr := uintptr(unsafe.Pointer([0]))
> js.Global().Call("displayImage", ptr, len(dw.image.Pix), sz.X, sz.Y)
> ```
> 
> And this is the relevant JS code:
> 
> ```js
> const appCanvas = document.getElementById('app');
> const appCanvasCtx = appCanvas.getContext('2d');
> 
> let wasm;
> let memoryBytes;
> 
> // displayImage takes the pointer to the target image in the wasm linear 
> memory
> // and its length. Then, it gets the resulting byte slice and creates an 
> image data
> // with the given width and height.
> function displayImage(pointer, length, w, h) {
>   // if it doesn't exist or is detached, we have to make it
>   if (!memoryBytes || memoryBytes.byteLength === 0) {
> memoryBytes = new Uint8ClampedArray(wasm.instance.exports.mem.buffer);
>   }
> 
>   // using subarray instead of slice gives a 5x performance improvement due 
> to no copying
>   let bytes = memoryBytes.subarray(pointer, pointer + length);
>   let data = new ImageData(bytes, w, h);
>   appCanvasCtx.putImageData(data, 0, 0);
> }
> ```
> In the JS code, `wasm` is initialized as the result of 
> `WebAssembly.instantiateStreaming`.
> 
> On Friday, July 24, 2020 at 10:53:14 AM UTC-7 Mark Farnan wrote:
> Little old, but this might also help. 
> 
>  A while back I built a helper package to deal with these issues for canvas 
> rendering from Go.  
> 
> https://github.com/markfarnan/go-canvas 
>   
> 
> I'm currently working on it to add  WebGL support & get it working in TinyGo 
> (some issues still).
> 
> Regards
> 
> Mark.
> 
> 
> 
> On Sunday, 22 March 2020 at 09:08:40 UTC+1 Scott Pakin wrote:
> I figure I ought to follow up with some results.  First, I got the suggested 
> approach of local render + js.CopyBytesToJS 
>  + update canvas from image 
> to work, so thanks, Agniva and Howard!  Second, for the benefit of future 
> readers of this thread, one thing that wasn't obvious to me is that one needs 
> to render the image data in a browser-recognizable image format—I used PNG 
> —not raw {red, 
> green, blue, alpha} bytes as is needed when writing directly to a canvas 
> 's image data.  Third, I used 
> JavaScript code like the following to update an invisible img 
>  then copy the image data from 
> there to a visible canvas:
> 
> function copyBytesToCanvas(data) {
> let blob = new Blob([data], {"type": "image/png"});
> let img = document.getElementById("myImage");
> img.onload = function() {
> let canvas = document.getElementById("myCanvas");
> let ctx = canvas.getContext("2d");
> ctx.drawImage(this, 0, 0);
> };
> img.src = URL.createObjectURL(blob);
> }
> 
> Fourth, the performance is indeed substantially faster than my previous 
> approach based on using SetIndex 
>  to write directly to the 
> canvas, even though the new approach requires the extra steps of encoding the 
> image in PNG format and copying the image data from an img to a canvas.  The 
> following performance data, measured with Go 1.14 and Chromium 80.0.3987.132 
> on an Ubuntu Linux system, is averaged over 10 runs:
> 
> Old: 686.9 ± 7.6 ms
> New: 290.4 ± 4.1 ms (284.3 ± 4.2 on the WebAssembly side plus 6.0 ± 2.3 on 
> the JavaScript side)
> 
> This is the time to render a simple 800×800 gradient pattern.
> 
> I hope others find this useful.
> 
> — Scott
> 
> -- 
> You received this message because you are subscribed to the Google Groups 
> "golang-nuts" group.
> To unsubscribe from this group and stop receiving emails from it, send an 
> email to golang-nuts+unsubscr...@googlegroups.com 
> .
> To view this discussion on the web visit 
> 

[go-nuts] Re: Writing bitmap data to a canvas with WebAssembly

2023-11-07 Thread Kai O'Reilly
For future reference for anyone who comes across this thread, you can 
directly pass RGBA image data to a canvas, which is around 200 times faster 
than encoding it to a PNG and rendering it to an offscreen image. Also, 
passing the unsafe pointer to the slice to JavaScript instead of copying 
the bytes improves performance by another factor of 5. These two changes 
combined reduced the render time for me from roughly 300 milliseconds to 
300 microseconds (a 1000x improvement). This performance is enough to run 
an interactive pure Go GUI through WASM without any noticeable lag, just by 
writing images to a canvas.

This is the relevant Go code:

```go
 sz := dw.image.Bounds().Size()
ptr := uintptr(unsafe.Pointer([0]))
js.Global().Call("displayImage", ptr, len(dw.image.Pix), sz.X, sz.Y)
```

And this is the relevant JS code:

```js
const appCanvas = document.getElementById('app');
const appCanvasCtx = appCanvas.getContext('2d');

let wasm;
let memoryBytes;

// displayImage takes the pointer to the target image in the wasm linear 
memory
// and its length. Then, it gets the resulting byte slice and creates an 
image data
// with the given width and height.
function displayImage(pointer, length, w, h) {
  // if it doesn't exist or is detached, we have to make it
  if (!memoryBytes || memoryBytes.byteLength === 0) {
memoryBytes = new Uint8ClampedArray(wasm.instance.exports.mem.buffer);
  }

  // using subarray instead of slice gives a 5x performance improvement due 
to no copying
  let bytes = memoryBytes.subarray(pointer, pointer + length);
  let data = new ImageData(bytes, w, h);
  appCanvasCtx.putImageData(data, 0, 0);
}
```
In the JS code, `wasm` is initialized as the result of 
`WebAssembly.instantiateStreaming`.

On Friday, July 24, 2020 at 10:53:14 AM UTC-7 Mark Farnan wrote:

> Little old, but this might also help. 
>
>  A while back I built a helper package to deal with these issues for 
> canvas rendering from Go.  
>
> https://github.com/markfarnan/go-canvas  
>
> I'm currently working on it to add  WebGL support & get it working in 
> TinyGo (some issues still).
>
> Regards
>
> Mark.
>
>
>
> On Sunday, 22 March 2020 at 09:08:40 UTC+1 Scott Pakin wrote:
>
>> I figure I ought to follow up with some results.  First, I got the 
>> suggested approach of local render + js.CopyBytesToJS 
>>  + update canvas from 
>> image to work, so thanks, Agniva and Howard!  Second, for the benefit of 
>> future readers of this thread, one thing that wasn't obvious to me is that 
>> one needs to render the image data in a browser-recognizable image format—I 
>> used PNG —*not* 
>> raw {red, green, blue, alpha} bytes as is needed when writing directly to a 
>> canvas 's image data.  
>> Third, I used JavaScript code like the following to update an invisible 
>> img  then copy the image 
>> data from there to a visible canvas:
>>
>> function copyBytesToCanvas(data) {
>> let blob = new Blob([data], {"type": "image/png"});
>> let img = document.getElementById("myImage");
>> img.onload = function() {
>> let canvas = document.getElementById("myCanvas");
>> let ctx = canvas.getContext("2d");
>> ctx.drawImage(this, 0, 0);
>> };
>> img.src = URL.createObjectURL(blob);
>> }
>>
>> Fourth, the performance is indeed substantially faster than my previous 
>> approach based on using SetIndex 
>>  to write directly to 
>> the canvas, even though the new approach requires the extra steps of 
>> encoding the image in PNG format and copying the image data from an img 
>> to a canvas.  The following performance data, measured with Go 1.14 and 
>> Chromium 80.0.3987.132 on an Ubuntu Linux system, is averaged over 10 
>> runs:
>>
>> *Old*: 686.9 ± 7.6 ms
>> *New*: 290.4 ± 4.1 ms (284.3 ± 4.2 on the WebAssembly side plus 6.0 ± 
>> 2.3 on the JavaScript side)
>>
>> This is the time to render a simple 800×800 gradient pattern.
>>
>> I hope others find this useful.
>>
>> — Scott
>>
>

-- 
You received this message because you are subscribed to the Google Groups 
"golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to golang-nuts+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/golang-nuts/267bc2d7-1e7a-4704-98cd-3c471fa0d19dn%40googlegroups.com.


[go-nuts] [security] Go 1.21.4 and Go 1.20.11 are released

2023-11-07 Thread announce
Hello gophers,

We have just released Go versions 1.21.4 and 1.20.11, minor point releases.

These minor releases include 2 security fixes following the security policy 
:

-   path/filepath: recognize \\??\\ as a Root Local Device path prefix.

On Windows, a path beginning with \\??\\ is a Root Local Device path 
equivalent
to a path beginning with ?\\. Paths with a \\??\\ prefix may be 
used to
access arbitrary locations on the system. For example, the path 
\\??\\c:\\x
is equivalent to the more common path c:\\x.

The filepath package did not recognize paths with a \\??\\ prefix as 
special.

Clean could convert a rooted path such as \\a\\..\\??\\b into
the root local device path \\??\\b. It will now convert this
path into .\\??\\b.

IsAbs did not report paths beginning with \\??\\ as absolute.
It now does so.

VolumeName now reports the \\??\\ prefix as a volume name.

Join(\`\\\`, \`??\`, \`b\`) could convert a seemingly innocent
sequence of path elements into the root local device path
\\??\\b. It will now convert this to \\.\\??\\b.

This is CVE-2023-45283 and https://go.dev/issue/63713.

-   path/filepath: recognize device names with trailing spaces and 
superscripts

The IsLocal function did not correctly detect reserved names in some 
cases:

-   reserved names followed by spaces, such as "COM1 ".

-   "COM" or "LPT" followed by a superscript 1, 2, or 3.

IsLocal now correctly reports these names as non-local.

This is CVE-2023-45284 and https://go.dev/issue/63713.

View the release notes for more information:
https://go.dev/doc/devel/release#go1.21.4

You can download binary and source distributions from the Go website:
https://go.dev/dl/

To compile from source using a Git clone, update to the release with
git checkout go1.21.4 and build as usual.

Thanks to everyone who contributed to the releases.

Cheers,
Heschi and Cherry for the Go team

-- 
You received this message because you are subscribed to the Google Groups 
"golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to golang-nuts+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/golang-nuts/MboX9kNFSYWoPG2_-FRGSw%40geopod-ismtpd-2.


[go-nuts] Re: Why golang's garbage collector didn't free obj when an finalizer is associate with it?

2023-11-07 Thread 'Michael Knyszek' via golang-nuts


On Tuesday, November 7, 2023 at 2:32:28 AM UTC-5 Jan wrote:

Btw, I don't follow the sentence:

"the GC necessarily has to keep referents of the object-to-be-finalized 
live even if the object isn't referenced anymore"

That is true for objects not in cycles that need to be finalized as well. 
I'm not sure I follow the reasoning here ...

That fact alone isn't enough. When you couple that fact with the fact that 
cycles can be arbitrarily deep, then you have a problem.

Sorry, this is perhaps a weird framing since it's based on the 
implementation details. Where I'm starting from is the example at the top 
of this post, which is a cycle that has only a single finalizer. The point 
I'm trying to make is that even having just a single finalizer in a cycle 
is a problem. AFAICT, the dependency rules that both the Go GC and the 
Boehm GC state could still be respected if each strongly connected 
component of objects had just a single finalizer, since that entire 
component could be finalized and freed before the next, and the ordering of 
finalizers is preserved. (If I'm wrong about this point, disregard me. I'm 
pretty sure but not certain that one could design a GC that did this.) But 
having to identify such connected components would be a significant 
performance limitation.


Asking about it in Bard , it explains:

" 
In Go, objects that are in a cyclic structure and that are marked with a 
finalizer (with SetFinalizer) don't get garbage collected when there are no 
more live pointers to the cyclic structure because the garbage collector 
cannot determine a safe order to run the finalizers.
"

Which seems to match the  Boehm collector 
 explanation, described 
under "Topologically Ordered Finalization".

That is also true, but AFAICT only really applies to cycles containing more 
than one finalizer. If a cycle (and I think more specifically, a strongly 
connected component of objects) has just one finalizer, one could argue 
such an ordering problem doesn't exist (unless I'm missing something of 
course). Nonetheless, the cycle still isn't collected.


On Tuesday, November 7, 2023 at 3:51:58 AM UTC+1 Michael Knyszek wrote:

Yes, cycles containing a finalizer aren't guaranteed to be freed. As others 
have pointed out, this is documented. SetFinalizer is really designed for a 
narrow set of use-cases, so concessions were made for overall GC 
performance. This case is one of them.

IIUC, the core problem is a combination of the fact that the cycle can be 
arbitrarily deep and the GC necessarily has to keep referents of the 
object-to-be-finalized live even if the object isn't referenced anymore. 
The GC must follow the pointers in an object's referents, and eventually it 
may come upon the almost-dead object it started from. But at that point it 
likely has no knowledge of where it came from. It's just a pointer on a 
queue. A GC could be implemented that keeps track of where the pointers it 
follows came from, but such an implementation would be substantially less 
performant.

Other GCs make the same choice. See the Boehm collector 
, for example.
On Monday, November 6, 2023 at 10:20:39 AM UTC-5 Harish Ganesan wrote:

Does this behaviour mean that, those memory will never be freed and keep 
piling on ? That can be disastrous.

On Monday, November 6, 2023 at 3:39:50 PM UTC+5:30 Jan wrote:

For what it's worth, a bit of "memory management" on structures in many 
cases is very ok (not sure if in your case). So for your cyclic structure 
with finalizers, requiring the user of your code to call some "Finalize()" 
method (some destructor method you define) that manually breaks the cycle, 
often is an ok solution. Fundamentally, it's the same as requiring someone 
to call Close() on an opened file (or any other resource, like sockets, db 
connections, etc).

As an anecdote, previously when I was doing C++ I was a big fan of 
referenced counted smart pointers (`shred_ptr<>`), which gets most of the 
benefit of GC, but with a much lower cost. They required manually breaking 
cycles, which I didn't find to be an issue at all in the great majority of 
the cases.

On Monday, November 6, 2023 at 11:01:04 AM UTC+1 Jan wrote:

I was very surprised by this behavior of SetFinalizer: and indeed if you 
remove the SetFinalizer one can see that s1 is freed, because the memory 
reported by `m.HeapAlloc` goes back down.

I think you already have the answer: the block that has the cycle (s1 and 
s2) have a SetFinalizer set, and it will never run, per documentation (I 
had never paid attention to this).

A suggestion to work around would be to move the stuff that needs a 
"Finalizer" to a leaf node, as in:

https://go.dev/play/p/WMMTdAza6aZ

But I understand this is not a solution if the finalizer needs to access 
the S1 (so, if the finalizer function needs any information that is not 
self-contained in `S1Data` 

[go-nuts] Re: Why golang's garbage collector didn't free obj when an finalizer is associate with it?

2023-11-07 Thread Harish Ganesan
I can completely understand the logic behind not freeing the objects with 
unclear ordering. But I expected that there will be some kind of upper 
limit / workaround for these kind of objects. But this means that using 
SetFinalizer on a big object, carelessly, can potentially lead to memory 
leaks.

I guess if we provide a destructor( like file.Close() or something like 
that ) for those objects, which just breaks the cycle, it will probably be 
enough to force the GC to clear the memory.

On Tuesday, November 7, 2023 at 8:21:58 AM UTC+5:30 Michael Knyszek wrote:

> Yes, cycles containing a finalizer aren't guaranteed to be freed. As 
> others have pointed out, this is documented. SetFinalizer is really 
> designed for a narrow set of use-cases, so concessions were made for 
> overall GC performance. This case is one of them.
>
> IIUC, the core problem is a combination of the fact that the cycle can be 
> arbitrarily deep and the GC necessarily has to keep referents of the 
> object-to-be-finalized live even if the object isn't referenced anymore. 
> The GC must follow the pointers in an object's referents, and eventually it 
> may come upon the almost-dead object it started from. But at that point it 
> likely has no knowledge of where it came from. It's just a pointer on a 
> queue. A GC could be implemented that keeps track of where the pointers it 
> follows came from, but such an implementation would be substantially less 
> performant.
>
> Other GCs make the same choice. See the Boehm collector 
> , for example.
> On Monday, November 6, 2023 at 10:20:39 AM UTC-5 Harish Ganesan wrote:
>
>> Does this behaviour mean that, those memory will never be freed and keep 
>> piling on ? That can be disastrous.
>>
>> On Monday, November 6, 2023 at 3:39:50 PM UTC+5:30 Jan wrote:
>>
>>> For what it's worth, a bit of "memory management" on structures in many 
>>> cases is very ok (not sure if in your case). So for your cyclic structure 
>>> with finalizers, requiring the user of your code to call some "Finalize()" 
>>> method (some destructor method you define) that manually breaks the cycle, 
>>> often is an ok solution. Fundamentally, it's the same as requiring someone 
>>> to call Close() on an opened file (or any other resource, like sockets, db 
>>> connections, etc).
>>>
>>> As an anecdote, previously when I was doing C++ I was a big fan of 
>>> referenced counted smart pointers (`shred_ptr<>`), which gets most of the 
>>> benefit of GC, but with a much lower cost. They required manually breaking 
>>> cycles, which I didn't find to be an issue at all in the great majority of 
>>> the cases.
>>>
>>> On Monday, November 6, 2023 at 11:01:04 AM UTC+1 Jan wrote:
>>>
 I was very surprised by this behavior of SetFinalizer: and indeed if 
 you remove the SetFinalizer one can see that s1 is freed, because the 
 memory reported by `m.HeapAlloc` goes back down.

 I think you already have the answer: the block that has the cycle (s1 
 and s2) have a SetFinalizer set, and it will never run, per documentation 
 (I had never paid attention to this).

 A suggestion to work around would be to move the stuff that needs a 
 "Finalizer" to a leaf node, as in:

 https://go.dev/play/p/WMMTdAza6aZ

 But I understand this is not a solution if the finalizer needs to 
 access the S1 (so, if the finalizer function needs any information that is 
 not self-contained in `S1Data` in my example).
 On Sunday, November 5, 2023 at 4:01:14 PM UTC+1 Soren Yang wrote:

> As shown in the following code:
>
> cyclic structure with finalizer 
>
> The s1 & s2 didn't been free, and finalizer didn't run. But when 
> enable the line which have been commented, all run as expected(s1 & s2 
> been 
> free)。
>
> I have seen the comment in runtime.SetFinalizer: If a cyclic 
> structure includes a block with a finalizer, that cycle is not guaranteed 
> to be garbage collected and the finalizer is not guaranteed to run, 
> because 
> there is no ordering that respects the dependencies.
>
> But why it haven't been free in first code?
>


-- 
You received this message because you are subscribed to the Google Groups 
"golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to golang-nuts+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/golang-nuts/6dda0d21-bd9f-45eb-bade-f20ac0996f45n%40googlegroups.com.