Add withValue for immutable tables (#24825)

This change adds `withValue` templates for the `Table` type that are
able to operate on immutable table values -- the existing implementation
requires a `var`.

This is needed for situations where performance is sensitive. There are
two goals with my implementation:

1. Don't create a copy of the value in the table. That's why I need the
`cursor` pragma. Otherwise, it would copy the value
2. Don't double calculate the hash. That's kind of intrinsic with this
implementation. But the only way to achieve this without this PR is to
first check `if key in table` then to read `table[key]`

I brought this up in the discord and a few folks tried to come up with
options that were as fast as this, but nothing quite matched the
performance here. Thread starts here:
https://discord.com/channels/371759389889003530/371759389889003532/1355206546966974584
This commit is contained in:
James
2025-03-29 15:08:45 -07:00
committed by GitHub
parent e0a4876981
commit 0f5732bc8c

View File

@@ -676,6 +676,68 @@ template withValue*[A, B](t: var Table[A, B], key: A,
else:
body2
template withValue*[A, B](t: Table[A, B], key: A,
value, body1, body2: untyped) =
## Retrieves the value at `t[key]` if it exists, assigns
## it to the variable `value` and executes `body`
runnableExamples:
type
User = object
name: string
proc `=copy`(dest: var User, source: User) {.error.}
proc exec(t: Table[int, User]) =
t.withValue(1, value):
assert value.name == "Hello"
do:
doAssert false
var executedElseBranch = false
t.withValue(521, value):
doAssert false
do:
executedElseBranch = true
assert executedElseBranch
var t = initTable[int, User]()
t[1] = User(name: "Hello")
t.exec()
mixin rawGet
var hc: Hash
var index = rawGet(t, key, hc)
if index > 0:
let value {.cursor, inject.} = t.data[index].val
body1
else:
body2
template withValue*[A, B](t: Table[A, B], key: A,
value, body: untyped) =
## Retrieves the value at `t[key]` if it exists, assigns
## it to the variable `value` and executes `body`
runnableExamples:
type
User = object
name: string
proc `=copy`(dest: var User, source: User) {.error.}
proc exec(t: Table[int, User]) =
t.withValue(1, value):
assert value.name == "Hello"
t.withValue(521, value):
doAssert false
var t = initTable[int, User]()
t[1] = User(name: "Hello")
t.exec()
withValue(t, key, value, body):
discard
iterator pairs*[A, B](t: Table[A, B]): (A, B) =
## Iterates over any `(key, value)` pair in the table `t`.