diff --git a/CHANGELOG.md b/CHANGELOG.md index df5c4f98b..088f14f2c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -65,7 +65,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - The `storeBit` method for `Builder` type and the `loadBit` method for `Slice` type: PR [#699](https://github.com/tact-lang/tact/pull/699), PR [#936](https://github.com/tact-lang/tact/pull/936) - The `toSlice` method for structs and messages: PR [#630](https://github.com/tact-lang/tact/pull/630), PR [#936](https://github.com/tact-lang/tact/pull/936) - Wider range of serialization options for integers — `uint1` through `uint256` and `int1` through `int257`: PR [#558](https://github.com/tact-lang/tact/pull/558), PR [#937](https://github.com/tact-lang/tact/pull/937) -- The `deepEquals` method for the `Map` type: PR [#637](https://github.com/tact-lang/tact/pull/637) +- The `deepEquals` method for the `Map` type: PR [#637](https://github.com/tact-lang/tact/pull/637), PR [#939](https://github.com/tact-lang/tact/pull/939) - `asm` bodies for module-level functions: PR [#769](https://github.com/tact-lang/tact/pull/769), PR [#825](https://github.com/tact-lang/tact/pull/825) - Corresponding stdlib functions for new TVM instructions from 2023.07 and 2024.04 upgrades: PR [#331](https://github.com/tact-lang/tact/pull/331). Added the `storeBuilder` extension function and `gasConsumed`, `getComputeFee`, `getStorageFee`, `getForwardFee`, `getSimpleComputeFee`, `getSimpleForwardFee`, `getOriginalFwdFee`, `myStorageDue` functions. - `slice`, `rawSlice`, `ascii` and `crc32` built-in functions: PR [#787](https://github.com/tact-lang/tact/pull/787), PR [#799](https://github.com/tact-lang/tact/pull/799) diff --git a/docs/src/content/docs/book/maps.mdx b/docs/src/content/docs/book/maps.mdx index e51e07546..93c20a3f2 100644 --- a/docs/src/content/docs/book/maps.mdx +++ b/docs/src/content/docs/book/maps.mdx @@ -182,6 +182,51 @@ if (fizz == null) { } ``` +### Compare with `.deepEquals()` {#deepequals} + +

+ +The `.deepEquals(){:tact}` [method](/book/functions#extension-function) on maps returns `true{:tact}` if all entries of the map match corresponding entries of another map, ignoring possible differences in the [underlying serialization logic][hashmap]. Returns `false{:tact}` otherwise. + +```tact +let fizz: map = emptyMap(); +let buzz: map = emptyMap(); + +fizz.set(1, 2); +buzz.set(1, 2); + +fizz.deepEquals(buzz); // true +fizz == buzz; // true, and uses much less gas to compute +``` + +Using `.deepEquals(){:tact}` is very important in cases where a map comes from the third-party source that doesn't provide any guarantees about the [serialization layout][hashmap]. For one such example, consider the following code: + +```typescript title="some-typescript-code.ts" +// First map, with long labels +const m1 = beginCell() + .storeUint(2, 2) // long label + .storeUint(8, 4) // key length + .storeUint(1, 8) // key + .storeBit(true) // value + .endCell(); + +// Second map, with short labels +const m2 = beginCell() + .storeUint(0, 1) // short label + .storeUint(0b111111110, 9) // key length + .storeUint(1, 8) // key + .storeBit(true) // value + .endCell(); +``` + +There, both maps are formed manually and both contain the same key-value pair. If you were to send both of those maps in a message to the Tact contract, and then compare them with `.deepEquals(){:tact}` and [equality operator `=={:tact}`](/book/operators#binary-equality), the former would produce `true{:tact}` because both maps have the same entry, while the latter would produce `false{:tact}`, because it only does the shallow comparison of map hashes. And those differ since the maps are serialized differently. + +:::note + + This function is very gas expensive, and for the majority of cases it'll be enough to use the shallow comparison via the [equality `=={:tact}`](/book/operators#binary-equality) or [inequality `!={:tact}`](/book/operators#binary-equality) operators. + +::: + ### Convert to a `Cell`, `.asCell()` {#ascell} Use `.asCell(){:tact}` [method](/book/functions#extension-function) on maps to convert all their values to a [`Cell{:tact}`][cell] type. Be mindful, that [`Cell{:tact}`][cell] type is able to store up to 1023 bits, so converting larger maps to the Cell will result in error. @@ -361,3 +406,5 @@ If you still need a large map or an unbound (infinitely large) map, it's better [p]: /book/types#primitive-types [int]: /book/integers [cell]: /book/cells#cells + +[hashmap]: https://docs.ton.org/develop/data-formats/tl-b-types#hashmap diff --git a/docs/src/content/docs/book/operators.mdx b/docs/src/content/docs/book/operators.mdx index b6b3d6a8e..51b51b252 100644 --- a/docs/src/content/docs/book/operators.mdx +++ b/docs/src/content/docs/book/operators.mdx @@ -385,8 +385,8 @@ Both operators can be applied to the following list of types and values: * [`Int{:tact}`][int] * [`Bool{:tact}`][bool] * [`Address{:tact}`][p] -* [`Cell{:tact}`][cell], implicitly compares via `.hash(){:tact}` -* [`Slice{:tact}`][slice], implicitly compares via `.hash(){:tact}` +* [`Cell{:tact}`][cell], implicitly compares via [`.hash(){:tact}`](/ref/core-cells#cellhash) +* [`Slice{:tact}`][slice], implicitly compares via [`.hash(){:tact}`](/ref/core-cells#slicehash) * [`String{:tact}`][p] * [`map{:tact}`](/book/maps), but only if their key and value types are identical * [Optionals and `null{:tact}` value](/book/optionals) @@ -434,6 +434,14 @@ nullable == anotherNullable; // false nullable != anotherNullable; // true ``` +:::note + + Binary equality `=={:tact}` and inequality `!={:tact}` operators implicitly compare [maps](/book/maps) by the hashes of their respective [cells][cell] via [`.hash(){:tact}`](/ref/core-cells#cellhash) function. While this is ok for the majority of cases because most map serializers work identically to the one from TON Blockchain sources, it's still possible to get false negative results by serializing a map manually or by changing the logic of the serializer in some library. + + If you need to guarantee that maps you're comparing are equal, and you're willing to pay a lot more gas for it, use the [`map.deepEquals(){:tact}`](/book/maps#deepequals) function. + +::: + ### Bitwise AND, `&` {#binary-bitwise-and} Binary ampersand (_bitwise AND_) operator `&{:tact}` applies a [bitwise AND](https://en.wikipedia.org/wiki/Bitwise_operation#AND), which performs the [logical AND](#binary-logical-and) operation on each pair of the corresponding bits of operands. This is useful when we want to clear selected bits off a number, where each bit represents an individual flag or a boolean state, which makes it possible to "store" up to $257$ boolean values per integer, as all integers in Tact are $257$-bit signed.