Skip to main content

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}