-
Notifications
You must be signed in to change notification settings - Fork 17.7k
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: builtin: delete should return the deleted map item #51405
Comments
What happens if
Could you show how common it is statistically? One single pattern may not be strong enough to support the change. There was a similar discussion regarding delete in #41130. |
Optional comma-ok like a map read? v, ok := delete(m, k) |
If I don't have useful numbers right now, but this pattern often appears in cleanup routine in order to re-use a map. #41130 was obviously flawed so not worth further discussion. #5147 could be an alternative but it's not looking very promising. |
@ydnar's proposal is also nice so it's similar to map access. |
The author's usage of |
For a case like for k := range m {
doSomethingWith(m[k])
delete(m, k)
} you could instead write for k, v := range m {
doSomethingWith(v)
}
m = nil So while the idea makes sense the example is not wholly convincing. If the main concern is the double hashing, then perhaps the compiler can detect this kind of pattern. It does already detect some map range patterns. See also #5147. |
Double hashing is indeed the primary concern, but it is not as clean/efficient, as explained in #5147 (comment) by @bradfitz:
I agree the original example isn't ideal. There're common cases where you don't clear the whole map, e.g. for k := range m {
if someConditionMatches(k) {
doSomethingWith(m[k])
delete(m, k) // double hashing & bucket lookup
}
} vs for k := range m {
if someConditionMatches(k) {
doSomethingWith(delete(m, k)) // single hashing & bucket lookup
}
} |
I get that you can use faster code if delete doesn't return anything but I don't get why that's relevant here. The compiler knows whether the returns are used so couldn't it emit the slow delete code when they are used and the fast delete code when they are not? Like: delete(m, k) // -> fastDelete(m, k)
v, ok := delete(m, k) // -> v, ok := slowDelete(m, k) That could mean having two delete algorithms but the first version of slowDelete could just be the equivalent of func slowDelete[M ~map[K]V, K comparable, V any](m M, k K) (V, bool) {
v, ok := m[k]
delete(m, k)
return v, ok
} Any improvements above that would be an optimization. As just demonstrated, with generics, it's easy to write your own delete that returns, though. It's possible that more general optimizations could make that just as fast as if the builtin directly supported this operation. At that point the major win for the builtin is that it could be called with 0, 1, or 2 returns instead of 0 or 2 returns. But you could of course write 3 variants of the generic delete to cover (V, bool), V, and bool returns. So extending the builtin with a comma-ok return would make it more convenient and probably simpler to optimize in the short term. Is that worth it? I don't know. Maybe it would suffice to add a function or two to the maps package. |
@riobard The first example in #51405 (comment) isn't all that convincing because it would normally be written as for k, v := range m {
if someConditionMatches(k) {
doSomethingWith(v)
delete(m, k) // double hashing & bucket lookup
}
} which does not require double hashing. |
@ianlancetaylor Ah stupid me! I completely forgot about the optional value from I guess then the returning-value |
@jimmyfrasche Is the current |
I've updated the proposal based on the discussion so far for clarity. |
We think we should explore compiler optimizations to avoid the double hashing, and only reach for a language change if that seems impossible. It's true that the map data structure has some optimizations for small maps, but those can in principle be replicated by the compiler's internal ABI as well. It's also unclear how often this case arises. For example, would any code in the standard library be better/easier to read if we had this facility? |
Based on the discussion above this is a likely decline in favor of looking into compiler optimizations. Leaving open for four weeks for final comments. |
No further comments. |
Currently the builtin
delete
function has the signatureA common usage pattern is to get an item from a map and then remove it after processing:
which involves hashing
k
and locating it inm
twice.If
delete
is changed tothen getting an item and removing it from a map can be done in one step:
This is similar to Python's
dict.pop(key)
method. The proposal changes the builtin function's API but I don't think it would break any existing code, so Go1 compatibility should not be an obstacle.Additionally, as suggested below by @ydnar,
delete
could be further enhanced with comma-ok pattern:The text was updated successfully, but these errors were encountered: