Skip to content
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

proposal: Go 2: spec: allow conversion from *T to *[1]T #45545

Closed
betawaffle opened this issue Apr 13, 2021 · 6 comments
Closed

proposal: Go 2: spec: allow conversion from *T to *[1]T #45545

betawaffle opened this issue Apr 13, 2021 · 6 comments

Comments

@betawaffle
Copy link

@betawaffle betawaffle commented Apr 13, 2021

To go along with the already accepted #395, I would love to see support for converting/aliasing a pointer of some type, to a single-element array pointer of the same type.

The main use-case would be passing an already-allocated single item to a function that accepts a slice of those items, avoiding an extra allocation and copy.

Another place this might be useful is in the reflect package, which mentions issue #2320.

Edit: Below are my answers to the questions from the Go 2 language change template.

Would you consider yourself a novice, intermediate, or experienced Go programmer?

Experienced.

What other languages do you have experience with?

None quite as much as Go at this point, but (to varying degrees): Rust, Zig, Ruby, C, Erlang, and Haskell.

Would this change make Go easier or harder to learn, and why?

It wouldn't make it easier, but I don't think it would make it harder either, unless you count learning obscure features of a language.

Has this idea, or one like it, been proposed before? If so, how does this proposal differ?

I'm not aware of any proposal exactly like this, but there are some related (and already accepted) proposals. Primarily #395, and to some extent #19367. The former was the inspiration for this proposal, and the latter would allow this to be done with less-nasty unsafe code than before.

The difference here is that Go would allow converting between these pointer types without unsafe, since there shouldn't really be anything unsafe about it.

Who does this proposal help, and why?

This proposal helps in performance-sensitive code where allocations are undesirable. When you already have a pointer to a heap-allocated value, and you need a slice (because a function signature demands it), you can avoid allocating space for a single-element slice and copying the value to it.

The only way to do this now is by using unsafe. Alternatively, you could work with a *[1]T instead of a *T from the beginning, but that isn't very ergonomic, and wouldn't be a viable option in the public interface of a library.

What is the proposed change?

The proposal is to allow unsafe-free conversion (or aliasing) of a pointer of some type, to an array pointer with a single element of the same type. You could then slice that array pointer.

Alternatively, we could allow converting to a slice directly (since *[1]T is rarely useful by itself), but that seems less general.

Please describe as precisely as possible the change to the language.

I'm not sure how to answer this any differently from my answer to the previous question.

What would change in the language spec?

Probably just adding a new item in the Conversions section where says:

A non-constant value x can be converted to type T in any of these cases.

Please also describe the change informally, as in a class teaching Go.

Yikes, I'll have to think about this one a bit.

Is this change backward compatible?

Yes. All code that was valid before will remain valid. Some code that would have required unsafe before will no longer require it.

Show example code before and after the change.

Before:

func ExamplePointer(ptr *SomeType) {
    ExampleSlice([]SomeType{*ptr})
}

func ExampleSlice(slice []SomeType) {
    for i := range slice {
        // ... do something with &slice[i]
    }
}

After:

func ExamplePointer(ptr *SomeType) {
    ExampleSlice((*[1]SomeType)(ptr)[:])
}

func ExampleSlice(slice []SomeType) {
    // Same as before.
}

Yes, it's longer and uglier, but also doesn't allocate.

What is the cost of this proposal? (Every language change has a cost).

I don't really know. I haven't implemented a language change and I'm not very familiar with the code that would need to be changed in the compiler. The documentation cost should be relatively small.

How many tools (such as vet, gopls, gofmt, goimports, etc.) would be affected?

I think the compiler would have given an error before this change, and thus vet would not likely need any logical changes. Beyond that, I'm not really sure.

What is the compile time cost?

It should be negligible.

What is the run time cost?

Where it is used, it can speed up execution slightly by avoiding unnecessary allocations. Otherwise, it has no impact.

Can you describe a possible implementation?

Nope. I'm just not familiar enough with the compiler.

Do you have a prototype? (This is not required.)

No, but you can achieve this with some rather nasty unsafe right now.

How would the language spec change?

I think I already answered this question above.

Orthogonality: how does this change interact or overlap with existing features?

The change would overlap somewhat with #19367 (accepted proposal, not exactly existing), except that it wouldn't require unsafe.

Is the goal of this change a performance improvement?

Yes. Strictly performance.

If so, what quantifiable improvement should we expect?

Fewer allocations and copying. It would be more noticeable if T is large.

How would we measure it?

A go test micro-benchmark.

Does this affect error handling?

No.

If so, how does this differ from previous error handling proposals?

Is this about generics?

No.

If so, how does this differ from the the current design draft and the previous generics proposals?

@gopherbot gopherbot added this to the Proposal milestone Apr 13, 2021
@seankhliao seankhliao changed the title Proposal: Allow conversion from *T to *[1]T proposal: spec: allow conversion from *T to *[1]T Apr 13, 2021
@ianlancetaylor
Copy link
Contributor

@ianlancetaylor ianlancetaylor commented Apr 13, 2021

I don't think this idea would help the reflect package. This could reduce allocations in the case where we already have the address of a value, and we need a slice. But the reflect package doesn't have the address of a value, it just has a value (an int). So for the reflect package case an allocation needs to occur no matter what.

@ianlancetaylor ianlancetaylor changed the title proposal: spec: allow conversion from *T to *[1]T proposal: Go 2: spec: allow conversion from *T to *[1]T Apr 13, 2021
@ianlancetaylor
Copy link
Contributor

@ianlancetaylor ianlancetaylor commented Apr 13, 2021

For language change proposals, please fill out the template at https://go.googlesource.com/proposal/+/refs/heads/master/go2-language-changes.md .

When you are done, please reply to the issue with @gopherbot please remove label WaitingForInfo.

Thanks!

@betawaffle
Copy link
Author

@betawaffle betawaffle commented Apr 13, 2021

@gopherbot please remove label WaitingForInfo

@ianlancetaylor
Copy link
Contributor

@ianlancetaylor ianlancetaylor commented May 4, 2021

In Go 1.17 we will introduce a new function unsafe.Slice that takes a pointer and a length, so we can now generate a single element slice from a pointer by writing unsafe.Slice(p, 1). That does require importing the unsafe package, but it seems to support the same functionality.

(With generics we could introduce a generic function that does this, to push the import of unsafe into that package.)

Therefore, this is a likely decline. Leaving open for four weeks for final comments.

@rogpeppe
Copy link
Contributor

@rogpeppe rogpeppe commented May 5, 2021

In Go 1.17 we will introduce a new function unsafe.Slice that takes a pointer and a length, so we can now generate a single element slice from a pointer by writing unsafe.Slice(p, 1). That does require importing the unsafe package, but it seems to support the same functionality.

For the record, for a notionally zero-cost operation, this could be made more efficient.
The generated machine code for the below code (as of 9e0facd) contains an explicit call to runtime.unsafeslice which, ISTM, should probably be inlined:

package main

import "unsafe"

var _t int

func main() {
	x := 1233
	y := asArray(&x)
	y[0] = 99
	_t = x
}

func asArray(x *int) *[1]int {
	if x == nil {
		return nil
	}
	return (*[1]int)(unsafe.Slice(x, 1))
}
@ianlancetaylor
Copy link
Contributor

@ianlancetaylor ianlancetaylor commented Jun 1, 2021

No change in consensus.

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

Successfully merging a pull request may close this issue.

None yet
4 participants