Skip to content
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

feat(docs): map.deepEquals() #939

Merged
merged 5 commits into from
Oct 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
47 changes: 47 additions & 0 deletions docs/src/content/docs/book/maps.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,51 @@ if (fizz == null) {
}
```

### Compare with `.deepEquals()` {#deepequals}

<Badge text="Available since Tact 1.5" variant="tip" size="medium"/><p/>

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<Int, Int> = emptyMap();
let buzz: map<Int, Int> = 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.
Expand Down Expand Up @@ -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
12 changes: 10 additions & 2 deletions docs/src/content/docs/book/operators.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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<K, V>{:tact}`](/book/maps), but only if their key and value types are identical
* [Optionals and `null{:tact}` value](/book/optionals)
Expand Down Expand Up @@ -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.
Expand Down
Loading