usearch 1.2.2 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/python/lib.cpp CHANGED
@@ -759,17 +759,86 @@ static void compact_index(dense_index_py_t& index, std::size_t threads) {
759
759
  index.compact(executor_default_t{threads});
760
760
  }
761
761
 
762
+ static py::dict index_metadata(index_dense_metadata_result_t const& meta) {
763
+ py::dict result;
764
+ result["matrix_included"] = !meta.config.exclude_vectors;
765
+ result["matrix_uses_64_bit_dimensions"] = meta.config.use_64_bit_dimensions;
766
+
767
+ index_dense_head_t const& head = meta.head;
768
+ result["version"] = std::to_string(head.version_major) + "." + //
769
+ std::to_string(head.version_minor) + "." + //
770
+ std::to_string(head.version_patch);
771
+
772
+ result["kind_metric"] = metric_kind_t(head.kind_metric);
773
+ result["kind_scalar"] = scalar_kind_t(head.kind_scalar);
774
+ result["kind_key"] = scalar_kind_t(head.kind_key);
775
+ result["kind_compressed_slot"] = scalar_kind_t(head.kind_compressed_slot);
776
+
777
+ result["count_present"] = std::uint64_t(head.count_present);
778
+ result["count_deleted"] = std::uint64_t(head.count_deleted);
779
+ result["dimensions"] = std::uint64_t(head.dimensions);
780
+
781
+ return result;
782
+ }
783
+
762
784
  // clang-format off
763
- template <typename index_at> void save_index(index_at const& index, std::string const& path) { index.save(path.c_str()).error.raise(); }
764
- template <typename index_at> void load_index(index_at& index, std::string const& path) { index.load(path.c_str()).error.raise(); }
765
- template <typename index_at> void view_index(index_at& index, std::string const& path) { index.view(path.c_str()).error.raise(); }
785
+ template <typename index_at> void save_index_to_path(index_at const& index, std::string const& path) { index.save(path.c_str()).error.raise(); }
786
+ template <typename index_at> void load_index_from_path(index_at& index, std::string const& path) { index.load(path.c_str()).error.raise(); }
787
+ template <typename index_at> void view_index_from_path(index_at& index, std::string const& path) { index.view(path.c_str()).error.raise(); }
766
788
  template <typename index_at> void reset_index(index_at& index) { index.reset(); }
767
789
  template <typename index_at> void clear_index(index_at& index) { index.clear(); }
768
790
  template <typename index_at> std::size_t max_level(index_at const &index) { return index.max_level(); }
791
+ template <typename index_at> std::size_t serialized_length(index_at const &index) { return index.serialized_length(); }
769
792
  template <typename index_at> typename index_at::stats_t compute_stats(index_at const &index) { return index.stats(); }
770
793
  template <typename index_at> typename index_at::stats_t compute_level_stats(index_at const &index, std::size_t level) { return index.stats(level); }
771
794
  // clang-format on
772
795
 
796
+ template <typename py_bytes_at> memory_mapped_file_t memory_map_from_bytes(py_bytes_at&& bytes) {
797
+ py::buffer_info info(py::buffer(bytes).request());
798
+ return {(byte_t*)(info.ptr), static_cast<std::size_t>(info.size)};
799
+ }
800
+
801
+ template <typename index_at> py::object save_index_to_buffer(index_at const& index) {
802
+ std::size_t serialized_length = index.serialized_length();
803
+
804
+ // Create an empty bytearray object using CPython API
805
+ PyObject* byte_array = PyByteArray_FromStringAndSize(nullptr, 0);
806
+ if (!byte_array)
807
+ throw std::runtime_error("Could not allocate bytearray object");
808
+
809
+ // Resize the bytearray object to the desired length
810
+ if (PyByteArray_Resize(byte_array, static_cast<Py_ssize_t>(serialized_length)) != 0) {
811
+ Py_XDECREF(byte_array);
812
+ throw std::runtime_error("Could not resize bytearray object");
813
+ }
814
+
815
+ char* buffer = PyByteArray_AS_STRING(byte_array);
816
+ memory_mapped_file_t memory_map((byte_t*)buffer, serialized_length);
817
+ serialization_result_t result = index.save(std::move(memory_map));
818
+
819
+ if (!result) {
820
+ Py_XDECREF(byte_array);
821
+ result.error.raise();
822
+ }
823
+
824
+ return py::reinterpret_steal<py::object>(byte_array);
825
+ }
826
+
827
+ template <typename index_at> void load_index_from_buffer(index_at& index, py::bytes const& buffer) {
828
+ index.load(memory_map_from_bytes(buffer)).error.raise();
829
+ }
830
+ template <typename index_at> void view_index_from_buffer(index_at& index, py::bytes const& buffer) {
831
+ index.view(memory_map_from_bytes(buffer)).error.raise();
832
+ }
833
+
834
+ template <typename index_at> std::vector<typename index_at::stats_t> compute_levels_stats(index_at const& index) {
835
+ using stats_t = typename index_at::stats_t;
836
+ std::size_t max_level = index.max_level();
837
+ std::vector<stats_t> result(max_level + 1);
838
+ index.stats(result.data(), max_level);
839
+ return result;
840
+ }
841
+
773
842
  template <typename internal_at, typename external_at = internal_at, typename index_at = void>
