Skip to main content

qubit_io/wrappers/
position_guard.rs

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