1#![allow(missing_docs)]
16
17use std::any::Any;
18use std::cmp::Ordering;
19use std::fmt::Debug;
20use std::fmt::Formatter;
21use std::fs::File;
22use std::io;
23use std::io::Read;
24use std::path::Path;
25use std::sync::Arc;
26
27use smallvec::smallvec;
28use thiserror::Error;
29
30use super::composite::AsCompositeIndex;
31use super::composite::ChangeIdIndexImpl;
32use super::composite::CompositeIndex;
33use super::composite::IndexSegment;
34use super::entry::IndexPosition;
35use super::entry::LocalPosition;
36use super::entry::SmallIndexPositionsVec;
37use super::entry::SmallLocalPositionsVec;
38use super::mutable::DefaultMutableIndex;
39use crate::backend::ChangeId;
40use crate::backend::CommitId;
41use crate::index::AllHeadsForGcUnsupported;
42use crate::index::ChangeIdIndex;
43use crate::index::Index;
44use crate::index::IndexError;
45use crate::index::MutableIndex;
46use crate::index::ReadonlyIndex;
47use crate::object_id::HexPrefix;
48use crate::object_id::ObjectId;
49use crate::object_id::PrefixResolution;
50use crate::revset::ResolvedExpression;
51use crate::revset::Revset;
52use crate::revset::RevsetEvaluationError;
53use crate::store::Store;
54
55#[derive(Debug, Error)]
57pub enum ReadonlyIndexLoadError {
58 #[error("Unexpected index version")]
59 UnexpectedVersion {
60 found_version: u32,
61 expected_version: u32,
62 },
63 #[error("Failed to load commit index file '{name}'")]
64 Other {
65 name: String,
67 #[source]
69 error: io::Error,
70 },
71}
72
73impl ReadonlyIndexLoadError {
74 fn invalid_data(
75 name: impl Into<String>,
76 error: impl Into<Box<dyn std::error::Error + Send + Sync>>,
77 ) -> Self {
78 Self::from_io_err(name, io::Error::new(io::ErrorKind::InvalidData, error))
79 }
80
81 fn from_io_err(name: impl Into<String>, error: io::Error) -> Self {
82 ReadonlyIndexLoadError::Other {
83 name: name.into(),
84 error,
85 }
86 }
87
88 pub(super) fn is_corrupt_or_not_found(&self) -> bool {
90 match self {
91 ReadonlyIndexLoadError::UnexpectedVersion { .. } => true,
92 ReadonlyIndexLoadError::Other { name: _, error } => {
93 matches!(
96 error.kind(),
97 io::ErrorKind::NotFound
98 | io::ErrorKind::InvalidData
99 | io::ErrorKind::UnexpectedEof
100 )
101 }
102 }
103 }
104}
105
106pub(crate) const INDEX_SEGMENT_FILE_FORMAT_VERSION: u32 = 6;
108
109pub(crate) const OVERFLOW_FLAG: u32 = 0x8000_0000;
111
112#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
114struct ParentIndexPosition(u32);
115
116impl ParentIndexPosition {
117 fn as_inlined(self) -> Option<IndexPosition> {
118 (self.0 & OVERFLOW_FLAG == 0).then_some(IndexPosition(self.0))
119 }
120
121 fn as_overflow(self) -> Option<u32> {
122 (self.0 & OVERFLOW_FLAG != 0).then_some(!self.0)
123 }
124}
125
126#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
128struct ChangeLocalPosition(u32);
129
130impl ChangeLocalPosition {
131 fn as_inlined(self) -> Option<LocalPosition> {
132 (self.0 & OVERFLOW_FLAG == 0).then_some(LocalPosition(self.0))
133 }
134
135 fn as_overflow(self) -> Option<u32> {
136 (self.0 & OVERFLOW_FLAG != 0).then_some(!self.0)
137 }
138}
139
140struct CommitGraphEntry<'a> {
141 data: &'a [u8],
142}
143
144impl CommitGraphEntry<'_> {
147 fn size(commit_id_length: usize) -> usize {
148 16 + commit_id_length
149 }
150
151 fn generation_number(&self) -> u32 {
152 u32::from_le_bytes(self.data[0..4].try_into().unwrap())
153 }
154
155 fn parent1_pos_or_overflow_pos(&self) -> ParentIndexPosition {
156 ParentIndexPosition(u32::from_le_bytes(self.data[4..8].try_into().unwrap()))
157 }
158
159 fn parent2_pos_or_overflow_len(&self) -> ParentIndexPosition {
160 ParentIndexPosition(u32::from_le_bytes(self.data[8..12].try_into().unwrap()))
161 }
162
163 fn change_id_lookup_pos(&self) -> u32 {
164 u32::from_le_bytes(self.data[12..16].try_into().unwrap())
165 }
166
167 fn commit_id(&self) -> CommitId {
168 CommitId::from_bytes(self.commit_id_bytes())
169 }
170
171 fn commit_id_bytes(&self) -> &[u8] {
173 &self.data[16..]
174 }
175}
176
177pub(super) struct ReadonlyIndexSegment {
222 parent_file: Option<Arc<ReadonlyIndexSegment>>,
223 num_parent_commits: u32,
224 name: String,
225 commit_id_length: usize,
226 change_id_length: usize,
227 num_local_commits: u32,
229 num_local_change_ids: u32,
230 num_change_overflow_entries: u32,
231 commit_lookup_base: usize,
233 change_id_table_base: usize,
234 change_pos_table_base: usize,
235 parent_overflow_base: usize,
236 change_overflow_base: usize,
237 data: Vec<u8>,
238}
239
240impl Debug for ReadonlyIndexSegment {
241 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
242 f.debug_struct("ReadonlyIndexSegment")
243 .field("name", &self.name)
244 .field("parent_file", &self.parent_file)
245 .finish()
246 }
247}
248
249impl ReadonlyIndexSegment {
250 pub(super) fn load(
252 dir: &Path,
253 name: String,
254 commit_id_length: usize,
255 change_id_length: usize,
256 ) -> Result<Arc<ReadonlyIndexSegment>, ReadonlyIndexLoadError> {
257 let mut file = File::open(dir.join(&name))
258 .map_err(|err| ReadonlyIndexLoadError::from_io_err(&name, err))?;
259 Self::load_from(&mut file, dir, name, commit_id_length, change_id_length)
260 }
261
262 pub(super) fn load_from(
264 file: &mut dyn Read,
265 dir: &Path,
266 name: String,
267 commit_id_length: usize,
268 change_id_length: usize,
269 ) -> Result<Arc<ReadonlyIndexSegment>, ReadonlyIndexLoadError> {
270 let from_io_err = |err| ReadonlyIndexLoadError::from_io_err(&name, err);
271 let read_u32 = |file: &mut dyn Read| {
272 let mut buf = [0; 4];
273 file.read_exact(&mut buf).map_err(from_io_err)?;
274 Ok(u32::from_le_bytes(buf))
275 };
276 let format_version = read_u32(file)?;
277 if format_version != INDEX_SEGMENT_FILE_FORMAT_VERSION {
278 return Err(ReadonlyIndexLoadError::UnexpectedVersion {
279 found_version: format_version,
280 expected_version: INDEX_SEGMENT_FILE_FORMAT_VERSION,
281 });
282 }
283 let parent_filename_len = read_u32(file)?;
284 let maybe_parent_file = if parent_filename_len > 0 {
285 let mut parent_filename_bytes = vec![0; parent_filename_len as usize];
286 file.read_exact(&mut parent_filename_bytes)
287 .map_err(from_io_err)?;
288 let parent_filename = String::from_utf8(parent_filename_bytes).map_err(|_| {
289 ReadonlyIndexLoadError::invalid_data(&name, "parent file name is not valid UTF-8")
290 })?;
291 let parent_file = ReadonlyIndexSegment::load(
292 dir,
293 parent_filename,
294 commit_id_length,
295 change_id_length,
296 )?;
297 Some(parent_file)
298 } else {
299 None
300 };
301 Self::load_with_parent_file(
302 file,
303 name,
304 maybe_parent_file,
305 commit_id_length,
306 change_id_length,
307 )
308 }
309
310 pub(super) fn load_with_parent_file(
313 file: &mut dyn Read,
314 name: String,
315 parent_file: Option<Arc<ReadonlyIndexSegment>>,
316 commit_id_length: usize,
317 change_id_length: usize,
318 ) -> Result<Arc<ReadonlyIndexSegment>, ReadonlyIndexLoadError> {
319 let from_io_err = |err| ReadonlyIndexLoadError::from_io_err(&name, err);
320 let read_u32 = |file: &mut dyn Read| {
321 let mut buf = [0; 4];
322 file.read_exact(&mut buf).map_err(from_io_err)?;
323 Ok(u32::from_le_bytes(buf))
324 };
325 let num_parent_commits = parent_file
326 .as_ref()
327 .map_or(0, |segment| segment.as_composite().num_commits());
328 let num_local_commits = read_u32(file)?;
329 let num_local_change_ids = read_u32(file)?;
330 let num_parent_overflow_entries = read_u32(file)?;
331 let num_change_overflow_entries = read_u32(file)?;
332 let mut data = vec![];
333 file.read_to_end(&mut data).map_err(from_io_err)?;
334
335 let commit_graph_entry_size = CommitGraphEntry::size(commit_id_length);
336 let graph_size = (num_local_commits as usize) * commit_graph_entry_size;
337 let commit_lookup_size = (num_local_commits as usize) * 4;
338 let change_id_table_size = (num_local_change_ids as usize) * change_id_length;
339 let change_pos_table_size = (num_local_change_ids as usize) * 4;
340 let parent_overflow_size = (num_parent_overflow_entries as usize) * 4;
341 let change_overflow_size = (num_change_overflow_entries as usize) * 4;
342
343 let graph_base = 0;
344 let commit_lookup_base = graph_base + graph_size;
345 let change_id_table_base = commit_lookup_base + commit_lookup_size;
346 let change_pos_table_base = change_id_table_base + change_id_table_size;
347 let parent_overflow_base = change_pos_table_base + change_pos_table_size;
348 let change_overflow_base = parent_overflow_base + parent_overflow_size;
349 let expected_size = change_overflow_base + change_overflow_size;
350
351 if data.len() != expected_size {
352 return Err(ReadonlyIndexLoadError::invalid_data(
353 name,
354 "unexpected data length",
355 ));
356 }
357
358 Ok(Arc::new(ReadonlyIndexSegment {
359 parent_file,
360 num_parent_commits,
361 name,
362 commit_id_length,
363 change_id_length,
364 num_local_commits,
365 num_local_change_ids,
366 num_change_overflow_entries,
367 commit_lookup_base,
368 change_id_table_base,
369 change_pos_table_base,
370 parent_overflow_base,
371 change_overflow_base,
372 data,
373 }))
374 }
375
376 pub(super) fn as_composite(&self) -> &CompositeIndex {
377 CompositeIndex::new(self)
378 }
379
380 pub(super) fn name(&self) -> &str {
381 &self.name
382 }
383
384 pub(super) fn commit_id_length(&self) -> usize {
385 self.commit_id_length
386 }
387
388 pub(super) fn change_id_length(&self) -> usize {
389 self.change_id_length
390 }
391
392 fn graph_entry(&self, local_pos: LocalPosition) -> CommitGraphEntry {
393 let table = &self.data[..self.commit_lookup_base];
394 let entry_size = CommitGraphEntry::size(self.commit_id_length);
395 let offset = (local_pos.0 as usize) * entry_size;
396 CommitGraphEntry {
397 data: &table[offset..][..entry_size],
398 }
399 }
400
401 fn commit_lookup_pos(&self, lookup_pos: u32) -> LocalPosition {
402 let table = &self.data[self.commit_lookup_base..self.change_id_table_base];
403 let offset = (lookup_pos as usize) * 4;
404 LocalPosition(u32::from_le_bytes(table[offset..][..4].try_into().unwrap()))
405 }
406
407 fn change_lookup_id(&self, lookup_pos: u32) -> ChangeId {
408 ChangeId::from_bytes(self.change_lookup_id_bytes(lookup_pos))
409 }
410
411 fn change_lookup_id_bytes(&self, lookup_pos: u32) -> &[u8] {
413 let table = &self.data[self.change_id_table_base..self.change_pos_table_base];
414 let offset = (lookup_pos as usize) * self.change_id_length;
415 &table[offset..][..self.change_id_length]
416 }
417
418 fn change_lookup_pos(&self, lookup_pos: u32) -> ChangeLocalPosition {
419 let table = &self.data[self.change_pos_table_base..self.parent_overflow_base];
420 let offset = (lookup_pos as usize) * 4;
421 ChangeLocalPosition(u32::from_le_bytes(table[offset..][..4].try_into().unwrap()))
422 }
423
424 fn overflow_parents(&self, overflow_pos: u32, num_parents: u32) -> SmallIndexPositionsVec {
425 let table = &self.data[self.parent_overflow_base..self.change_overflow_base];
426 let offset = (overflow_pos as usize) * 4;
427 let size = (num_parents as usize) * 4;
428 table[offset..][..size]
429 .chunks_exact(4)
430 .map(|chunk| IndexPosition(u32::from_le_bytes(chunk.try_into().unwrap())))
431 .collect()
432 }
433
434 fn overflow_changes_from(
436 &self,
437 overflow_pos: u32,
438 ) -> impl Iterator<Item = LocalPosition> + use<'_> {
439 let table = &self.data[self.change_overflow_base..];
440 let offset = (overflow_pos as usize) * 4;
441 table[offset..]
442 .chunks_exact(4)
443 .map(|chunk| LocalPosition(u32::from_le_bytes(chunk.try_into().unwrap())))
444 }
445
446 fn commit_id_byte_prefix_to_lookup_pos(&self, prefix: &[u8]) -> PositionLookupResult {
448 binary_search_pos_by(self.num_local_commits, |pos| {
449 let local_pos = self.commit_lookup_pos(pos);
450 let entry = self.graph_entry(local_pos);
451 entry.commit_id_bytes().cmp(prefix)
452 })
453 }
454
455 fn change_id_byte_prefix_to_lookup_pos(&self, prefix: &[u8]) -> PositionLookupResult {
457 binary_search_pos_by(self.num_local_change_ids, |pos| {
458 let change_id_bytes = self.change_lookup_id_bytes(pos);
459 change_id_bytes.cmp(prefix)
460 })
461 }
462}
463
464impl IndexSegment for ReadonlyIndexSegment {
465 fn num_parent_commits(&self) -> u32 {
466 self.num_parent_commits
467 }
468
469 fn num_local_commits(&self) -> u32 {
470 self.num_local_commits
471 }
472
473 fn parent_file(&self) -> Option<&Arc<ReadonlyIndexSegment>> {
474 self.parent_file.as_ref()
475 }
476
477 fn name(&self) -> Option<String> {
478 Some(self.name.clone())
479 }
480
481 fn commit_id_to_pos(&self, commit_id: &CommitId) -> Option<LocalPosition> {
482 self.commit_id_byte_prefix_to_lookup_pos(commit_id.as_bytes())
483 .ok()
484 .map(|pos| self.commit_lookup_pos(pos))
485 }
486
487 fn resolve_neighbor_commit_ids(
488 &self,
489 commit_id: &CommitId,
490 ) -> (Option<CommitId>, Option<CommitId>) {
491 self.commit_id_byte_prefix_to_lookup_pos(commit_id.as_bytes())
492 .map_neighbors(|pos| {
493 let local_pos = self.commit_lookup_pos(pos);
494 let entry = self.graph_entry(local_pos);
495 entry.commit_id()
496 })
497 }
498
499 fn resolve_commit_id_prefix(&self, prefix: &HexPrefix) -> PrefixResolution<CommitId> {
500 self.commit_id_byte_prefix_to_lookup_pos(prefix.min_prefix_bytes())
501 .prefix_matches(prefix, |pos| {
502 let local_pos = self.commit_lookup_pos(pos);
503 let entry = self.graph_entry(local_pos);
504 entry.commit_id()
505 })
506 .map(|(id, _)| id)
507 }
508
509 fn resolve_neighbor_change_ids(
510 &self,
511 change_id: &ChangeId,
512 ) -> (Option<ChangeId>, Option<ChangeId>) {
513 self.change_id_byte_prefix_to_lookup_pos(change_id.as_bytes())
514 .map_neighbors(|pos| self.change_lookup_id(pos))
515 }
516
517 fn resolve_change_id_prefix(
518 &self,
519 prefix: &HexPrefix,
520 ) -> PrefixResolution<(ChangeId, SmallLocalPositionsVec)> {
521 self.change_id_byte_prefix_to_lookup_pos(prefix.min_prefix_bytes())
522 .prefix_matches(prefix, |pos| self.change_lookup_id(pos))
523 .map(|(id, lookup_pos)| {
524 let change_pos = self.change_lookup_pos(lookup_pos);
525 if let Some(local_pos) = change_pos.as_inlined() {
526 (id, smallvec![local_pos])
527 } else {
528 let overflow_pos = change_pos.as_overflow().unwrap();
529 let positions: SmallLocalPositionsVec = self
533 .overflow_changes_from(overflow_pos)
534 .take_while(|&local_pos| {
535 let entry = self.graph_entry(local_pos);
536 entry.change_id_lookup_pos() == lookup_pos
537 })
538 .collect();
539 debug_assert_eq!(
540 overflow_pos + u32::try_from(positions.len()).unwrap(),
541 (lookup_pos + 1..self.num_local_change_ids)
542 .find_map(|lookup_pos| self.change_lookup_pos(lookup_pos).as_overflow())
543 .unwrap_or(self.num_change_overflow_entries),
544 "all overflow positions to the next change id should be collected"
545 );
546 (id, positions)
547 }
548 })
549 }
550
551 fn generation_number(&self, local_pos: LocalPosition) -> u32 {
552 self.graph_entry(local_pos).generation_number()
553 }
554
555 fn commit_id(&self, local_pos: LocalPosition) -> CommitId {
556 self.graph_entry(local_pos).commit_id()
557 }
558
559 fn change_id(&self, local_pos: LocalPosition) -> ChangeId {
560 let entry = self.graph_entry(local_pos);
561 self.change_lookup_id(entry.change_id_lookup_pos())
562 }
563
564 fn num_parents(&self, local_pos: LocalPosition) -> u32 {
565 let graph_entry = self.graph_entry(local_pos);
566 let pos1_or_overflow_pos = graph_entry.parent1_pos_or_overflow_pos();
567 let pos2_or_overflow_len = graph_entry.parent2_pos_or_overflow_len();
568 let inlined_len1 = pos1_or_overflow_pos.as_inlined().is_some() as u32;
569 let inlined_len2 = pos2_or_overflow_len.as_inlined().is_some() as u32;
570 let overflow_len = pos2_or_overflow_len.as_overflow().unwrap_or(0);
571 inlined_len1 + inlined_len2 + overflow_len
572 }
573
574 fn parent_positions(&self, local_pos: LocalPosition) -> SmallIndexPositionsVec {
575 let graph_entry = self.graph_entry(local_pos);
576 let pos1_or_overflow_pos = graph_entry.parent1_pos_or_overflow_pos();
577 let pos2_or_overflow_len = graph_entry.parent2_pos_or_overflow_len();
578 if let Some(pos1) = pos1_or_overflow_pos.as_inlined() {
579 if let Some(pos2) = pos2_or_overflow_len.as_inlined() {
580 smallvec![pos1, pos2]
581 } else {
582 smallvec![pos1]
583 }
584 } else {
585 let overflow_pos = pos1_or_overflow_pos.as_overflow().unwrap();
586 let num_parents = pos2_or_overflow_len.as_overflow().unwrap();
587 self.overflow_parents(overflow_pos, num_parents)
588 }
589 }
590}
591
592#[derive(Clone, Debug)]
594pub struct DefaultReadonlyIndex(Arc<ReadonlyIndexSegment>);
595
596impl DefaultReadonlyIndex {
597 pub(super) fn from_segment(segment: Arc<ReadonlyIndexSegment>) -> Self {
598 DefaultReadonlyIndex(segment)
599 }
600
601 pub(super) fn as_segment(&self) -> &Arc<ReadonlyIndexSegment> {
602 &self.0
603 }
604}
605
606impl AsCompositeIndex for DefaultReadonlyIndex {
607 fn as_composite(&self) -> &CompositeIndex {
608 self.0.as_composite()
609 }
610}
611
612impl Index for DefaultReadonlyIndex {
613 fn shortest_unique_commit_id_prefix_len(&self, commit_id: &CommitId) -> usize {
614 self.as_composite()
615 .shortest_unique_commit_id_prefix_len(commit_id)
616 }
617
618 fn resolve_commit_id_prefix(&self, prefix: &HexPrefix) -> PrefixResolution<CommitId> {
619 self.as_composite().resolve_commit_id_prefix(prefix)
620 }
621
622 fn has_id(&self, commit_id: &CommitId) -> bool {
623 self.as_composite().has_id(commit_id)
624 }
625
626 fn is_ancestor(&self, ancestor_id: &CommitId, descendant_id: &CommitId) -> bool {
627 self.as_composite().is_ancestor(ancestor_id, descendant_id)
628 }
629
630 fn common_ancestors(&self, set1: &[CommitId], set2: &[CommitId]) -> Vec<CommitId> {
631 self.as_composite().common_ancestors(set1, set2)
632 }
633
634 fn all_heads_for_gc(
635 &self,
636 ) -> Result<Box<dyn Iterator<Item = CommitId> + '_>, AllHeadsForGcUnsupported> {
637 Ok(Box::new(self.as_composite().all_heads()))
638 }
639
640 fn heads(
641 &self,
642 candidates: &mut dyn Iterator<Item = &CommitId>,
643 ) -> Result<Vec<CommitId>, IndexError> {
644 self.as_composite().heads(candidates)
645 }
646
647 fn evaluate_revset<'index>(
648 &'index self,
649 expression: &ResolvedExpression,
650 store: &Arc<Store>,
651 ) -> Result<Box<dyn Revset + 'index>, RevsetEvaluationError> {
652 self.as_composite().evaluate_revset(expression, store)
653 }
654}
655
656impl ReadonlyIndex for DefaultReadonlyIndex {
657 fn as_any(&self) -> &dyn Any {
658 self
659 }
660
661 fn as_index(&self) -> &dyn Index {
662 self
663 }
664
665 fn change_id_index(
666 &self,
667 heads: &mut dyn Iterator<Item = &CommitId>,
668 ) -> Box<dyn ChangeIdIndex> {
669 Box::new(ChangeIdIndexImpl::new(self.clone(), heads))
670 }
671
672 fn start_modification(&self) -> Box<dyn MutableIndex> {
673 Box::new(DefaultMutableIndex::incremental(self.0.clone()))
674 }
675}
676
677#[derive(Clone, Copy, Debug)]
679struct PositionLookupResult {
680 result: Result<u32, u32>,
683 size: u32,
684}
685
686impl PositionLookupResult {
687 fn ok(self) -> Option<u32> {
689 self.result.ok()
690 }
691
692 fn neighbors(self) -> (Option<u32>, Option<u32>) {
695 match self.result {
696 Ok(pos) => (pos.checked_sub(1), (pos + 1..self.size).next()),
697 Err(pos) => (pos.checked_sub(1), (pos..self.size).next()),
698 }
699 }
700
701 fn map_neighbors<T>(self, mut lookup: impl FnMut(u32) -> T) -> (Option<T>, Option<T>) {
703 let (prev_pos, next_pos) = self.neighbors();
704 (prev_pos.map(&mut lookup), next_pos.map(&mut lookup))
705 }
706
707 fn prefix_matches<T: ObjectId>(
710 self,
711 prefix: &HexPrefix,
712 lookup: impl FnMut(u32) -> T,
713 ) -> PrefixResolution<(T, u32)> {
714 let lookup_pos = self.result.unwrap_or_else(|pos| pos);
715 let mut matches = (lookup_pos..self.size)
716 .map(lookup)
717 .take_while(|id| prefix.matches(id))
718 .fuse();
719 match (matches.next(), matches.next()) {
720 (Some(id), None) => PrefixResolution::SingleMatch((id, lookup_pos)),
721 (Some(_), Some(_)) => PrefixResolution::AmbiguousMatch,
722 (None, _) => PrefixResolution::NoMatch,
723 }
724 }
725}
726
727fn binary_search_pos_by(size: u32, mut f: impl FnMut(u32) -> Ordering) -> PositionLookupResult {
729 let mut low = 0;
730 let mut high = size;
731 while low < high {
732 let mid = (low + high) / 2;
733 let cmp = f(mid);
734 low = if cmp == Ordering::Less { mid + 1 } else { low };
737 high = if cmp == Ordering::Greater { mid } else { high };
738 if cmp == Ordering::Equal {
739 let result = Ok(mid);
740 return PositionLookupResult { result, size };
741 }
742 }
743 let result = Err(low);
744 PositionLookupResult { result, size }
745}