qubit_io/wrappers/position_guard.rs
1// =============================================================================
2// Copyright (c) 2026 Haixing Hu.
3//
4// SPDX-License-Identifier: Apache-2.0
5//
6// Licensed under the Apache License, Version 2.0.
7// =============================================================================
8use std::io::{
9 Result,
10 Seek,
11 SeekFrom,
12};
13
14/// Guard that restores a seekable stream to its original position.
15///
16/// `PositionGuard` captures the stream position when it is created and seeks
17/// back to that position when the guard is dropped, unless
18/// [`PositionGuard::restore`] or [`PositionGuard::dismiss`] has already
19/// completed. Drop-time restoration errors are ignored because [`Drop::drop`]
20/// cannot return a [`Result`]; call [`PositionGuard::restore`] when the error
21/// must be observed. A failed drop-time restore does not panic and is not
22/// logged by this type.
23///
24/// # Examples
25/// ```
26/// use std::io::{
27/// Cursor,
28/// Seek,
29/// SeekFrom,
30/// };
31///
32/// use qubit_io::PositionGuard;
33///
34/// let mut stream = Cursor::new(b"abcdef".to_vec());
35/// stream.seek(SeekFrom::Start(2))?;
36///
37/// {
38/// let mut guard = PositionGuard::new(&mut stream)?;
39/// guard.inner_mut().seek(SeekFrom::End(0))?;
40/// }
41///
42/// assert_eq!(2, stream.position());
43/// # Ok::<(), std::io::Error>(())
44/// ```
45pub struct PositionGuard<'a, S>
46where
47 S: Seek + ?Sized,
48{
49 stream: &'a mut S,
50 position: u64,
51 done: bool,
52}
53
54impl<'a, S> PositionGuard<'a, S>
55where
56 S: Seek + ?Sized,
57{
58 /// Captures the current position of `stream`.
59 ///
60 /// # Parameters
61 /// - `stream`: Seekable stream to guard.
62 ///
63 /// # Returns
64 /// A guard that will restore the captured position on drop.
65 ///
66 /// # Errors
67 /// Returns the error reported by [`Seek::stream_position`] when the current
68 /// position cannot be read.
69 #[inline]
70 pub fn new(stream: &'a mut S) -> Result<Self> {
71 let position = stream.stream_position()?;
72 Ok(Self {
73 stream,
74 position,
75 done: false,
76 })
77 }
78
79 /// Returns the captured stream position.
80 ///
81 /// # Returns
82 /// The position captured when this guard was created.
83 #[inline]
84 pub fn position(&self) -> u64 {
85 self.position
86 }
87
88 /// Returns a mutable reference to the guarded stream.
89 ///
90 /// # Returns
91 /// The guarded stream reference.
92 #[inline]
93 pub fn inner_mut(&mut self) -> &mut S {
94 self.stream
95 }
96
97 /// Restores the captured position immediately.
98 ///
99 /// After a successful restore, drop-time restoration is disabled. If
100 /// restoring fails, drop will still make a best-effort restore attempt.
101 ///
102 /// # Errors
103 /// Returns the error reported by [`Seek::seek`] when the stream cannot seek
104 /// back to the captured position.
105 pub fn restore(&mut self) -> Result<()> {
106 self.stream.seek(SeekFrom::Start(self.position)).map(|_| {
107 self.done = true;
108 })
109 }
110
111 /// Disables drop-time restoration without moving the stream.
112 ///
113 /// This is useful when the caller intentionally wants to keep the stream at
114 /// its current position.
115 #[inline]
116 pub fn dismiss(mut self) {
117 self.done = true;
118 }
119}
120
121impl<S> Drop for PositionGuard<'_, S>
122where
123 S: Seek + ?Sized,
124{
125 fn drop(&mut self) {
126 if !self.done {
127 drop(self.stream.seek(SeekFrom::Start(self.position)));
128 }
129 }
130}