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