-
Notifications
You must be signed in to change notification settings - Fork 17.9k
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: slices: MutableValues iterator #70813
Comments
Related Issues
(Emoji vote if this was helpful or unhelpful; more detailed feedback welcome in this discussion.) |
@pkierski |
This is just a restatement of #69916 without addressing any of the concerns in the comments. I don't think we need to rehash the discussion. |
I think @pkierski well addresses the concerns in the first comment. The other two haven't undergone the proposal process yet. |
Kindly re-ping. This alike proposals have never been accepted or declined. Do you think @seankhliao have the right to close it? |
OK, I'll reopen it to follow the process, but I think the comments are fair: this proposal does not appear to address the shortcomings pointed out for the earlier proposals. Without doing that the proposal review committee is just going to close it. In particular the behavior if the slice changes during iteration is going to be unclear and surprising. |
I don't see any valid objection reasons there. Those reasons look not iterator specific. They also apply to traditional loops. |
The objection is not that it isn't sometimes useful, but that the existence of a standard library function decreases safety by making a footgun more accessible, and encourages a poor choice of slice element types. |
As I have stated in my last comment, the so-called "footgun" exists before Go 1.23, which introduced iterators. |
The baseline without this proposal is (as shown in the proposal text) a loop which begins by immediately taking the address of the current element: for i := range example {
e := &example[i]
// and then, presumably, mutate what e points at
} The net effect of this proposal is to move the for i, e := range slices.MutableValues(example) {
// and then, presumably, mutate what e points at
} It seems like this is a classic tension between convenience and explicitness. The original example makes it relatively clear in the loop body that The proposed example moves that information slightly further out of reach: the reader must now understand what The way I understood the concerns in #69916 was that they were imagining someone maintaining some code that is already using this helper function but doesn't notice that It's always tricky to trade off a concrete benefit against a hypothetical hazard, but perhaps we can sidestep that by asking a more straightforward question: is the benefit of eliminating that single statement great enough to justify any language or library change at all? As with the previous proposal, for me the primary concern is helping an author discover that they are mistakenly modifying a value that will be immediately discarded at the end of the loop iteration, which caused me to suggest a However, I wouldn't be particularly upset if this proposal were accepted. I don't personally think it pulls its weight in terms of overall value provided, but it also seems like a relatively straightforward function that's unlikely to need significant maintenance once it's been added. 🤷♂️ |
The proposal has a big benefit. For a slice |
The proposal doesn't include any examples of functions that require sequences of pointers in particular. I expect it would help to add some realistic examples of such situations if that's a significant part of the value proposition. |
I agree that in simple use case (single loop) this is not worth to add new method like that. But for more complicated case like nested loops tratidional way is more complicated and not so easy to read: type (
element struct {
field int
innerElements []innerElement
}
innerElement struct {
innerField int
}
)
var (
srcElements = []element{
{
field: 1,
innerElements: []innerElement{
{innerField: 1},
{innerField: 2},
},
},
{
field: 1,
innerElements: []innerElement{
{innerField: 3},
{innerField: 4},
},
},
{
field: 1,
innerElements: []innerElement{
{innerField: 5},
{innerField: 6},
},
},
}
doubledElements = []element{
{
field: 1,
innerElements: []innerElement{
{innerField: 2},
{innerField: 4},
},
},
{
field: 1,
innerElements: []innerElement{
{innerField: 6},
{innerField: 8},
},
},
{
field: 1,
innerElements: []innerElement{
{innerField: 10},
{innerField: 12},
},
},
}
)
func TestDoubleWithRangeAndIndex(t *testing.T) {
elements := slices.Clone(srcElements)
// change base elements without MutableValues
// using indexing
for i, e := range elements {
for ii, ie := range e.innerElements {
elements[i].innerElements[ii].innerField = ie.innerField * 2
// alternative for mutating more than one inner field:
// e.innerElements[ii].innerField = ie.innerField * 2
}
// alternative for mutating more than one inner field:
//elements[i] = e
}
if !reflect.DeepEqual(elements, doubledElements) {
t.Fail()
}
}
func TestDoubleWithRangeAndPointer(t *testing.T) {
elements := slices.Clone(srcElements)
// change base elements without MutableValues
// using pointer to element (useful in case of neested loops)
for i := range elements {
e := &elements[i]
for ii := range e.innerElements {
ie := &e.innerElements[ii]
ie.innerField *= 2
}
}
if !reflect.DeepEqual(elements, doubledElements) {
t.Fail()
}
}
func TestDoubleWithMutableValuesIterator(t *testing.T) {
elements := slices.Clone(srcElements)
// values in elements will be changed
for e := range slices.MutableValues(elements) {
for ie := range slices.MutableValues(e.innerElements) {
ie.innerField *= 2
// changing other fields is also easy here
}
}
if !reflect.DeepEqual(elements, doubledElements) {
t.Fail()
}
} |
I want to address it again. Iterators are not only to shorten loop code. They certainly have other meaningfulness. Otherwise, the following APIs in the std library are totally meaningless (they don't shorten loop code): slices.All
slices.Values
maps.All
maps.Keys
maps.Values |
Proposal Details
It's a common task to iterate over a slice and modify its elements. For the following structure:
we have to use one of:
or
The proposal is to add
MutableValues
iterator toslices
package:Mutating of all elements of the slice can looks like:
The text was updated successfully, but these errors were encountered: