From 210f747596f7ed99f8302bdb83bfd832f42ea4ae Mon Sep 17 00:00:00 2001 From: James Date: Sat, 29 Mar 2025 15:08:45 -0700 Subject: [PATCH] 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 (cherry picked from commit 0f5732bc8c35b8f11b55d34da1cbd3b3937b6f4d) --- lib/pure/collections/tables.nim | 62 +++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/lib/pure/collections/tables.nim b/lib/pure/collections/tables.nim index 6ead190962..300e711cc9 100644 --- a/lib/pure/collections/tables.nim +++ b/lib/pure/collections/tables.nim @@ -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`.