774
843
  static py::tuple get_typed_vectors_for_keys(index_at const& index, py::buffer keys) {
775
844
 
@@ -865,30 +934,16 @@ PYBIND11_MODULE(compiled, m) {
865
934
  .value("I16", scalar_kind_t::i16_k)
866
935
  .value("I8", scalar_kind_t::i8_k);
867
936
 
868
- m.def("index_dense_metadata", [](std::string const& path) -> py::dict {
869
- index_dense_metadata_result_t meta = index_dense_metadata(path.c_str());
937
+ m.def("index_dense_metadata_from_path", [](std::string const& path) -> py::dict {
938
+ index_dense_metadata_result_t meta = index_dense_metadata_from_path(path.c_str());
870
939
  forward_error(meta);
940
+ return index_metadata(meta);
941
+ });
871
942
 
872
- index_dense_head_t const& head = meta.head;
873
-
874
- py::dict result;
875
- result["matrix_included"] = !meta.config.exclude_vectors;
876
- result["matrix_uses_64_bit_dimensions"] = meta.config.use_64_bit_dimensions;
877
-
878
- result["version"] = std::to_string(head.version_major) + "." + //
879
- std::to_string(head.version_minor) + "." + //
880
- std::to_string(head.version_patch);
881
-
882
- result["kind_metric"] = metric_kind_t(head.kind_metric);
883
- result["kind_scalar"] = scalar_kind_t(head.kind_scalar);
884
- result["kind_key"] = scalar_kind_t(head.kind_key);
885
- result["kind_compressed_slot"] = scalar_kind_t(head.kind_compressed_slot);
886
-
887
- result["count_present"] = std::uint64_t(head.count_present);
888
- result["count_deleted"] = std::uint64_t(head.count_deleted);
889
- result["dimensions"] = std::uint64_t(head.dimensions);
890
-
891
- return result;
943
+ m.def("index_dense_metadata_from_buffer", [](py::bytes const& buffer) -> py::dict {
944
+ index_dense_metadata_result_t meta = index_dense_metadata_from_buffer(memory_map_from_bytes(buffer));
945
+ forward_error(meta);
946
+ return index_metadata(meta);
892
947
  });
893
948
 
894
949
  m.def("exact_search", &search_many_brute_force, //
@@ -1034,8 +1089,9 @@ PYBIND11_MODULE(compiled, m) {
1034
1089
  [](dense_index_py_t const& index) -> std::size_t { return index.metric().dimensions(); });
1035
1090
  i.def_property_readonly( //
1036
1091
  "dtype", [](dense_index_py_t const& index) -> scalar_kind_t { return index.scalar_kind(); });
1037
- i.def_property_readonly( //
1038
- "memory_usage", [](dense_index_py_t const& index) -> std::size_t { return index.memory_usage(); });
1092
+
1093
+ i.def_property_readonly("serialized_length", &dense_index_py_t::serialized_length);
1094
+ i.def_property_readonly("memory_usage", &dense_index_py_t::memory_usage);
1039
1095
 
1040
1096
  i.def_property("expansion_add", &dense_index_py_t::expansion_add, &dense_index_py_t::change_expansion_add);
1041
1097
  i.def_property("expansion_search", &dense_index_py_t::expansion_search, &dense_index_py_t::change_expansion_search);
@@ -1138,9 +1194,14 @@ PYBIND11_MODULE(compiled, m) {
1138
1194
  },
1139
1195
  py::arg("offset"));
1140
1196
 
1141
- i.def("save", &save_index<dense_index_py_t>, py::arg("path"));
1142
- i.def("load", &load_index<dense_index_py_t>, py::arg("path"));
1143
- i.def("view", &view_index<dense_index_py_t>, py::arg("path"));
1197
+ i.def("save_index_to_path", &save_index_to_path<dense_index_py_t>);
1198
+ i.def("load_index_from_path", &load_index_from_path<dense_index_py_t>);
1199
+ i.def("view_index_from_path", &view_index_from_path<dense_index_py_t>);
1200
+
1201
+ i.def("save_index_to_buffer", &save_index_to_buffer<dense_index_py_t>);
1202
+ i.def("load_index_from_buffer", &load_index_from_buffer<dense_index_py_t>);
1203
+ i.def("view_index_from_buffer", &view_index_from_buffer<dense_index_py_t>);
1204
+
1144
1205
  i.def("reset", &reset_index<dense_index_py_t>);
1145
1206
  i.def("clear", &clear_index<dense_index_py_t>);
1146
1207
  i.def("copy", &copy_index, py::kw_only(), py::arg("copy") = true);
@@ -1155,7 +1216,8 @@ PYBIND11_MODULE(compiled, m) {
1155
1216
  i_stats.def_readonly("allocated_bytes", &punned_index_stats_t::allocated_bytes);
1156
1217
 
1157
1218
  i.def_property_readonly("max_level", &max_level<dense_index_py_t>);
1158
- i.def_property_readonly("levels_stats", &compute_stats<dense_index_py_t>);
1219
+ i.def_property_readonly("stats", &compute_stats<dense_index_py_t>);
1220
+ i.def_property_readonly("levels_stats", &compute_levels_stats<dense_index_py_t>);
1159
1221
  i.def("level_stats", &compute_level_stats<dense_index_py_t>, py::arg("level"));
1160
1222
 
1161
1223
  auto is = py::class_<dense_indexes_py_t>(m, "Indexes");
@@ -126,35 +126,63 @@ def test_index_stats(batch_size):
126
126
  index.add(keys, vectors, threads=threads)
127
127
 
128
128
  assert index.max_level >= 0
129
- assert index.levels_stats.nodes >= batch_size
129
+ assert index.stats.nodes >= batch_size
130
+ assert index.levels_stats[0].nodes == batch_size
130
131
  assert index.level_stats(0).nodes == batch_size
131
132
 
133
+ assert index.levels_stats[index.max_level].nodes > 0
134
+
132
135
 
133
136
  @pytest.mark.parametrize("ndim", [1, 3, 8, 32, 256, 4096])
134
- @pytest.mark.parametrize("batch_size", [1, 7, 1024])
137
+ @pytest.mark.parametrize("batch_size", [0, 1, 7, 1024])
135
138
  @pytest.mark.parametrize("quantization", [ScalarKind.F32, ScalarKind.I8])
136
139
  def test_index_save_load_restore_copy(ndim, quantization, batch_size):
137
140
  index = Index(ndim=ndim, dtype=quantization, multi=False)
138
- keys = np.arange(batch_size)
139
- vectors = random_vectors(count=batch_size, ndim=ndim)
140
- index.add(keys, vectors, threads=threads)
141
+
142
+ if batch_size > 0:
143
+ keys = np.arange(batch_size)
144
+ vectors = random_vectors(count=batch_size, ndim=ndim)
145
+ index.add(keys, vectors, threads=threads)
141
146
 
142
147
  index.save("tmp.usearch")
143
148
  index.clear()
144
149
  assert len(index) == 0
150
+ assert os.path.exists("tmp.usearch")
145
151
 
146
152
  index.load("tmp.usearch")
147
153
  assert len(index) == batch_size
148
- assert len(index[0].flatten()) == ndim
154
+ if batch_size > 0:
155
+ assert len(index[0].flatten()) == ndim
149
156
 
150
- index = Index.restore("tmp.usearch", view=True)
157
+ index_meta = Index.metadata("tmp.usearch")
158
+ assert index_meta is not None
159
+
160
+ index = Index.restore("tmp.usearch", view=False)
151
161
  assert len(index) == batch_size
152
- assert len(index[0].flatten()) == ndim
162
+ if batch_size > 0:
163
+ assert len(index[0].flatten()) == ndim
153
164
 
154
165
  copied_index = index.copy()
155
166
  assert len(copied_index) == len(index)
156
- assert np.allclose(np.vstack(copied_index.get(keys)), np.vstack(index.get(keys)))
157
-
167
+ if batch_size > 0:
168
+ assert np.allclose(
169
+ np.vstack(copied_index.get(keys)), np.vstack(index.get(keys))
170
+ )
171
+
172
+ # Perform the same operations in RAM, without touching the filesystem
173
+ serialized_index = index.save()
174
+ deserialized_metadata = Index.metadata(serialized_index)
175
+ assert deserialized_metadata is not None
176
+
177
+ deserialized_index = Index.restore(serialized_index)
178
+ assert len(deserialized_index) == len(index)
179
+ assert set(np.array(deserialized_index.keys)) == set(np.array(index.keys))
180
+ if batch_size > 0:
181
+ assert np.allclose(
182
+ np.vstack(deserialized_index.get(keys)), np.vstack(index.get(keys))
183
+ )
184
+
185
+ deserialized_index.reset()
158
186
  index.reset()
159
187
  os.remove("tmp.usearch")
160
188
 
@@ -21,14 +21,21 @@ from typing import (
21
21
  import numpy as np
22
22
  from tqdm import tqdm
23
23
 
24
- from usearch.compiled import Index as _CompiledIndex
25
- from usearch.compiled import Indexes as _CompiledIndexes
26
- from usearch.compiled import IndexStats as _CompiledIndexStats
24
+ # Precompiled symbols that won't be exposed directly:
25
+ from usearch.compiled import (
26
+ Index as _CompiledIndex,
27
+ Indexes as _CompiledIndexes,
28
+ IndexStats as _CompiledIndexStats,
29
+ index_dense_metadata_from_path as _index_dense_metadata_from_path,
30
+ index_dense_metadata_from_buffer as _index_dense_metadata_from_buffer,
31
+ exact_search as _exact_search,
32
+ )
27
33
 
28
- from usearch.compiled import index_dense_metadata as _index_dense_metadata
29
- from usearch.compiled import exact_search as _exact_search
30
- from usearch.compiled import MetricKind, ScalarKind, MetricSignature
34
+ # Precompiled symbols that will be exposed
31
35
  from usearch.compiled import (
36
+ MetricKind,
37
+ ScalarKind,
38
+ MetricSignature,
32
39
  DEFAULT_CONNECTIVITY,
33
40
  DEFAULT_EXPANSION_ADD,
34
41
  DEFAULT_EXPANSION_SEARCH,
@@ -60,6 +67,10 @@ DTypeLike = Union[str, ScalarKind]
60
67
 
61
68
  MetricLike = Union[str, MetricKind, CompiledMetric]
62
69
 
70
+ NoneType = type(None)
71
+
72
+ PathOrBuffer = Union[str, os.PathLike, bytes]
73
+
63
74
 
64
75
  def _normalize_dtype(dtype, metric: MetricKind = MetricKind.Cos) -> ScalarKind:
65
76
  if dtype is None or dtype == "":
@@ -559,29 +570,38 @@ class Index:
559
570
  self._compiled.load(path)
560
571
 
561
572
  @staticmethod
562
- def metadata(path: os.PathLike) -> Optional[dict]:
563
- path = os.fspath(path)
564
- if not os.path.exists(path):
565
- return None
573
+ def metadata(path_or_buffer: PathOrBuffer) -> Optional[dict]:
566
574
  try:
567
- return _index_dense_metadata(path)
568
- except Exception:
569
- return None
575
+ if isinstance(path_or_buffer, bytearray):
576
+ path_or_buffer = bytes(path_or_buffer)
577
+ if isinstance(path_or_buffer, bytes):
578
+ return _index_dense_metadata_from_buffer(path_or_buffer)
579
+ else:
580
+ path_or_buffer = os.fspath(path_or_buffer)
581
+ if not os.path.exists(path_or_buffer):
582
+ return None
583
+ return _index_dense_metadata_from_path(path_or_buffer)
584
+ except Exception as e:
585
+ raise e
570
586
 
571
587
  @staticmethod
572
- def restore(path: os.PathLike, view: bool = False) -> Optional[Index]:
573
- path = os.fspath(path)
574
- meta = Index.metadata(path)
588
+ def restore(path_or_buffer: PathOrBuffer, view: bool = False) -> Optional[Index]:
589
+ meta = Index.metadata(path_or_buffer)
575
590
  if not meta:
576
591
  return None
577
- return Index(
592
+
593
+ index = Index(
578
594
  ndim=meta["dimensions"],
579
595
  dtype=meta["kind_scalar"],
580
596
  metric=meta["kind_metric"],
581
- path=path,
582
- view=view,
583
597
  )
584
598
 
599
+ if view:
600
+ index.view(path_or_buffer)
601
+ else:
602
+ index.load(path_or_buffer)
603
+ return index
604
+
585
605
  def __len__(self) -> int:
586
606
  return self._compiled.__len__()
587
607
 
@@ -837,6 +857,10 @@ class Index:
837
857
  def ndim(self) -> int:
838
858
  return self._compiled.ndim
839
859
 
860
+ @property
861
+ def serialized_length(self) -> int:
862
+ return self._compiled.serialized_length
863
+
840
864
  @property
841
865
  def metric(self) -> Union[MetricKind, CompiledMetric]:
842
866
  return self._metric_jit if self._metric_jit else self._metric_kind
@@ -895,23 +919,34 @@ class Index:
895
919
  def expansion_search(self, v: int):
896
920
  self._compiled.expansion_search = v
897
921
 
898
- def save(self, path: Optional[os.PathLike] = None):
899
- path = path if path else self.path
900
- if path is None:
901
- raise Exception("Define `path` argument")
902
- self._compiled.save(os.fspath(path))
903
-
904
- def load(self, path: Optional[os.PathLike] = None):
905
- path = path if path else self.path
906
- if path is None:
907
- raise Exception("Define `path` argument")
908
- self._compiled.load(os.fspath(path))
909
-
910
- def view(self, path: Optional[os.PathLike] = None):
911
- path = path if path else self.path
912
- if path is None:
913
- raise Exception("Define `path` argument")
914
- self._compiled.view(os.fspath(path))
922
+ def save(self, path_or_buffer: Union[str, os.PathLike, NoneType] = None) -> Optional[bytes]:
923
+ path_or_buffer = path_or_buffer if path_or_buffer else self.path
924
+ if path_or_buffer is None:
925
+ return self._compiled.save_index_to_buffer()
926
+ else:
927
+ self._compiled.save_index_to_path(os.fspath(path_or_buffer))
928
+
929
+ def load(self, path_or_buffer: Union[str, os.PathLike, bytes, NoneType] = None):
930
+ path_or_buffer = path_or_buffer if path_or_buffer else self.path
931
+ if path_or_buffer is None:
932
+ raise Exception("Define the source")
933
+ if isinstance(path_or_buffer, bytearray):
934
+ path_or_buffer = bytes(path_or_buffer)
935
+ if isinstance(path_or_buffer, bytes):
936
+ self._compiled.load_index_from_buffer(path_or_buffer)
937
+ else:
938
+ self._compiled.load_index_from_path(os.fspath(path_or_buffer))
939
+
940
+ def view(self, path_or_buffer: Union[str, os.PathLike, bytes, bytearray, NoneType] = None):
941
+ path_or_buffer = path_or_buffer if path_or_buffer else self.path
942
+ if path_or_buffer is None:
943
+ raise Exception("Define the source")
944
+ if isinstance(path_or_buffer, bytearray):
945
+ path_or_buffer = bytes(path_or_buffer)
946
+ if isinstance(path_or_buffer, bytes):
947
+ self._compiled.view_index_from_buffer(path_or_buffer)
948
+ else:
949
+ self._compiled.view_index_from_path(os.fspath(path_or_buffer))
915
950
 
916
951
  def clear(self):
917
952
  """Erases all the vectors from the index, preserving the space for future insertions."""
@@ -1049,12 +1084,27 @@ class Index:
1049
1084
  return self._compiled.max_level + 1
1050
1085
 
1051
1086
  @property
1052
- def levels_stats(self) -> _CompiledIndexStats:
1087
+ def stats(self) -> _CompiledIndexStats:
1053
1088
  """Get the accumulated statistics for the entire multi-level graph.
1054
1089
 
1055
1090
  :return: Statistics for the entire multi-level graph.
1056
1091
  :rtype: _CompiledIndexStats
1057
1092
 
1093
+ Statistics:
1094
+ - ``nodes`` (int): The number of nodes in that level.
1095
+ - ``edges`` (int): The number of edges in that level.
1096
+ - ``max_edges`` (int): The maximum possible number of edges in that level.
1097
+ - ``allocated_bytes`` (int): The amount of allocated memory for that level.
1098
+ """
1099
+ return self._compiled.stats
1100
+
1101
+ @property
1102
+ def levels_stats(self) -> List[_CompiledIndexStats]:
1103
+ """Get the accumulated statistics for every level graph.
1104
+
1105
+ :return: Statistics for every level graph.
1106
+ :rtype: List[_CompiledIndexStats]
1107
+
1058
1108
  Statistics:
1059
1109
  - ``nodes`` (int): The number of nodes in that level.
1060
1110
  - ``edges`` (int): The number of edges in that level.
package/rust/README.md CHANGED
@@ -11,7 +11,7 @@ cargo add usearch
11
11
  ```rust
12
12
 
13
13
  let options = IndexOptions {
14
- dimensions: 5,
14
+ dimensions: 3,
15
15
  metric: MetricKind::IP,
16
16
  quantization: ScalarKind::F16,
17
17
  connectivity: 0,
@@ -36,7 +36,7 @@ assert_eq!(index.size(), 2);
36
36
 
37
37
  // Read back the tags
38
38
  let results = index.search(&first, 10).unwrap();
39
- assert_eq!(results.count, 2);
39
+ assert_eq!(results.keys.len(), 2);
40
40
  ```
41
41
 
42
42
  ## Multi-Threading
package/rust/lib.cpp CHANGED
@@ -47,12 +47,23 @@ size_t Index::dimensions() const { return index_->dimensions(); }
47
47
  size_t Index::connectivity() const { return index_->connectivity(); }
48
48
  size_t Index::size() const { return index_->size(); }
49
49
  size_t Index::capacity() const { return index_->capacity(); }
50
+ size_t Index::serialized_length() const { return index_->serialized_length(); }
50
51
 
51
- void Index::save(rust::Str path) const { index_->save(std::string(path).c_str()); }
52
- void Index::load(rust::Str path) const { index_->load(std::string(path).c_str()); }
53
- void Index::view(rust::Str path) const { index_->view(std::string(path).c_str()); }
52
+ void Index::save(rust::Str path) const { index_->save(output_file_t(std::string(path).c_str())).error.raise(); }
53
+ void Index::load(rust::Str path) const { index_->load(input_file_t(std::string(path).c_str())).error.raise(); }
54
+ void Index::view(rust::Str path) const { index_->view(memory_mapped_file_t(std::string(path).c_str())).error.raise(); }
54
55
 
55
- scalar_kind_t quantization(rust::Str quant) { return scalar_kind_from_name(quant.data(), quant.size()); }
56
+ void Index::save_to_buffer(rust::Slice<uint8_t> buffer) const {
57
+ index_->save(memory_mapped_file_t((byte_t*)buffer.data(), buffer.size())).error.raise();
58
+ }
59
+
60
+ void Index::load_from_buffer(rust::Slice<uint8_t const> buffer) const {
61
+ index_->load(memory_mapped_file_t((byte_t*)buffer.data(), buffer.size())).error.raise();
62
+ }
63
+
64
+ void Index::view_from_buffer(rust::Slice<uint8_t const> buffer) const {
65
+ index_->view(memory_mapped_file_t((byte_t*)buffer.data(), buffer.size())).error.raise();
66
+ }
56
67
 
57
68
  std::unique_ptr<Index> wrap(index_t&& index) {
58
69
  std::unique_ptr<index_t> punned_ptr;
package/rust/lib.hpp CHANGED
@@ -32,11 +32,16 @@ class Index {
32
32
  size_t connectivity() const;
33
33
  size_t size() const;
34
34
  size_t capacity() const;
35
+ size_t serialized_length() const;
35
36
 
36
37
  void save(rust::Str path) const;
37
38
  void load(rust::Str path) const;
38
39
  void view(rust::Str path) const;
39
40
 
41
+ void save_to_buffer(rust::Slice<uint8_t> buffer) const;
42
+ void load_from_buffer(rust::Slice<uint8_t const> buffer) const;
43
+ void view_from_buffer(rust::Slice<uint8_t const> buffer) const;
44
+
40
45
  private:
41
46
  std::unique_ptr<index_dense_t> index_;
42
47
  };